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

83 lines
4.7 KiB
Markdown

# 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:
```python
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)