Authentication
One credential type: a bearer API key. No cookies, no signatures, no client secrets.
Authorization: Bearer ad_live_xxxxxxxxxxxxxxxxxxxxxxxx
An API key, sent as Authorization: Bearer <key>.
Keys look like ad_live_… (production traffic, real spend) or ad_test_…. Create them in the dashboard under API keys. The full value is shown exactly once, at creation — only a SHA-256 hash is stored, so a lost key cannot be recovered, only rotated. Revoking a key takes effect immediately on the next request.
ad_live_…LiveSpends real credit against the account balance and counts toward its quotas. Use in production only.
ad_test_…TestFor integration work. Same request and response shapes, same error contract — so an integration proven on a test key behaves identically on a live one.
The prefix is part of the key, not a header or a query parameter — a key is self-describing, so a secret scanner and a code reviewer can both tell at a glance which one leaked.
A key’s full value is displayed exactly once, at creation. Only its SHA-256 hash is stored, so the platform genuinely cannot show it again — not to you, and not to an operator with database access. A lost key is rotated, never recovered.
401 unauthorized; there is no cache to wait out and no grace period. Revoke first, investigate second — create the replacement key before you revoke the old one if you need continuity.Create, list and revoke keys at API keys.
A key is granted a set of scopes at creation — the capabilities it may exercise. There are exactly two, and each is enforced at authentication, so a key cannot exceed them:
chat:writePOST /v1/chat/completionsRun a model. Spends credit against the account balance.
models:readGET /v1/modelsList the published catalog. Read-only.
Choose scopes when you create the key. Leaving them unselected grants full access — every scope — so an existing integration is unaffected. A call a key is not scoped for is refused with 403 insufficient_scope (permission_error in the OpenAI error shape); the response details.required_scope names the one it was missing. Narrow a key to what it does — a detection pipeline that only reads the catalog never needs to be able to spend.
A key can be given an optional lifetime — 30, 90, or 365 days, or never — chosen at creation. A key with a lifetime stops working the moment it passes it; a key set to never expire lives until it is revoked.
401 key_expired, not a generic unauthorized, so your client can tell “this key aged out, rotate it” apart from a typo or a revocation. The response carries details.expired_at. There is no grace period — create the replacement before the old one lapses if you need continuity.The dashboard shows each key’s remaining life as an expires in N days countdown, and an aged-out key as expired — so a key nearing its end is visible before it fails a request.
Authentication answering “this key is real” is not the same as authorisation answering “this account may generate”. These are the credential-shaped failures, and only the first one is about the key itself:
unauthorizedMissing, malformed, or revoked API key.
email_unverifiedThe owning account has not confirmed its email address. Open the sign-in link that was mailed to it; the same key then works unchanged.
account_suspendedThe account is suspended and cannot start new generations.
account_restrictedThe account is paused pending a trust & safety review.
- Not in browser JavaScript. Anything the browser can send, a visitor can read.
- Not in a mobile binary. Shipped is published.
- Not in a repository, a CI log, or an error report — use an environment variable.
- Not in a URL query string. This API takes the credential in the
Authorizationheader only, so it never lands in an access log or a referrer.