All posts
guide

Sign in with ChatGPT API: set up plan usage safely

A ChatGPT login alone doesn't fund inference. Follow the OAuth, host ID, token storage, and Responses rules, then check whether your app needs commercial approval.

EiliyaSeptember 30, 20267 min read

A successful ChatGPT login doesn't pay for a model request by itself. The Sign in with ChatGPT API needs separate permission to spend the user's plan and an eligible Responses API request. OpenAI documents these as separate capabilities, with no OpenAI API key needed for the open-source plan flow.

As of September 30, 2026, the public path covers open-source tools and personal projects that run locally. Paid or remotely hosted products need to request access. Sort that out before you build the sign-in button.

Choose your Sign in with ChatGPT API path

Identity-only sign-in gives you a verified account identity. You still create the app's own account and session, and the login grants no inference allowance.

Plan usage authorizes eligible model requests. You don't get the user's ChatGPT conversations or provider API key. The quickstart separates website identity, plugin identity, and open-source plan usage.

OpenAI's cookbook example builds Paste Perfect, a desktop app for rewriting text. Its devkit provides @siwc/local and React components. If you're looking for a login with ChatGPT SDK, start there instead of improvising token handling inside a browser component.

One OAuth client can cover several hosts

The OAuth client is the registration your user authorizes. In the dynamic open-source flow, the issued client_id belongs to that authenticated user and the workspace they selected during registration.

An agent host is where an instance runs, such as a laptop installation or self-hosted VM. One client can cover several hosts for the same user and workspace; they share plan settings and limits. The overview requires each host to keep its own persistent ext_agent_host_id, so a restart doesn't create a new host.

Pick one of the accepted formats:

Host identifier What to persist
urn:ietf:params:oauth:jwk-thumbprint:... A public-key thumbprint URI following RFC 9278. OpenAI recommends this format.
urn:uuid:... A UUIDv4 generated once for the host's lifecycle.
did:key:<key> A key identifier using this specific DID method.

Create the opaque identifier before the first sign-in, and don't derive it from an email address. A public-key-based host ID identifies the host, but the flow doesn't verify possession of the private key. Treat it as an identifier, not an authentication credential.

Register once, then reuse the issued client ID

Follow the registration and sign-in contract: start the loopback callback listener before you open the system browser. Generate fresh state, an OpenID Connect nonce, and a PKCE verifier for every attempt.

For first-time registration, use client_id=dynamic_agent_client, put your actual app name in agent_name_hint, and send the saved ext_agent_host_id. Returning sign-ins use the issued client ID. Leave out agent_name_hint on reauthorization.

The authorization endpoint is https://auth.openai.com/api/accounts/authorize. Request identity scopes openid profile email, plus offline_access resource.invoke chatgpt.tokens.use.direct for renewable plan access. Set resource to https://api.openai.com/v1, response_type to code, and the PKCE challenge method to S256.

Use an HTTP callback on 127.0.0.1, such as http://127.0.0.1:1455/auth/callback. Keep that address; localhost isn't a substitute. Later sign-ins can change the port, but the scheme, host, and path must stay consistent, and authorization and token exchange must use exactly the same URI within each attempt.

After the browser returns:

  1. Validate state and handle denial before redeeming anything.
  2. For a new registration, retain the issued client_id. The literal dynamic_agent_client is not the ID to save or exchange.
  3. POST a form-encoded authorization-code grant to https://auth.openai.com/api/accounts/oauth/token, including the issued ID, code, verifier, original callback URI, and resource.
  4. Verify the ID token's signature using OpenAI's JWKS, then check issuer, audience, expiration, and nonce.
  5. Confirm that the returned scopes include chatgpt.tokens.use.direct before enabling inference.

You don't need a client secret for this public-client flow. A valid ID token without the plan scope can keep identity sign-in working, but it won't fund a model request.

Protect tokens and keep registrations separate

Store each issued client ID alongside its validated identity and credentials. Two registrations aren't interchangeable just because their emails match. Keep a new sign-in separate from the active account until validation succeeds.

The account guide requires protected runtime storage. Tokens belong outside browser storage, source control, logs, and analytics. The sign-in guide calls for atomic writes to credential files with owner-only permissions: 0600 on Unix.

The token reference gives access tokens one hour and refresh tokens 30 days. A successful refresh returns a replacement with a fresh lifetime. Serialize session refreshes so concurrent processes don't race the rotating token, then save the replacement credentials together.

On sign-out, stop requests, attempt to revoke the refresh token through the discovery document's revocation endpoint, and clear local tokens. Keep the registration mapping and host ID for the next sign-in.

Fetch the account's models, then finish a Responses request

Fetch GET https://api.openai.com/v1/models using the selected account's bearer access token. In the model guide, the response has a models array. Show entries whose visibility is list, display their display_name, and send the selected slug as the model; refresh the picker when you switch accounts.

Send inference to POST https://api.openai.com/v1/responses with the same bearer credential. The documented example uses this body:

{
  "model": "gpt-6.1-sol",
  "input": [{"role": "user", "content": "Say exactly: Hello, world!"}],
  "store": false,
  "stream": true
}

Use that model only if it's in the account's catalog. Wait for response.completed before calling the request a success. Some text in the stream isn't enough: usage failures can arrive after streaming starts.

Your API-key request builder may need changes

The preview limitations require an input array, streaming, and disabled storage. For HTTP requests, leave out previous_response_id and send the history you need yourself. Use instructions or developer messages instead of explicit system-message items.

Fields including temperature, max_output_tokens, background, and conversation are unsupported. Check the full list before you reuse an API-key request builder.

The exclusions include image generation, file search, Code Interpreter, native computer use, hosted MCP connectors, and Responses tool_search. A model may accept image or file inputs, but that doesn't give you image generation or the Files upload API.

Codex app-server and VMs need their own token handling

For Codex app-server, pass the OAuth access token as ACCESS_TOKEN to the child process. Configure a Responses provider with the public API base URL, env_key="ACCESS_TOKEN", requires_openai_auth=false, and supports_websockets=false.

Initialize the stdio connection with your app's stable name, title, and version, then start a thread and turn. Check the status on turn/completed, because a completed event can report a failed turn.

You own token renewal: restart app-server with the refreshed credential and resume the saved thread. Its model catalog doesn't verify entitlement.

The VM guide says to finish OAuth locally; the loopback callback reaches the computer running the browser. Transfer the protected credential record over a secure channel, keep the VM's own host ID, and let the VM handle later refreshes. Host-specific attribution and plan-access revocation for transferred sessions aren't available yet.

A usage failure needs more than another login

Use the error guide to give each failure a useful next step. Pause requests and link to usage settings after a usage-limit error, which may mean the app hit its cap rather than the whole plan running out. Retry temporary availability failures within bounds; correct requests that ask for unsupported capabilities.

The UI guidelines call for the sign-in label Continue with ChatGPT, confirmation of plan use after the first authorized sign-in, a visible active billing path, and Manage usage near the composer. A failed request isn't permission to quietly change who pays.

A hosted app can use a wallet and earn on usage

Commercial access still needs the interest form. AI Pass offers hosted apps a user-funded wallet and multi-provider OAuth and REST integration, without a required subscription or application form. Users pay for exact usage without spending their ChatGPT allowance; AI Pass is independent of OpenAI.

ChatGPT plan usage cuts your inference cost and earns you nothing on that usage; with AI Pass, you can add a markup and earn on eligible paid usage under the current AI Pass terms. To start today, open the docs, pick your coding tool, paste the brief, and let your agent inspect and set up the project with your browser approval, without provider keys or manual OAuth client setup.

FAQ

Do I need an OpenAI API key?

No. The documented open-source plan flow uses an OAuth access token, with neither a provider API key nor a client secret.

Can I use a ChatGPT OAuth token with Chat Completions?

The published flow documents eligible Responses API requests. Don't assume it supports another endpoint.

Is the client ID the same as the host ID?

No. OpenAI issues the client registration ID. Your app creates and persists a distinct identifier for each host.

Can a paid web app use dynamic registration immediately?

Paid or remotely hosted apps are directed to the interest form. The public implementation docs don't grant commercial approval.

Sources