Login with ChatGPT
Reference

Server handler

createChatGPTHandler() options, defaults, and the getSession / proxyFetch / getModels helpers.

import { createChatGPTHandler } from "@opencoredev/loginwithchatgpt-server";

const auth = createChatGPTHandler(options);

Returns a ChatGPTHandler:

interface ChatGPTHandler {
  basePath: string;
  handler: (request: Request) => Promise<Response>;
  fetch: (request: Request) => Promise<Response>; // alias of handler
  proxyFetch: (request: Request) => typeof fetch;
  getSession: (request: Request) => Promise<PublicSession>;
  getModels: (request: Request) => Promise<string[] | undefined>;
  dangerouslyGetTokens: (request: Request, options?: { includeRefreshToken?: boolean }) => Promise<ChatGPTTokens | undefined>;
  /** @deprecated Prefer proxyFetch(), /responses, or /models. */
  getTokens: (request: Request, options?: { includeRefreshToken?: boolean }) => Promise<ChatGPTTokens | undefined>;
}

Mount handler at ${basePath}/*. It only requires Web-standard Request, Response, fetch, and crypto.subtle, so use it in runtimes that provide those APIs. The routes it serves are documented in HTTP routes.

Options

Server options

OptionTypeDefaultNotes
basePathstring"/api/chatgpt"Mount path; leading / added, trailing / stripped.
secretstringephemeral randomSigns cookies (HMAC-SHA256) and encrypts tokens (AES-GCM). Logs a warning when omitted. Always set it outside local dev.
sessionStoreKeyValueStore<StoredSession>new MemoryStore()Use a shared store in production.
cookieNamestring"lwc_session"Session cookie name.
cookiePartial<CookieOptions>{}Overrides: path, domain, maxAge, secure, httpOnly, sameSite.
sessionTtlMsnumber2_592_000_000 (30 days)Session and cookie lifetime.
defaultModelstring"gpt-5.5"Injected when a /responses body omits model.
enableResponsesProxybooleantruefalse disables the /responses and /models routes.
enableRealtimebooleantruefalse disables both direct and app-server Realtime routes.
realtimeRealtimeProxyPolicy{}Experimental private voice signaling policy.
responsesProxyResponsesProxyPolicy{}Guardrails below.
allowedOriginsreadonly string[][]Extra origins allowed on cookie-authenticated non-GET routes.
dangerouslyAllowTokenExportbooleanfalseEnables dangerouslyGetTokens() / deprecated getTokens(). Only use when your server code intentionally accepts custody of raw bearer tokens.
dangerouslyAllowRefreshTokenExportbooleanfalseAlso allows { includeRefreshToken: true }. Requires dangerouslyAllowTokenExport; use only for deliberate migration/export flows.

responsesProxy

OptionTypeDefaultNotes
allowedModelsreadonly string[] | (model: string) => booleanall allowedDisallowed models get 403 model_not_allowed.
maxRequestBytesnumber41_943_040 (40 MiB)Maximum raw JSON body size, including base64 image-editing inputs.
rateLimitfalse | { limit?, windowMs?, store? }{ limit: 30, windowMs: 60_000 }Per-session limit; false disables. Default store is per-instance memory; pass a shared store across instances.

realtime

OptionTypeDefaultNotes
maxRequestBytesnumber262_144Maximum JSON SDP + session body.
allowedTransportsreadonly ("wm" | "vp" | "vps")[]["wm"]Undocumented compatibility transports require explicit opt-in.
allowedModesreadonly ("wingman" | "advanced" | "standard")[]unrestrictedDisallowed requests get 403 realtime_mode_not_allowed.
sessionDefaultsChatGPTRealtimeSessionOptions{}Defaults merged before browser options.
getAuthcallbackunsetSupplies server-held ChatGPT web auth for direct /wm; Codex login tokens are intentionally not reused for that route.
appServerRealtimeAppServerPolicyunsetEnables desktop-style native handoffs and arbitrary server-side tools. Requires Node/Bun and the codex executable.

realtime.appServer

OptionTypeDefaultNotes
toolsreadonly RealtimeDynamicTool[]requiredServer-owned function names, descriptions, and JSON input schemas.
executeToolcallbackrequiredRevalidate arguments and execute under loginSessionId / the authenticated request snapshot.
confirmToolcallbackunsetRequired when any tool returns pendingConfirmation; called only by the authenticated confirmation route.
allowedModelsreadonly string[] | (model: string) => booleanall allowedRestricts the delegated execution model selected by the browser.
defaultModelstringunsetDelegated execution model when the browser omits one.
reasoningEffortReasoningEffort"low"App-server execution effort; low keeps voice-tool turns responsive.
sessionTtlMsnumber1_800_000Maximum Live process lifetime. A new call also closes the previous call for that login session.
commandreadonly string[]codex --enable realtime_conversation app-server --stdioApp-server executable override.
executionInstructionsstringbuilt-in tool-use instructionsInstructions for delegated execution turns.
realtimePromptstringbuilt-in native voice promptPrompt for the interruptible voice surface.
sessionFactory(options) => RealtimeAppServerSessionHandlebuilt-in sessionAdvanced process-hosting or test override using the minimal lifecycle contract.

The handler adds request, loginSessionId, and liveSessionId to tool and confirmation callbacks. Tokens stay inside the SDK. Tool results must be JSON-serializable. If executeTool returns pendingConfirmation, the bridge immediately completes the model-side tool request so the call remains responsive; the consequential action stays pending until the application calls the confirmation route.

Codex request defaults

Applied to proxied /responses bodies when the request does not set them (see request normalization):

OptionTypeEffective defaultNotes
instructionsstringa generic assistant promptSent when the body has no instructions.
reasoningEffort"none" | "low" | "medium" | "high" | "xhigh""medium"Overridden per request by the reasoning header.
reasoningSummarystring"auto"Merged into reasoning.
textVerbosity"low" | "medium" | "high""medium"Merged into text.
serviceTier"auto" | "default" | "flex" | "priority" | "fast"unsetApplied when the body has no service_tier.

Upstream configuration

Inherited from ChatGPTConfig. Override only when you know why (core reference): clientId, issuer, scope, codexBaseUrl, realtimeBaseUrl, Realtime client identifiers, originator, clientVersion, fetch.

Helpers

All three read the session cookie from the request and are safe in any server context (route handlers, server components, middleware).

getSession(request)

Returns public state without touching upstream:

const session = await auth.getSession(request);
// { status: "authenticated", user: { accountId, email, name, plan } }
// or { status: "unauthenticated" | "pending" | "expired" | "error" }

proxyFetch(request)

Creates a request-scoped fetch that carries the user's session cookie back through the mounted handler. Use it when you want a custom server route with AI SDK control, but you do not want application code to receive bearer tokens:

app/api/chat/route.ts
import { createChatGPTProxyProvider } from "@opencoredev/loginwithchatgpt-ai";
import { streamText } from "ai";

export async function POST(request: Request) {
  const chatgpt = createChatGPTProxyProvider({
    fetch: auth.proxyFetch(request),
  });
  const { prompt } = await request.json();

  const result = streamText({ model: chatgpt(), prompt });
  return result.toUIMessageStreamResponse();
}

The provider still calls /api/chatgpt/responses and /api/chatgpt/models; the handler loads, refreshes, and injects credentials internally.

getModels(request)

Returns the account's model slugs, or undefined when unauthenticated. Refreshes tokens before calling upstream. Throws ChatGPTAuthError with code models_request_failed on upstream failure.

dangerouslyGetTokens(request, options?)

Disabled unless createChatGPTHandler({ dangerouslyAllowTokenExport: true }) is set. When enabled, returns a fresh access token plus account id, with the refresh token redacted by default.

Exporting bearer tokens moves trust from the SDK boundary into your application code: server code can log, copy, or forward them. Prefer proxyFetch(request) for request handling. Only enable this for advanced integrations that intentionally accept that custody model.

const tokens = await auth.dangerouslyGetTokens(request);
// { accessToken, accountId, expiresAt } with no refreshToken

const exported = await auth.dangerouslyGetTokens(request, {
  includeRefreshToken: true,
});
// Requires dangerouslyAllowRefreshTokenExport: true. Use only for migration/export flows.

Session storage

Sessions are stored under the cookie's session id. When secret is set, token JSON is AES-GCM encrypted before writing. The store contract:

interface KeyValueStore<T> {
  get(key: string): Promise<T | undefined> | T | undefined;
  set(key: string, value: T, options?: { ttlMs?: number }): Promise<void> | void;
  delete(key: string): Promise<void> | void;
}

MemoryStore (exported for tests and local dev) enforces TTL lazily at read time. How the cookie, store, and refresh cycle fit together is covered in Sessions & tokens.

On this page