5.2 KiB
Apodex $20 Promo Credits — Investigation (2026-10-03)
VERDICT (proven, 70+ accounts)
Email-OTP registration via client_id=apodex-platform-web gets $0 promotional credits. Verified on ~70 accounts:
GET https://platform.apodex.ai/v1/user/credit-grants→{"grants":[]}on every accountGET /v1/user/account→balance_cents:0,credits.active_cents:0,topup_promo:null,discount_promo:true(topup discount only, not money)GET /v1/user/resource-packages→{"active_packages":[],"expired_packages":[]}- Any
POST api.apodex.ai/v1/chat/completionswith the key →HTTP 402 insufficient_balance(key is valid, balance is zero) - Delayed-grant hypothesis disproven: account created 4h earlier still
grants:[].
What the "$20" actually is
- The email «Everything you need to make your first Apodex API call» is a PRICING email, not a credit grant. Its "$20.00" is the output price per request of the Deep Research tier (
Deep Research $5.00 input / $20.00 output, Deep Solve $25, Deep Discover $100). The "Limited-time 20% off Core Models" isdiscount_promo:true— discount at settlement, no code, no cash. - The «Promotional Credits US$20.00 Active, valid until 2026-11-02» screenshot matches the promo window of the consumer app welcome bonus (see below) — same expiry 2026-11-02 (
expires_at=1793616663). Accounts showing $20 were NOT created via our email-OTP platform flow.
TWO DIFFERENT PRODUCTS share auth.apodex.ai
| Developer platform | Consumer research app | |
|---|---|---|
| Host | platform.apodex.ai + api.apodex.ai | www.apodex.ai |
| client_id (verify-login) | apodex-platform-web |
apodex-web |
| Signup bonus for email OTP | NONE ($0) | Welcome Bonus 300 credits (/api/vip/info → credit_balance:300), expires 2026-11-02 |
| Token cross-validity | 401 on www APIs | 401 on platform APIs |
| API surface | /v1/chat/completions (OpenAI-compat), /v1/api-keys, /v1/user/* |
/api/vip/info, /api/vip/plans, /api/vip/boost, /api/vip/credit-transactions, /api/vip/invoices |
Consumer 300 credits are research-chat units, NOT USD, and cannot be spent via the API. Do not advertise them as "$20".
Console endpoint inventory (captured from real logged-in browser session)
Platform console calls these on load (use for audits — all accept Authorization: Bearer <access_token>):
GET /v1/user/account
GET /v1/user/credit-grants <- the authoritative promo-credit check
GET /v1/user/resource-packages
GET /v1/user/usage?days=30
GET /v1/payments/history?limit=20
GET /v1/api-keys
GET /auth/region
404 (do not exist): /v1/user/credits, /v1/promo-codes, /v1/credits, /v1/balance, /v1/promotions, /v1/coupons.
Remaining untested hypotheses for getting real $20 (in priority order)
- OAuth signup (Google / GitHub buttons on platform login). GitHub OAuth flow reachable:
https://auth.apodex.ai/api/auth/github?return_to=<platform url>(validates return_to host). One pooled GH account returnederror=access_denied(app previously authorized/revoked) — needs a fresh GH account per attempt, browser-based (Playwright; urllib dies on the state cookie/redirect chain). NOT yet proven to grant $20. - Geo/IP gating — signup from residential US IP. Not tested (free proxies all dead at the time).
- Promo code — no redeem/coupon endpoint exists in either bundle (
redeem:0, coupon:0, promotional:0hits in console+www JS, 3MB scanned). Likely dead end unless a code is applied at signup via query param. - The screenshot may simply be from the promo's landing/marketing or another user's OAuth account — treat channel posts claiming "$20 for everyone" as unverified bait until an OAuth/geo account reproduces it.
Auth flow details (platform, for OAuth work)
- Login page offers: email OTP, password, Continue with Google, Continue with GitHub.
- localStorage keys (from console bundle):
apodex_auth_token,apodex-user-storage,apodex_pp_access_token,apodex_pp_refresh_token,apodex_pp_access_expires_at,apodex_signed_in; cookieapodex_auth_token. - Injecting tokens into localStorage does NOT authenticate the console — it redirects to
/login?redirect=%2Fconsole. Only a real OTP/OAuth login in the browser yields a working console session; capture API responses during that session instead. - verify-login payload:
{"email":..., "code":..., "client_id":"apodex-platform-web"}→data.tokens.{access_token,refresh_token},user.id=email_xxxxxxxxxxxx.
Operational lesson: OTP stealing between concurrent processes
batch100.py running in background + a single-account probe both call read_otp() on the same Gmail inbox. The batch's reader consumes/marks messages first (UNSEEN search), so the probe gets OTP: None even though the mail arrived. Symptom: probe hangs 3 min then "no otp" while the batch log shows fresh OK registrations. Both processes hitting /api/auth/passwordless/send-code also share one rate-limit bucket → mutual 429 (PASSWORDLESS_SEND_RATE_LIMITED, backoff 60s×hits). Rule: before any single-shot OTP probe, kill ALL other python processes touching the inbox (psutil cmdline match on batch100.py; killing the terminal session is not enough if it spawned children).