AI chat (user)
HTTP routes and behavior for ai chat (user).
POST /api/ai/chat # SSE-streamed conversation with tool useGET /api/ai/available # effective availability + personal|shared|none sourceGET /api/ai/settings # provider catalog, write-only credential flags, effective sourcePUT /api/ai/settings # test + atomically save personal provider/model/key overrideDELETE /api/ai/settings # clear personal override and return to granted included accessPUT /api/ai/credentials/{provider} # test + set/replace one personal API key (write-only)DELETE /api/ai/credentials/{provider} # erase one personal API keyGET /api/ai/codex/status # personal linked state, safe account metadata, usage windowsPOST /api/ai/codex/device/begin # begin ChatGPT device authorizationGET /api/ai/codex/device/{flowID} # poll this user's pending device flowDELETE /api/ai/codex/device/{flowID} # cancel this user's pending device flowDELETE /api/ai/codex # unlink this user's ChatGPT account
GET /api/ai/grok/status # personal xAI link state and safe account metadataPOST /api/ai/grok/device/begin # begin xAI Grok device authorizationGET /api/ai/grok/device/{flowID} # poll this user's pending xAI device flowDELETE /api/ai/grok/device/{flowID} # cancel this user's pending xAI device flowDELETE /api/ai/grok # unlink this user's xAI accountAll personal settings, credentials, OAuth accounts, and device flows derive ownership from the authenticated caller; no user ID is accepted from the client. Both providers’ device/begin return a verification URL, one-time code, flow ID, expiry, and polling interval; the app opens the explicit ChatGPT or xAI browser sign-in while keeping access and refresh tokens off the device. Codex delegates its flow to the pinned app-server; the xAI flow is served directly against auth.x.ai with xAI’s public Grok CLI client, and its rotating refresh tokens are persisted per use. API keys and completed OAuth authorization are AES-256-GCM encrypted at rest, and responses expose configured booleans rather than secret values. A provider, model, key, or completed OAuth selection is accepted only after the exact candidate completes a small tool-free response turn; a failure leaves the previous key and selection unchanged.
Resolution is deterministic: a personal selection row is the explicit override; otherwise a user with an included-access grant receives the admin’s shared provider. If the personal key, OAuth link, runtime, or allowance is unavailable, that request fails as personal instead of silently switching accounts and spending shared quota. The source is resolved once per request and never changes during a provider turn. Chat admission is non-blocking and cost-aware: one active turn per user, 16 turns server-wide, and at most four included-provider turns; excess requests are rejected before any provider call.