Login with ChatGPT
Concepts

Response proxy

What happens between streamText() in the browser and the ChatGPT-backed Codex endpoint.

The browser provider sends AI SDK traffic to POST /api/chatgpt/responses. The proxy exists so credentials never enter the browser, and so you have one chokepoint to police what a signed-in page can spend.

Request lifecycle

For every proxied request, the handler:

  1. Verifies the signed session cookie. No cookie means 401.
  2. Rejects cross-origin browser requests unless the origin is in allowedOrigins (403).
  3. Applies the per-session rate limit (429 with retry-after).
  4. Validates the JSON body: object shape, size under maxRequestBytes, model in allowedModels if configured.
  5. Loads tokens and refreshes them if needed.
  6. Normalizes the body for the stateless Codex backend, then adds the bearer token, account id header, and client_version query parameter.
  7. Streams the upstream body back with cache-control: no-store.

Guardrails and their defaults

Every proxied request spends the signed-in user's own plan, so the proxy is not a generic pass-through:

GuardrailDefaultOption
Rate limit30 requests / 60s per sessionresponsesProxy.rateLimit (false to disable)
Body size40 MiBresponsesProxy.maxRequestBytes
Model allowlistall modelsresponsesProxy.allowedModels (array or predicate)
Cross-origin browser callssame-origin onlyallowedOrigins
Default model injectiongpt-5.5 when the body omits modeldefaultModel
server.ts
const auth = createChatGPTHandler({
  secret: process.env.LWC_SECRET,
  responsesProxy: {
    allowedModels: ["gpt-5.5", "gpt-5.4", "gpt-5.4-mini"],
    rateLimit: { limit: 10, windowMs: 60_000 },
  },
});

The default rate-limit store is per instance

The rate limiter counts in memory. Behind multiple instances or serverless invocations, each instance counts separately. Pass a shared store in responsesProxy.rateLimit to enforce the limit globally.

If you need custom quotas, audit logging, or stricter endpoint controls, keep the request-scoped proxy pattern and wrap auth.proxyFetch(request) in your own route. Raw token export is available only through the explicit dangerous escape hatch in the server reference.

Request normalization

The ChatGPT-backed Codex endpoint is stateless and rejects some standard Responses API fields. createCodexFetch() from @opencoredev/loginwithchatgpt-core rewrites every body the same way for both the proxy and the direct provider:

  • Forces store: false. Turns are stateless.
  • Fills instructions, reasoning (effort: "medium", summary: "auto"), and text.verbosity: "medium" when the body leaves them out.
  • Adds reasoning.encrypted_content to include, so multi-turn reasoning survives statelessness.
  • Drops item_reference input items and strips id fields from the rest.
  • Removes max_output_tokens and max_completion_tokens, which the endpoint rejects.

SDK headers

Browser code cannot rewrite proxied bodies, so two narrow headers let it opt into request settings. The server validates each value, writes it into the body, and returns 400 for anything else. The values and UI patterns are in Fast tier & thinking effort; the exact contract is in the routes reference.

One behavior worth knowing here: if upstream rejects service_tier=fast for the session, the handler retries once without it and adds a x-login-with-chatgpt-service-tier-fallback: auto response header, so the request still streams.

On this page