#!/usr/bin/env python3 """Verify Alibaba Bailian Coding Plan keys (sk-sp- prefix). Endpoint: https://coding.dashscope.aliyuncs.com/v1 These are SEPARATE from regular DashScope paygo keys (sk-<32hex>) and use the coding-plan domain. Classification: only 401 = DEAD. A key that lists models but fails chat is NO_ACCESS/NO_BALANCE per body. """ import json, sys, time, urllib.request, urllib.error from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) from verify_cache import CachedVerifier BASE = "https://coding.dashscope.aliyuncs.com/v1" OUT = Path("results/codingplan") OUT.mkdir(parents=True, exist_ok=True) CHAT_MODEL = "qwen3-coder-plus" FAKES = ("xxxxxxxx", "your-", "example", "1234567890abcdefghij", "super-secret", "shared-test", "your-bailian") def http(method, path, key, body=None, timeout=20): url = BASE + path headers = {"Authorization": f"Bearer {key}", "Accept": "*/*"} data = None if body is not None: data = json.dumps(body).encode() headers["Content-Type"] = "application/json" req = urllib.request.Request(url, data=data, headers=headers, method=method) try: with urllib.request.urlopen(req, timeout=timeout) as r: return r.getcode(), r.read().decode("utf-8", "replace") except urllib.error.HTTPError as e: try: return e.code, e.read().decode("utf-8", "replace") except Exception: return e.code, "" except Exception as e: return 0, f"network: {type(e).__name__}: {e}" def verify(key): # cheapest auth check mc, mb = http("GET", "/models", key) # real chat regardless (models can 200 while chat is denied) cc, cb = http("POST", "/chat/completions", key, { "model": CHAT_MODEL, "messages": [{"role": "user", "content": "hi"}], "max_tokens": 5, "temperature": 0, }) blow = (cb or "").lower() if cc == 200 and '"choices"' in cb: return "USABLE", f"models={mc} chat 200 {cb[:100]}" if cc == 401: return "DEAD", f"401: {cb[:140]}" if cc in (402, 429): return "NO_BALANCE", f"models={mc} chat={cc}: {cb[:140]}" if cc == 403: return "NO_ACCESS", f"models={mc} 403: {cb[:140]}" if cc == 400: if any(w in blow for w in ("balance", "quota", "insufficient", "arrear", "suspend")): return "NO_BALANCE", f"models={mc} 400: {cb[:140]}" return "NO_ACCESS", f"models={mc} 400: {cb[:140]}" if cc == 0: return "UNKNOWN", f"models={mc} net: {cb[:140]}" if 500 <= cc < 600: return "NO_ACCESS", f"models={mc} 5xx {cc}: {cb[:120]}" return "NO_ACCESS", f"models={mc} HTTP {cc}: {cb[:140]}" def load_candidates(): keys = {} pool = Path("results/extracted_keys.txt") for ln in pool.read_text(errors="replace").splitlines(): p = ln.split("|", 3) if len(p) < 3: continue if p[0] not in ("AlibabaCodingPlan", "SCNet"): continue k = p[1].strip() if not k.startswith("sk-sp-"): continue if any(f in k.lower() for f in FAKES): continue # require plausible length: sk-sp- + at least 24 chars if len(k) < 30: continue if k not in keys: keys[k] = p[2] return keys def main(): keys = load_candidates() print(f"candidates: {len(keys)} sk-sp- keys", flush=True) buckets = {"USABLE": [], "NO_BALANCE": [], "NO_ACCESS": [], "UNKNOWN": [], "DEAD": []} with CachedVerifier("codingplan", verify) as ver: with ThreadPoolExecutor(max_workers=16) as pool: futs = {pool.submit(ver, k): (k, s) for k, s in keys.items()} done = 0 for fut in as_completed(futs): k, s = futs[fut]; done += 1 try: v, d = fut.result() except Exception as e: v, d = "UNKNOWN", f"exc:{e}" buckets[v].append((k, s, d)) if done % 20 == 0: print(f" {done}/{len(keys)} usable={len(buckets['USABLE'])} " f"nobal={len(buckets['NO_BALANCE'])} dead={len(buckets['DEAD'])} " f"hit={ver.hits} live={ver.live}", flush=True) print(f"cache: {ver.hits} hits / {ver.live} live", flush=True) names = {"USABLE":"usable.txt","NO_BALANCE":"no_balance.txt", "NO_ACCESS":"no_access.txt","UNKNOWN":"unknown.txt","DEAD":"dead.txt"} for label, fn in names.items(): with (OUT/fn).open("w") as f: for k,s,d in buckets[label]: f.write(f"{k}|{s}|{d}\n") print() for label in ("USABLE","NO_BALANCE","NO_ACCESS","UNKNOWN","DEAD"): print(f" {label:11s}: {len(buckets[label])}") if buckets["USABLE"]: print("\n=== USABLE CODING PLAN KEYS ===") for k,s,d in buckets["USABLE"]: print(f" {k}\n {s}\n {d[:120]}") if __name__ == "__main__": main()