Files
autoreg-skills-max/skills/chat2api-proxy/references/apodex-consumer-bridge-2026-10-03.md

4.7 KiB

Apodex consumer bridge — pool-of-farmed-accounts → OpenAI API (2026-10-03)

New chat2api variant: NOT one subscription + one token, but a pool of 100 free accounts (each with $20 welcome credits) behind one OpenAI-compatible front. Live-verified: apodex_bridge.py on :8420, standard ~11s/6cr, pro deep-research ~293s/54cr. Full Apodex specifics: skill apodex-api → references/web-chat-stream-bridge-2026-10-03.md.

Pattern (generalizable to any web-chat with account farms)

OpenAI client → bridge :8420 (stdlib http.server, ThreadingHTTPServer)
                  → Pool(accounts.json): acquire/release per request, thread-safe
                  → replay web app's internal SSE endpoint with Bearer consumer token
                  → final_answer event → OpenAI chat.completion (stream emulated as 3 SSE chunks)

Key design points that proved necessary:

  1. Rotate accounts per request, not per failure only. The web endpoint 429s on rapid same-account/same-IP use. Bridge marks 401/403 → dead, 429 → rotate + sleep, max N tries with an excluded-set so the same dead account isn't retried.
  2. Token TTL ≈ 1h and refresh_token also dies (single-use / short-lived on many platforms). Don't build on refresh — build a revival worker (bridge_reauth.py): re-login the SAME email via passwordless OTP (proxy-rotated send-code), credits persist. Bridge hot-reloads the pool file by mtime so revival works while serving.
  3. Balance gate: check /api/vip/info every ~40min per account, retire below floor.
  4. stdlib-only server = zero deps, survives any venv mess; -u for unbuffered logs.

Endpoint discovery when bundles don't reveal it

Next.js SPA lazy-chunks hid the chat client; grepping downloaded bundles failed. What worked (reusable):

  1. Mine storage keys from the bundles you DO have: grep localStorage.setItem, SIGNED_IN_COOKIE, persist-store names (zustand → mirage-auth-store).
  2. Playwright headless: goto site → seed the persist store with your API-obtained token: {"state":{"token":<jwt>,"expiresAt":<ms>,"refreshToken":null},"version":0}
    • set the signed-in cookie.
  3. Reload the app route → attach page.on('request')/page.on('response') → type a message in the UI → the real internal endpoint (method, headers, exact JSON body incl. client-generated uuids) lands in the capture log.
  4. Replay from urllib — no browser needed afterwards. Wrong seed key = app silently treats you as logged out (redirect to marketing page).

Pitfalls (new this session)

Zombie server on the port answering with OLD code

Symptom: fresh code works when called in-process (import bridge; run_completion(...)) but HTTP responses come back in ~1s with pre-patch behaviour. Cause: TWO listeners on :8420 (old process survived a "restart"), OS hands the connection to either. Fix: netstat -ano | grep :PORT → kill every PID listed → verify port free → start ONE server. Always diff "in-process works but HTTP doesn't" as a stale-server signal, not a code bug.

taskkill /F mangled by git-bash (MSYS)

taskkill //F //PID n → "Invalid argument/option - '//F'". MSYS path conversion eats the flags. Reliable kill from this environment:

subprocess.run(['taskkill','/F','/PID',pid], capture_output=True, text=True)

(via python one-liner in terminal). Also powershell Get-Process | Format-Table can hang >60s — prefer netstat+python kill.

Publishing a repo that has secrets in GIT HISTORY

Flipping an existing private repo to public exposes secrets in old commits even if HEAD is sanitized. This session: IMAP app-password was committed early. Correct flow: create a NEW public repo with a single clean commit (Contents API PUTs on an empty repo work once; git-data blobs/tree may 409 "Repository is empty" — just use Contents API sequentially). Before publishing, sweep every blob downloaded from the repo (not local files) with a regex for creds; rotate any secret that was ever committed.

Sanitization must include indirect leaks

Hardcoded mailbox + app-password appeared in 6 scripts, not just the main one. Sweep ALL repo-bound files for email|password|sk- patterns, replace with os.environ.get('X', 'placeholder'), then py_compile every file — and check import os is actually present (py_compile does NOT catch NameError-at-runtime).

Files (local + public repo ArthurMonteiro08586/apodex-credit-farm-public)

  • apodex_bridge.py — OpenAI front + pool (models apodex-web, apodex-web-pro)
  • bridge_reauth.py — OTP revival worker, proxy-rotated, atomic pool rewrite
  • capture_chat.py — Playwright localStorage-seed + request capture
  • test_stream.py — raw SSE dump of one query (event census)