How to use your ChatGPT subscription in other apps
By Flavio Copes
Sign in with ChatGPT lets Plus and Pro users spend their plan in Devin, Warp, Notion, Pi and other apps. How to connect one, cap it, and build your own.
If an app supports Sign in with ChatGPT, you can now pay for its AI with the ChatGPT subscription you already have. You click Continue with ChatGPT, allow Use your ChatGPT plan, and the AI requests you make in that app count against your Plus or Pro limits. You don’t need an API key, and you don’t buy the app’s credits for that part.
OpenAI announced it at DevDay on September 29, 2026, with 16 launch partners. Devin, Warp, Amp, Notion and Vercel are on the list, and so are the open source coding agents Pi, OpenCode and OpenClaw. Tibo, who works on Codex and ChatGPT at OpenAI, put it like this on X: “No little rules, you can just use all your included usage right there.”
Let’s see how to connect an app and cap how much of your plan it can use. Then, if you build tools, we’ll write a small one that runs on a ChatGPT plan.
What changed with Sign in with ChatGPT?
Sign in with ChatGPT isn’t new. OpenAI released it as a beta in July 2026, with Airtable, GitLab, HubSpot, Notion, Supabase and Vercel among the first apps. Back then it was a login button, like Sign in with Google. The app got your name, email address and profile picture, and nothing else.
At DevDay OpenAI added a second permission on top of the login. Use your ChatGPT plan lets an app send AI requests that come out of the usage included in your plan. OpenAI’s docs call it your “Codex / ChatGPT work usage”, so it’s the same usage Codex draws from.
The two permissions are separate. You can sign in to an app and say no to the plan part.
Before this, using an OpenAI model inside someone else’s tool meant pasting an API key and paying per token, or buying the tool’s own credits. I compare the two ways of paying for a model in the Subscription or API key lesson of my free AI Fundamentals Course. Now the subscription you already pay for covers it.
Sam Altman pitched it to developers from the stage, as The New Stack reported: “They’re already paying for an AI subscription, and now you don’t have to cover their token costs to get them going.”
A few tools did a version of this before. OpenClaw and Pi already let you log in with your ChatGPT account through the same sign-in Codex uses. The new program makes it official and puts the controls in ChatGPT’s settings.
Who can use it?
Plan usage is for ChatGPT Plus and Pro subscribers.
The limits are the ones you know from Codex, with one detail that depends on the plan:
- On Plus, the five-hour limit is shared by every app that uses your plan. If Devin uses it up, Pi waits too.
- On Pro, the five-hour limit doesn’t apply to these apps. The weekly limit still does.
Connecting an app doesn’t add anything to your plan. The app gets no separate allowance, and it can still charge for its own features. We’ll see an example of that below.
Which apps support it?
OpenAI keeps the list on its Sign in with ChatGPT page, split into three groups. This is the list as of September 30, 2026.
These 12 apps can use your plan:
- Amp Code
- Conductor
- Dactyl
- Devin
- Hermes Agent
- Hyperagent
- Kilo Code
- Lovable (coming soon)
- Notion
- Vercel
- Vorflux
- Warp
These four open source tools can use it too:
That’s the 16 from the announcement. Lovable is still marked as coming soon, so 15 of them work today.
Airtable, Canva, GitLab, HubSpot and Supabase support the login only. You can sign in to them with ChatGPT, but they don’t use your plan.
OpenAI told The New Stack: “We’re starting with these 16 launch partners today, and we’ll expand quickly.” If the app you use isn’t there yet, check the page again in a few weeks.
How do you connect an app?
The steps are the same in every web app on the list:
- Choose Continue with ChatGPT on the app’s sign-in page.
- Sign in to the ChatGPT account you want to use.
- Check what the app asks for, usually your name, email address and profile picture.
- Review the Use your ChatGPT plan permission and allow it.
- Click Continue.
If you already have an account in that app with the same email, the app asks you to confirm and links the two. You can also connect ChatGPT to an existing account later. Sign in to the app as usual and look for the ChatGPT option in its account or billing settings.
OpenAI’s design guidelines ask apps to show a Using ChatGPT plan label near the model picker, with a Manage usage link next to it. That’s how you can tell which requests come out of your plan.
Using your plan doesn’t give the app access to your ChatGPT conversations or your memories. If an app asks for other permissions, you approve those separately.
Coding agents in the terminal
The open source agents do the same from the terminal. They start a small local server, open the ChatGPT consent page in your browser, and save the tokens on your computer.
In Pi, the option arrived in a recent release, so update first:
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
Then start pi and type:
/login openai
Pi lists the ways to log in to OpenAI. Pick Sign in with ChatGPT, and the provider shows up as OpenAI (ChatGPT subscription). If you logged in with ChatGPT in an older version of Pi, that login is still there as OpenAI Codex (legacy). It’s the old route through the Codex sign-in.
In OpenClaw, the new method is called siwc:
openclaw models auth login --provider openai --method siwc
OpenClaw’s docs describe it as using your Codex allowance, with its own usage tracking and token limits for each OpenClaw instance. The older Codex login and the API key option are still there next to it.
In OpenCode, run /connect, pick OpenAI, then ChatGPT Pro/Plus (browser). On a server with no browser, pick ChatGPT Pro/Plus (headless) instead.
How do you limit what each app uses?
This is what I’d set up right after connecting anything.
Open ChatGPT Settings > Usage. Under App limits you’ll see the apps connected to your plan. To cap one:
- Open the percentage menu next to the app.
- Pick its weekly limit, as a percentage of your overall weekly plan usage.
- Click Save.
Say you give Devin 25%. Devin can use up to a quarter of your weekly usage, then it stops. That 25% isn’t set aside for Devin, though. The limit is a cap, so Codex and your other apps can still use the whole plan.
Apps approved by OpenAI can also spend your ChatGPT credits balance, if you have one. The same settings page controls each app’s access to your plan and to your credits.
When an app reaches its limit, or you reach your plan’s limit, its requests stop. OpenAI says the app won’t switch you to its own billing automatically. Many apps will offer to sell you their own credits at that point, and that’s a separate purchase you make in the app.
To cut an app off completely, go to Settings > Security and login > Sign in with ChatGPT, pick the app and click Disconnect. OpenAI doesn’t notify the app. Its next request or token refresh fails, and that’s how it finds out.
What doesn’t your plan pay for?
Your plan pays for the model calls. It doesn’t pay for everything else an app sells.
Dactyl, the platform Ryan Dahl launched for vibe coding native iOS and Android apps, shows the split well. According to The New Stack, the agent that reads your request and writes the Swift code runs on your ChatGPT plan. Compiling the app, generating icons and images, creating 3D assets and publishing still use Dactyl credits, and its Builder plan costs $20 a month.
So check each app’s pricing page to see which parts still use its own credits.
Build a terminal tool that runs on a ChatGPT plan
Now the developer side. If your tool is open source and runs on the user’s own computer, you don’t need to ask OpenAI for anything. There’s no client secret and no partner API key. Your tool registers itself the first time someone signs in.
Websites and hosted apps are different. Commercial sign-in is a limited trial with selected partners, and if you want plan usage in a paid or remotely hosted app, OpenAI asks you to fill in its interest form.
We’re going to build a small Node.js tool with two files. login.mjs signs you in once and saves the tokens. ask.mjs sends a question to a model on your plan and prints the answer as it streams in.
Create a folder and install two packages. We use jose to check the ID token and openai for the API calls:
mkdir ask-chatgpt
cd ask-chatgpt
npm init -y
npm install jose openai
You need a recent version of Node.
How the sign-in works
It’s the standard OAuth authorization code flow with PKCE, which I explain step by step in A deep dive into OAuth 2.0. The same flow is also a lesson in my free Web Authentication Course.
OpenAI adds a few rules on top of it.
The first time, you send client_id=dynamic_agent_client, a placeholder that tells OpenAI to register a new client. The user gives the agent a name on the consent page, and the redirect brings back your real client ID. You save it and use it from then on.
You also send ext_agent_host_id, a random ID for the machine your tool runs on. You generate it once, save it, and send the same one every time.
The redirect goes to a small server your tool starts on 127.0.0.1. We use http://127.0.0.1:1455/auth/callback. Only the port can change, and localhost isn’t accepted.
Finally, you ask for the chatgpt.tokens.use.direct scope. That’s the Use your ChatGPT plan permission. The user can turn it off on the consent page, so check it’s in the token response before you use the token.
The login script
Here’s login.mjs. It creates the host ID, builds the authorization URL and waits for the redirect. Then it exchanges the code for tokens, checks the ID token and the scope, and saves everything in ~/.config/ask-chatgpt/credentials.json:
import { createServer } from 'node:http'
import { createHash, randomBytes, randomUUID } from 'node:crypto'
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { homedir } from 'node:os'
import { join } from 'node:path'
import { createRemoteJWKSet, jwtVerify } from 'jose'
const AUTH = 'https://auth.openai.com'
const RESOURCE = 'https://api.openai.com/v1'
const REDIRECT_URI = 'http://127.0.0.1:1455/auth/callback'
const DIR = join(homedir(), '.config', 'ask-chatgpt')
const CREDENTIALS = join(DIR, 'credentials.json')
const HOST_ID = join(DIR, 'host-id')
mkdirSync(DIR, { recursive: true, mode: 0o700 })
if (!existsSync(HOST_ID)) {
writeFileSync(HOST_ID, `urn:uuid:${randomUUID()}`, { mode: 0o600 })
}
const hostId = readFileSync(HOST_ID, 'utf8')
const saved = existsSync(CREDENTIALS)
? JSON.parse(readFileSync(CREDENTIALS, 'utf8'))
: null
const random = () => randomBytes(32).toString('base64url')
const state = random()
const nonce = random()
const verifier = random()
const challenge = createHash('sha256').update(verifier).digest('base64url')
const url = new URL(`${AUTH}/api/accounts/authorize`)
url.search = new URLSearchParams({
client_id: saved?.client_id ?? 'dynamic_agent_client',
...(saved ? {} : { agent_name_hint: 'Ask ChatGPT' }),
ext_agent_host_id: hostId,
response_type: 'code',
redirect_uri: REDIRECT_URI,
scope:
'openid profile email offline_access resource.invoke chatgpt.tokens.use.direct',
resource: RESOURCE,
state,
nonce,
code_challenge: challenge,
code_challenge_method: 'S256',
})
const callback = await new Promise((resolve, reject) => {
const server = createServer((req, res) => {
const params = new URL(req.url, REDIRECT_URI).searchParams
if (!req.url.startsWith('/auth/callback')) {
res.writeHead(404).end()
return
}
res.end('Done. You can close this tab and go back to the terminal.')
server.close()
if (params.get('state') !== state) reject(new Error('State mismatch'))
else if (params.has('error')) reject(new Error(params.get('error')))
else resolve(params)
})
server.listen(1455, '127.0.0.1', () => {
console.log(`Open this URL in your browser:\n\n${url}\n`)
})
})
const clientId = saved?.client_id ?? callback.get('client_id')
const response = await fetch(`${AUTH}/api/accounts/oauth/token`, {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: clientId,
code: callback.get('code'),
code_verifier: verifier,
redirect_uri: REDIRECT_URI,
resource: RESOURCE,
}),
})
if (!response.ok) {
throw new Error(`Token exchange failed: ${await response.text()}`)
}
const tokens = await response.json()
const jwks = createRemoteJWKSet(new URL(`${AUTH}/.well-known/jwks.json`))
const { payload } = await jwtVerify(tokens.id_token, jwks, {
issuer: AUTH,
audience: clientId,
})
if (payload.nonce !== nonce) throw new Error('Nonce mismatch')
if (!tokens.scope.split(' ').includes('chatgpt.tokens.use.direct')) {
throw new Error('You signed in, but you did not allow plan usage')
}
writeFileSync(
CREDENTIALS,
JSON.stringify(
{
email: payload.email,
client_id: clientId,
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
expires_at: Date.now() + tokens.expires_in * 1000,
},
null,
2,
),
{ mode: 0o600 },
)
console.log(`Signed in as ${payload.email}`)
A few details are worth a closer look.
state protects us from a forged redirect. If the value that comes back doesn’t match the one we sent, we stop. nonce does the same job for the ID token.
verifier and challenge are the PKCE pair. The hash goes in the URL, and the original value only goes in the token request. Someone who steals the code from the redirect can’t use it without the verifier.
jwtVerify from jose checks the ID token’s signature against OpenAI’s public keys. It also checks that the issuer is https://auth.openai.com and that the token was issued for our client ID.
The second time you run the script, it sends the saved client_id and leaves out agent_name_hint. Without that, every login would register a new app with its own usage settings.
The credentials file is readable only by you (0o600). These tokens spend your plan, so treat the file like a password and keep it out of Git.
Run it:
node login.mjs
It prints a long URL that starts with https://auth.openai.com/api/accounts/authorize?client_id=dynamic_agent_client. Open it in your browser, sign in, give the agent a name, allow Use your ChatGPT plan and continue. The browser lands on our little server, which answers “Done. You can close this tab and go back to the terminal.”, and the terminal prints Signed in as followed by your email.
If you turn off the plan permission, the script stops with You signed in, but you did not allow plan usage and saves nothing.
The ask script
The access token lasts an hour. The refresh token lasts 30 days, and you get a new one every time you use it. So ask.mjs first refreshes the tokens when they’re about to expire.
After that, it has two modes. With no arguments it lists the models your account can use. With a question it streams the answer from the Responses API:
import { readFileSync, writeFileSync } from 'node:fs'
import { homedir } from 'node:os'
import { join } from 'node:path'
import OpenAI from 'openai'
const MODEL = 'gpt-6.1-sol'
const FILE = join(homedir(), '.config', 'ask-chatgpt', 'credentials.json')
const credentials = JSON.parse(readFileSync(FILE, 'utf8'))
if (Date.now() > credentials.expires_at - 60_000) {
const response = await fetch('https://auth.openai.com/api/accounts/oauth/token', {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
client_id: credentials.client_id,
refresh_token: credentials.refresh_token,
resource: 'https://api.openai.com/v1',
}),
})
if (!response.ok) {
throw new Error(`Refresh failed, run login.mjs again: ${await response.text()}`)
}
const tokens = await response.json()
credentials.access_token = tokens.access_token
credentials.refresh_token = tokens.refresh_token
credentials.expires_at = Date.now() + tokens.expires_in * 1000
writeFileSync(FILE, JSON.stringify(credentials, null, 2), { mode: 0o600 })
}
const question = process.argv.slice(2).join(' ')
if (!question) {
const response = await fetch('https://api.openai.com/v1/models', {
headers: { authorization: `Bearer ${credentials.access_token}` },
})
if (!response.ok) throw new Error(await response.text())
const { models } = await response.json()
for (const model of models.filter((m) => m.visibility === 'list')) {
console.log(`${model.slug} ${model.display_name}`)
}
process.exit()
}
const client = new OpenAI({
apiKey: credentials.access_token,
baseURL: 'https://api.openai.com/v1',
maxRetries: 0,
})
const stream = await client.responses.create({
model: MODEL,
input: [{ role: 'user', content: question }],
store: false,
stream: true,
})
let completed = false
for await (const event of stream) {
if (event.type === 'response.output_text.delta') {
process.stdout.write(event.delta)
} else if (event.type === 'response.failed') {
throw new Error(`Response failed: ${event.response.error?.code}`)
} else if (event.type === 'response.completed') {
completed = true
}
}
if (!completed) throw new Error('The stream ended before the answer was complete')
console.log()
The access token goes in apiKey. The SDK sends it as a Bearer token, the same header an API key uses, so the rest is the usual Responses API. We set baseURL explicitly, so an OPENAI_BASE_URL in your environment can’t send the token somewhere else.
store: false and stream: true are required on this route. maxRetries: 0 stops the SDK from retrying by itself, because retrying won’t fix a usage limit.
Notice that we only trust the answer after response.completed. A usage limit can kick in halfway through a stream, and then you get response.failed instead.
List the models first:
node ask.mjs
The file uses gpt-6.1-sol, the new GPT model OpenAI uses in its own examples. If your list doesn’t have it, change MODEL to one of the slugs you see. Then ask something:
node ask.mjs "Explain PKCE in one sentence"
The answer streams into the terminal, and it counts against your plan. Your tool shows up in Settings > Usage too, so you can cap it like any other app.
What the plan route can’t do
This is a preview, and the requests have rules the normal API doesn’t have. These are the ones you’ll hit first (checked September 30, 2026):
- Leave out
temperature,top_p,max_output_tokens,metadata,truncationanduser, because this route doesn’t support them. - There’s no
previous_response_idover HTTP. Send the conversation you need ininputevery time. - Messages with the
systemrole are rejected. Useinstructionsinstead. - Hosted tools don’t work: no image generation, file search, Code Interpreter, computer use or hosted MCP. Your own function tools do work.
- Text, images and files work as input when the model supports them. Audio, video, the Files upload API and transcription don’t.
Two errors deserve their own handling. subscription_sharing_usage_limit_exceeded (HTTP 429) means the user or your app reached a limit. Tell them, link to Settings > Usage, and don’t retry in a loop. subscription_sharing_user_not_eligible (HTTP 403) means the account, its workspace or a policy doesn’t allow plan usage. Sending the user through the login again won’t change that.
If your tool runs on a server, the 127.0.0.1 redirect can’t reach it, because it goes to the computer running the browser. OpenAI’s docs say to sign in on your own computer and copy the credentials file to the server over SSH.
Our two files skip a few things a real tool needs. A logout command should revoke the refresh token at https://auth.openai.com/api/accounts/oauth/revoke. A re-login should check that the ID token belongs to the same account before it replaces the saved tokens. And the UI should follow OpenAI’s guidelines, with a Continue with ChatGPT button and a Using ChatGPT plan label. The developer docs cover all three.
How I would use it
Pi is my agent for small jobs, and it has run on my ChatGPT plan since I started using it at the end of April, through the Codex login. So what’s new for me is the control.
I’d move Pi to the new login and give it a weekly cap in Settings > Usage. A Pi session shouldn’t be able to use up the Codex usage I need for my other work.
The one place I’d really like to use it is my app idea generator. It runs a small Workers AI model today, and I pay for every call, so it has daily caps. If visitors could bring their own ChatGPT plan, the cost would go away and I could use a much better model. But it’s a website, and plan usage on websites goes through OpenAI’s interest form, so it stays on Workers AI for now.
The limit I’d keep in mind is the shared pool on Plus. One busy app can use up the five-hour window, and then Codex and every other connected app wait with it. The per-app caps help, but they’re weekly, so they can’t stop that.
Want me to talk about your product? You can sponsor this site.