BYOK (provider OAuth)
Bring-your-own-key via OAuth. Link a Quota user’s ChatGPT subscription so model requests run against their OpenAI plan instead of charging Quota credits. The user authorizes once; Quota stores the encrypted tokens and refreshes them as needed.
Flow#
- The user clicks Connect ChatGPT in your dashboard. You send the browser to
GET /account/providers/openai/connect. - Quota generates a one-time
state(10-minute TTL), stores it server-side bound to the user, and 302-redirects the browser to OpenAI’s authorization URL with the requested scopes. - The user authorizes on OpenAI; OpenAI redirects back to
GET /account/providers/openai/callback?code=...&state=.... - Quota validates
state, exchanges the code for tokens, encrypts them withPROVIDER_TOKEN_ENCRYPTION_KEY, upserts the row inuser_provider_links, then redirects the browser to/account/dashboard?provider_linked=openai. - Quota records a
social_link_createdauth event for audit.
Start the OAuth flow#
Issues a 302 redirect to OpenAI’s authorization URL. Requires the same session token as the rest of the Account surface.
Scopes requested: openid profile email model.request.read model.request.write. The two model.request.* scopes let Quota call OpenAI on the user’s behalf using their subscription.
Request
<a href="https://api.usequota.ai/account/providers/openai/connect">
Connect ChatGPT
</a>Don’t fetch this endpoint with fetch(); it’s a redirect, not JSON. Either link to it directly or open it in a popup window and listen for the dashboard redirect.
Errors
| 401 unauthorized | Missing or expired session token. |
| 503 not_configured | OPENAI_OAUTH_CLIENT_ID is not set on the server. |
OAuth callback#
OpenAI redirects to this URL after the user authorizes. You will rarely call it directly — it’s the registered redirect URI on the OpenAI OAuth app.
Query
| codestring | Authorization code from OpenAI. Single-use. |
| statestring | The opaque value Quota issued at /connect. Validated server-side and consumed on use. |
| errorstring | Set by OpenAI if the user denied or the upstream rejected. Quota redirects to /account/dashboard?provider_error=<error>. |
Outcomes
The handler always responds with a 302 to the dashboard:
| ?provider_linked=openai | Tokens stored, link is active. The dashboard surfaces a success toast. |
| ?provider_error=<code> | Something went wrong. Common values: access_denied (user declined), token_exchange_failed (code was invalid or expired), server_config (encryption key missing). |
Errors
Two cases return JSON instead of a redirect, because they indicate a misuse of the endpoint that no UI flow should ever produce:
| 400 invalid_request | Missing code or state in the query string. |
| 400 invalid_state | State unknown, already consumed, or expired (>10 min since /connect). |
Disconnect#
Removes the user’s OpenAI provider link. Subsequent model requests fall back to Quota credits (the standard billing path). Records a social_link_removed auth event.
Request
curl -X POST https://api.usequota.ai/account/providers/openai/disconnect \
-H "Authorization: Bearer sess_..."Response
{
"success": true,
"provider": "openai"
}Errors
| 404 not_found | No active OpenAI link exists for this user. |
Reading link state#
There is no dedicated GET /account/providers/openai endpoint. The current link state is part of GET /account:
{
"linked_providers": [
{
"provider": "openai",
"subscription_tier": "plus",
"linked_at": "2026-04-02T11:08:33.510Z",
"status": "active"
}
]
}status is one of active, expired, or revoked. subscription_tier is best-effort and may be null if the userinfo call failed at link time — the token still works in that case.