import sys sys.dont_write_bytecode = True import argparse import os import re import requests sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from keycheck_common import ( append_jsonl, commit_status_transaction, default_input_file, default_proxy_file, ensure_output_files, iter_findings, load_checked_statuses, load_known_keys, load_proxies, mask_secret, read_plain_keys, recover_status_transaction, request_error_message, record_validation_result, require_provider_authority, service_output_dir, should_skip_key, write_keycheck_event, ) SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) PARENT_DIR = os.path.dirname(SCRIPT_DIR) SERVICE = "anthropic" OUTPUT_DIR = os.getenv("KEYCHECK_OUTPUT_DIR") or service_output_dir(SERVICE) INPUT_FILE = os.getenv("KEYCHECK_INPUT_FILE") or default_input_file() PROXY_FILE = os.getenv("KEYCHECK_PROXY_FILE") or default_proxy_file() CHECKED_FILE = os.path.join(OUTPUT_DIR, "anthropicChecked.txt") RESULTS_FILE = os.path.join(OUTPUT_DIR, "anthropicResults.jsonl") STATUS_FILES = { "VALID": os.path.join(OUTPUT_DIR, "anthropicAlive.txt"), "NO_QUOTA": os.path.join(OUTPUT_DIR, "anthropicNoQuota.txt"), "DEAD": os.path.join(OUTPUT_DIR, "anthropicDead.txt"), "LIMITED": os.path.join(OUTPUT_DIR, "anthropicLimited.txt"), "RESTRICTED": os.path.join(OUTPUT_DIR, "anthropicRestricted.txt"), "NETWORK": os.path.join(OUTPUT_DIR, "anthropicNetwork.txt"), "UNKNOWN": os.path.join(OUTPUT_DIR, "anthropicUnknown.txt"), } ANTHROPIC_REGEX = re.compile(r"sk-ant-(?:api03|admin01)-[A-Za-z0-9\-_]{93}AA|sk-ant-[A-Za-z0-9\-_]{86}") ANTHROPIC_ADMIN_PREFIX = "sk-ant-admin01-" ANTHROPIC_ADMIN_API_KEYS_URL = "https://api.anthropic.com/v1/organizations/api_keys" def ensure_files(): ensure_output_files([CHECKED_FILE, RESULTS_FILE, *STATUS_FILES.values()]) recover_status_transaction(CHECKED_FILE, STATUS_FILES) def extract_candidates(input_file, plain_files): seen_plain = set() for item in iter_findings(input_file, ["Anthropic"]): key = item["raw"] if key and ANTHROPIC_REGEX.fullmatch(key): yield key, item["source"], item["finding"] for item in read_plain_keys(plain_files, ANTHROPIC_REGEX): key = item["key"] if key not in seen_plain: seen_plain.add(key) yield key, item["source"], {} def tier_from_rpm(rpm): mapping = {5: "Free Tier", 50: "Tier 1", 1000: "Tier 2", 2000: "Tier 3", 4000: "Tier 4"} return mapping.get(rpm, "Scale/Unknown") def anthropic_rate_headers(response): output = {} for name, value in response.headers.items(): lowered = name.lower() if lowered.startswith("anthropic-ratelimit-"): output[lowered.replace("anthropic-ratelimit-", "rate_").replace("-", "_")] = value return output def list_models(key, proxy, timeout): headers = { "anthropic-version": "2023-06-01", "x-api-key": key, } try: response = requests.get("https://api.anthropic.com/v1/models", headers=headers, proxies=proxy, timeout=timeout) except requests.RequestException as exc: return {"models_error": str(exc)[:500]} if response.status_code != 200: return {"models_status": response.status_code, "models_error": request_error_message(response)} try: data = response.json() except ValueError: return {"models_status": response.status_code, "models_error": "invalid JSON response"} models = [] for item in data.get("data") or []: if isinstance(item, dict) and item.get("id"): models.append(item["id"]) return { "models_status": response.status_code, "models_count": len(models), "models": models[:50], } def check_admin_key(key, proxy, timeout): headers = { "anthropic-version": "2023-06-01", "x-api-key": key, } try: response = requests.get( ANTHROPIC_ADMIN_API_KEYS_URL, headers=headers, params={"limit": 1}, proxies=proxy, timeout=timeout, ) except requests.RequestException as exc: return {"status": "NETWORK", "admin": True, "message": str(exc)[:500]} if response.status_code == 200: return { "status": "VALID", "admin": True, "http_status": 200, "message": "Anthropic Admin API access confirmed", } status = { 401: "DEAD", 403: "RESTRICTED", 429: "LIMITED", }.get(response.status_code, "UNKNOWN") message = request_error_message(response).replace(key, "***REDACTED***") return { "status": status, "admin": True, "http_status": response.status_code, "message": message, } def check_key(key, proxy, timeout, model="claude-opus-4-6", include_models=False): if key.startswith(ANTHROPIC_ADMIN_PREFIX): return check_admin_key(key, proxy, timeout) url = "https://api.anthropic.com/v1/messages" headers = { "content-type": "application/json", "anthropic-version": "2023-06-01", "x-api-key": key, } payload = { "model": model, "messages": [{"role": "user", "content": "ping"}], "max_tokens": 1, } try: response = requests.post(url, headers=headers, json=payload, proxies=proxy, timeout=timeout) except requests.RequestException as exc: return {"status": "NETWORK", "message": str(exc)} if response.status_code == 200: rpm = 0 try: rpm = int(response.headers.get("anthropic-ratelimit-requests-limit", "0")) except ValueError: rpm = 0 rate_data = anthropic_rate_headers(response) result = { "status": "VALID", "model": model, "rpm": rpm, "tier": tier_from_rpm(rpm), **rate_data, } if include_models: result.update(list_models(key, proxy, timeout)) token_limit = rate_data.get("rate_tokens_limit") or "" token_remaining = rate_data.get("rate_tokens_remaining") or "" token_part = f" tokens={token_remaining}/{token_limit}" if token_limit or token_remaining else "" result["message"] = f"model={model}; rpm={rpm}; tier={result['tier']}{token_part}" return result if response.status_code == 429: return {"status": "LIMITED", "http_status": 429, "message": request_error_message(response)} message = request_error_message(response) lower = message.lower() if "credit balance is too low" in lower or "usage limits" in lower: return {"status": "NO_QUOTA", "http_status": response.status_code, "message": message} if response.status_code in (401, 403): status = "RESTRICTED" if "disabled" in lower or response.status_code == 403 else "DEAD" return {"status": status, "http_status": response.status_code, "message": message} return {"status": "UNKNOWN", "http_status": response.status_code, "message": message} def write_result(key, result, source, finding): write_keycheck_event(SERVICE, RESULTS_FILE, key, result, source, finding, "Anthropic") commit_status_transaction( CHECKED_FILE, STATUS_FILES, key, result["status"], result.get("message", ""), source, ) record_validation_result(SERVICE, key, result, source, finding, "Anthropic") def parse_args(): parser = argparse.ArgumentParser(description="Anthropic key checker") parser.add_argument("--input", default=INPUT_FILE) parser.add_argument("--plain", action="append", default=[]) parser.add_argument("--proxy-file", default=PROXY_FILE) parser.add_argument("--timeout", type=int, default=20) parser.add_argument("--model", default=os.getenv("ANTHROPIC_CHECK_MODEL", "claude-opus-4-6")) parser.add_argument("--list-models", action="store_true") parser.add_argument("--max-keys", type=int, default=0) parser.add_argument("--retry-network", action="store_true") parser.add_argument("--retry-limited", action="store_true") parser.add_argument("--retry-unknown", action="store_true") parser.add_argument("--retry-no-balance", action="store_true") parser.add_argument("--recheck-all", action="store_true") return parser.parse_args() def main(): require_provider_authority(SERVICE) args = parse_args() ensure_files() proxy_cycler = load_proxies(args.proxy_file) checked = load_checked_statuses(CHECKED_FILE) known = load_known_keys(CHECKED_FILE, STATUS_FILES) retry_statuses = set() if args.retry_network: retry_statuses.add("NETWORK") if args.retry_limited: retry_statuses.add("LIMITED") if args.retry_unknown: retry_statuses.add("UNKNOWN") if args.retry_no_balance: retry_statuses.add("NO_QUOTA") processed = 0 skipped = 0 for key, source, finding in extract_candidates(args.input, args.plain): if should_skip_key(key, checked, known, args, retry_statuses, service=SERVICE, source=source, finding=finding, detector="Anthropic"): skipped += 1 continue if args.max_keys and processed >= args.max_keys: break processed += 1 print(f"\n[{processed}] Anthropic candidate {mask_secret(key)} from {source}") proxy = next(proxy_cycler) if proxy_cycler else None result = check_key(key, proxy, args.timeout, args.model, args.list_models) print(f" STATUS: {result['status']} | {result.get('message', '')[:200]}") write_result(key, result, source, finding) known.add(key) checked[key] = result["status"] print(f"\nDone. Processed={processed}, skipped={skipped}, results={RESULTS_FILE}") if __name__ == "__main__": main()