Tokens and actors
The credentials you'll touch when integrating Quota. Each one represents a different actor and travels on different requests. Mixing them up is the most common cause of the “why is the wrong wallet being charged” class of bug.
01 At a glance
| Token | Looks like | Represents | Used at | Bills |
|---|---|---|---|---|
| Developer session | sess_… | You, logged in to the dashboard | /developers/*, /account/* | n/a (management) |
| Developer API key | sk-quota-… | Your app server | /v1/* | Your developer wallet |
| OAuth client secret | quota_secret_… | Your app, exchanging an OAuth code | /oauth/token | n/a (auth) |
| User access token | quota_token_… | An end-user who consented | /v1/*, /v1/balance | That user's wallet |
| User refresh token | quota_refresh_… | Your server, renewing a user token | /oauth/token | n/a (auth) |
02 Developer session
A sess_… token is what POST /auth/login returns when you sign in as a developer. It authenticates you, the human running the dashboard or a deployment script — never an end-user. Use it to create OAuth apps, mint API keys, configure scopes, manage webhooks, and read account-level data.
Never ship a developer session into a client app or commit one to a public repo. They are full-power and they only revoke when you sign out or rotate.
# Get a developer session
curl -X POST https://api.usequota.ai/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"…"}'
# → { "session_token": "sess_5d8c7e1f…" }
# Use it on developer endpoints
curl https://api.usequota.ai/developers/apps \
-H "Authorization: Bearer sess_5d8c7e1f…"The session also works as a cookie (quota_session=sess_…) on the dashboard. The bearer form is what most agentic setup scripts will use.
03 Developer API key
An sk-quota-… key is what you mint from the dashboard or POST /developers/keys. It authenticates your app server for model calls and spends your developer wallet — that's the developer billing mode. For end-user-pays flows, swap the API key for an end-user OAuth access token instead (see Connect Quota Wallet or Sign in with Quota).
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.QUOTA_API_KEY, // sk-quota-…
baseURL: "https://api.usequota.ai/v1",
});
// Bills your developer wallet
const r = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Hi" }],
});04 OAuth client secret
A quota_secret_… is the server-side half of your OAuth app credentials. It only ever appears on POST /oauth/token when your server is trading an authorization code for an access token, or refreshing one. Treat it like a password — server-only, never bundled with client code.
curl -X POST https://api.usequota.ai/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE_FROM_CALLBACK" \
-d "redirect_uri=https://yourapp.com/auth/quota/callback" \
-d "client_id=quota_client_…" \
-d "client_secret=quota_secret_…"05 User OAuth tokens
After a user completes /oauth/authorize and your server exchanges the code, you get back two tokens:
access_token(quota_token_…): use as the bearer on the user's requests to/v1/*. Quota debits that user's wallet. Short-lived (typically one hour).refresh_token(quota_refresh_…): use onPOST /oauth/tokenwithgrant_type=refresh_tokento get a fresh access token. Long-lived. Store server-side, never in a browser cookie in production.
import OpenAI from "openai";
// Same SDK, same baseURL — only the apiKey changes.
const client = new OpenAI({
apiKey: userAccessToken, // quota_token_…
baseURL: "https://api.usequota.ai/v1",
});
// Bills the user's wallet, not yours
const r = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Hi" }],
});See Connect Quota Wallet for the full OAuth round-trip in plain Express and Next.js.
06 Common mismatches
If your bill went somewhere unexpected, it's usually one of:
- Wrong wallet charged. You called
/v1/chat/completionswithsk-quota-…while expecting per-user billing. API-key auth bills the developer. Swap the bearer for a user OAuth access token (quota_token_…) to bill that user's wallet instead. /v1/balancereturned 0 / a stranger's amount. The endpoint returns whoever the bearer is. Withsk-quota-…it's your developer balance; withquota_token_…it's that user's balance. There's no “current user” — it's always the bearer.401 invalid_tokenon a fresh OAuth code. Codes are single-use and expire fast. Make sure the sameredirect_uriis used at/oauth/authorizeand/oauth/token, and that you exchange the code before the user clicks again.403 insufficient_scopeon/v1/balance. The user token doesn't carrycredits.read. If you want to display the balance, requestcredits.read credits.spendat authorization time, not justcredits.spend.- Committed a token by accident. Rotate immediately: API keys via the dashboard (revoke and re-mint), OAuth client secrets via secret rotation (grace-period flow), developer sessions via
POST /auth/logout.