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
| Option | Type | Default | Notes |
|---|---|---|---|
basePath | string | "/api/chatgpt" | Mount path; leading / added, trailing / stripped. |
secret | string | ephemeral random | Signs cookies (HMAC-SHA256) and encrypts tokens (AES-GCM). Logs a warning when omitted. Always set it outside local dev. |
sessionStore | KeyValueStore<StoredSession> | new MemoryStore() | Use a shared store in production. |
cookieName | string | "lwc_session" | Session cookie name. |
cookie | Partial<CookieOptions> | {} | Overrides: path, domain, maxAge, secure, httpOnly, sameSite. |
sessionTtlMs | number | 2_592_000_000 (30 days) | Session and cookie lifetime. |
defaultModel | string | "gpt-5.5" | Injected when a /responses body omits model. |
enableResponsesProxy | boolean | true | false disables the /responses and /models routes. |
responsesProxy | ResponsesProxyPolicy | {} | Guardrails below. |
allowedOrigins | readonly string[] | [] | Extra origins allowed on cookie-authenticated non-GET routes. |
dangerouslyAllowTokenExport | boolean | false | Enables dangerouslyGetTokens() / deprecated getTokens(). Only use when your server code intentionally accepts custody of raw bearer tokens. |
dangerouslyAllowRefreshTokenExport | boolean | false | Also allows { includeRefreshToken: true }. Requires dangerouslyAllowTokenExport; use only for deliberate migration/export flows. |
responsesProxy
| Option | Type | Default | Notes |
|---|---|---|---|
allowedModels | readonly string[] | (model: string) => boolean | all allowed | Disallowed models get 403 model_not_allowed. |
maxRequestBytes | number | 41_943_040 (40 MiB) | Maximum raw JSON body size, including base64 image-editing inputs. |
rateLimit | false | { 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. |
Codex request defaults
Applied to proxied /responses bodies when the request does not set them
(see request normalization):
| Option | Type | Effective default | Notes |
|---|---|---|---|
instructions | string | a generic assistant prompt | Sent when the body has no instructions. |
reasoningEffort | "none" | "low" | "medium" | "high" | "xhigh" | "medium" | Overridden per request by the reasoning header. |
reasoningSummary | string | "auto" | Merged into reasoning. |
textVerbosity | "low" | "medium" | "high" | "medium" | Merged into text. |
serviceTier | "auto" | "default" | "flex" | "priority" | "fast" | unset | Applied when the body has no service_tier. |
Upstream configuration
Inherited from ChatGPTConfig. Override only when you know why
(core reference): clientId, issuer, scope,
codexBaseUrl, 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:
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.