# Apodex Passwordless Auth Flow > Discovered 2026-06-26. This is the CORRECT auth flow, NOT the now-dead Supabase flow. ## Overview Apodex uses a passwordless auth system proxied through `www.apodex.ai`, NOT direct Supabase calls. The old Supabase project (`hgejqhubxceqppqkgqgx.supabase.co`) is dead — DNS fails everywhere. ## Endpoints Both work from Python WITHOUT cookies, CSRF tokens, or special headers: | Endpoint | Method | Returns | |----------|--------|---------| | `https://www.apodex.ai/api/auth/passwordless/send-code` | POST | 200 + `{"success":true,"message":"Login code sent..."}` | | `https://www.apodex.ai/api/auth/passwordless/verify-login` | POST | 200 + `{"success":true,"data":{"access_token":"eyJ...","user":{...}}}` | ## Full Flow (Python) ```python import requests import imaplib, ssl, re, time, email EMAIL = '[REDACTED-EMAIL]' PASSWORD = '[REDACTED]' # Step 1: Send OTP s = requests.Session() r = s.post('https://www.apodex.ai/api/auth/passwordless/send-code', json={'email': EMAIL}, timeout=15) assert r.status_code == 200, f'Send code failed: {r.status_code} {r.text}' # Step 2: Get OTP from IMAP time.sleep(3) ctx = ssl.create_default_context() imap = imaplib.IMAP4_SSL('secureimap.t-online.de', 993, ssl_context=ctx, timeout=15) imap.login(EMAIL, PASSWORD) imap.select('INBOX') _, msgs = imap.search(None, 'FROM', 'apodex') _, data = imap.fetch(msgs[0].split()[-1], '(RFC822)') raw = data[0][1].decode('utf-8', errors='ignore') msg = email.message_from_string(raw) body = '' if msg.is_multipart() else msg.get_payload(decode=True).decode('utf-8', errors='ignore') if msg.is_multipart(): for part in msg.walk(): if part.get_content_type() == 'text/plain': body = part.get_payload(decode=True).decode('utf-8', errors='ignore') break otp = re.findall(r'\b\d{6}\b', body)[0] imap.logout() # Step 3: Verify OTP r = s.post('https://www.apodex.ai/api/auth/passwordless/verify-login', json={'email': EMAIL, 'code': otp}, timeout=15) data = r.json() access_token = data['data']['access_token'] # Apodex JWT # Step 4: Use token (limited) r = requests.get('https://www.apodex.ai/api/auth/get_user_info', headers={'Authorization': f'Bearer {access_token}'}) # ✅ Works — returns user profile # ❌ What does NOT work: r = requests.get('https://api.apodex.ai/v1/api-keys', headers={'Authorization': f'Bearer {access_token}'}) # Returns 401 {"error":"invalid token"} — need Supabase JWT instead # But the existing sk- keys DO work: r = requests.get('https://api.apodex.ai/v1/models', headers={'Authorization': 'Bearer [REDACTED-KEY]='}) # ✅ Returns model list ``` ## Token Comparison ``` Apodex JWT (from verify-login): Header: {"alg":"HS256","typ":"JWT"} Payload: {"user_id":"email_xxx","exp":1785xxxxxx,"session_id":"uuid"} → Works for: www.apodex.ai/api/auth/* → Fails for: api.apodex.ai/v1/api-keys Supabase JWT (from Supabase auth, now DEAD): Header: {"alg":"HS256","typ":"JWT"} Payload: {"iss":"supabase","ref":"hgejqhubxceqppqkgqgx","role":"anon","sub":"uuid","aud":"authenticated"} → Works for: api.apodex.ai/v1/api-keys (but Supabase project is dead) API Key (sk-...): opaque base64-encoded string → Works for: api.apodex.ai/v1/chat/completions, api.apodex.ai/v1/models ``` ## Rate Limit - Endpoint: `POST /api/auth/passwordless/send-code` - After ~8-10 requests: HTTP 429 - Body: `{"code":"PASSWORDLESS_SEND_RATE_LIMITED","message":"Operation too frequent, please try again later"}` - Scope: IP-based (not cookie-based) - Recovery: Wait 5-10 minutes - Bypass: Rotate IP via VPN/proxy ## Headers Observed in Response ``` set-cookie: session=; HttpOnly; Max-Age=3600; Path=/; SameSite=Lax ``` This session cookie is set on send-code and may be needed for verify-login.