Core primitives
ChatGPTConfig, exported defaults, and the device-flow, token, and transport primitives in @opencoredev/loginwithchatgpt-core.
@opencoredev/loginwithchatgpt-core contains the primitives the other packages are built
on. Reach for it when building headless logins, custom
handlers, or tests. Typical apps never import it directly.
It has no dependencies and runs anywhere with Web-standard fetch and
crypto.
ChatGPTConfig
Every package accepts these fields and resolves them with resolveConfig():
| Option | Default | Notes |
|---|---|---|
clientId | the public Codex client id | Override only if you control a compatible client. |
issuer | "https://auth.openai.com" | Derives the OAuth, device-code, and token URLs. |
scope | "openid profile email offline_access" | offline_access is required for refresh tokens. |
codexBaseUrl | "https://chatgpt.com/backend-api/codex" | Base URL for models and responses. |
originator | "codex_cli_rs" | Sent as a header to upstream endpoints. |
clientVersion | "0.142.5" | Gates model availability; see client version drift. |
fetch | global fetch | Inject for testing, proxying, or exotic runtimes. |
The concrete defaults are exported as constants (DEFAULT_CLIENT_ID,
DEFAULT_ISSUER, DEFAULT_SCOPE, DEFAULT_CODEX_BASE_URL,
DEFAULT_ORIGINATOR, DEFAULT_CLIENT_VERSION, DEFAULT_MODEL). Treat them
as operational defaults, not guarantees. The upstream endpoints can change
independently of this package.
Device flow
import {
requestDeviceCode,
pollDeviceCode,
exchangeDeviceAuthorization,
waitForDeviceTokens,
resolveConfig,
} from "@opencoredev/loginwithchatgpt-core";
const config = resolveConfig();
const device = await requestDeviceCode(config);
// { deviceAuthId, userCode, verificationUrl, interval, expiresAt }pollDeviceCode(config, device)runs one poll and returns{ status: "pending" }or{ status: "authorized", … }.exchangeDeviceAuthorization(config, poll)trades an authorized poll result forChatGPTTokens.waitForDeviceTokens(config, device, options?)is the loop in one call, withsignal,onPoll, andintervalMsoptions.
Device codes live for 15 minutes (DEVICE_CODE_TTL_MS).
Tokens
interface ChatGPTTokens {
accessToken: string;
refreshToken?: string;
idToken?: string;
accountId?: string; // chatgpt_account_id claim
expiresAt?: number; // epoch ms
}refreshTokens(config, refreshToken)performs one refresh and throwsrefresh_token_invalidwhen the token is dead.ensureFreshTokens(config, tokens, { onRefresh?, force? })refreshes only when needed (60-second early margin), with a persistence callback.isAccessTokenExpired(tokens)is the same margin check, standalone.isRefreshTokenInvalid(error)is the type guard for the dead-refresh-token case.
JWT and PKCE helpers
parseUser(idToken)buildsChatGPTUser(accountId,email,name,plan) from id-token claims.decodeJwt(token),getTokenExpiry(token), andderiveAccountId(token)decode payloads without verifying signatures, so treat their output as claims, not proof.generatePkce(),createState(), andrandomToken(length?)support the loopback redirect flow (createAuthorizationUrl()plusexchangeAuthorizationCode()), which exists alongside the device flow for environments that can listen on localhost.
Codex transport
This layer makes AI SDK traffic acceptable to the ChatGPT-backed backend:
createCodexFetch({ config, getAuth, ...CodexResponsesOptions })returns a fetch-compatible function that rewrites URLs ontocodexBaseUrl, injects the bearer token andchatgpt-account-idheader, addsclient_version, and normalizes/responsesbodies.normalizeResponsesBody(body, options?)is the body rewrite by itself (rules).listCodexModels({ config, getAuth })andextractCodexModelSlugs(value)handle model listing and tolerant slug extraction.
Store
KeyValueStore<T> and MemoryStore<T> are the
session-store contract used by
the server package, exported here so custom stores need no extra dependency.
Everything these functions can throw is listed in Error codes.