La documentación se publica en inglés.

DocumentationSign in

Errors and refusals

Where each kind of refusal arrives, the one envelope every API error uses, and the step-up that is not a denial.

Refusals arrive in three places

A sign-in can be refused before the person sees anything, at your redirect URI, or at the token endpoint. Each has its own shape, and your code meets all three.

Before the redirect URI is trustedJSON, no redirect
A missing or unknown client_id, or a redirect_uri that is not registered for it, is answered as an API error — invalid_request or unauthorized_client, status 400 — and never redirected, because the only place to send the person is the one that failed validation.
At your redirect URIRFC 6749 query parameters
Once the client and its redirect URI are validated, every later refusal comes back to you as ?error=…&error_description=…&state=…. The codes a relying party may be told are invalid_request, unsupported_response_type, invalid_scope, unauthorized_client, access_denied and server_error.
At the token endpointbare OAuth error
POST /v1/oauth/token answers { "error", "error_description" } with no envelope: invalid_grant, invalid_client (status 401, with WWW-Authenticate: Basic), unsupported_grant_type, invalid_request. A person no longer in good standing is a 403; a budget or credit refusal is a 402.

The API error envelope

Every API error outside the OAuth routes — developer routes, userinfo, the public routes — is one object with an error member. There is no top-level status field; the HTTP status carries that.

Recorded from /v1/userinfo without a token
{
  "error": {
    "code": "invalid_token",
    "message": "A valid OAuth access token is required.",
    "requestId": "7015cb0d-ac1a-427c-9b4f-0dacb1a2235e"
  }
}
code
A stable identifier. Branch on it. There are a few hundred across the platform; the reference lists the ones on the developer surface.
message
Written for the developer reading a log. It is rewritten without notice; never match on it and never show it to the person as-is.
requestId
What support needs. Log it against your own correlation id on every non-2xx; never log the request body beside it.
details
Optional. A plain object with more for your code — retryAfterSeconds on a 429, redirectUrl on a redirectable authorization error.

Userinfo refusals use OAuth's own codes inside that envelope: invalid_token (401) for a missing, expired or revoked token, and insufficient_scope (403) for a resource the token's scopes do not cover.

What the status tells you

StatusMeansYour move
400The request is malformed or a value is invalidFix the call. Never retry unchanged
401Not authenticated, or the token is expired or invalidRe-authenticate, then retry once
402A budget or credit refusalStop. Credits are added in the developer console by the application's owner, or by a team member with the billing role on one of the owner's active production applications (to the owner's balance; the receipt is in the payer's name). Only the owner raises the application's budget
403Authenticated, but not permitted — including step-upDo not retry. Check the code for a step-up
404No such resource, or you cannot see itTreat as absent. Do not distinguish the two to end users
409Conflicts with current stateRe-read state and decide. Retrying unchanged will fail again
429Rate limitedWait Retry-After seconds
5xxOur fault, or an upstream providerRetry with backoff on idempotent calls only

Step-up is a recovery path

Some console actions require the developer to confirm with a passkey before they proceed — issuing or revoking a client secret, registering a webhook endpoint, changing a live application. They answer 403 with a code ending in STEP_UP_REQUIRED, and they are not permission denials: the caller is allowed, they have just not confirmed recently enough.

Handling it
const STEP_UP = /STEP_UP_REQUIRED$/;

if (response.status === 403 && STEP_UP.test(body.error.code)) {
  // Send them to confirm, and come back to this exact page afterwards.
  return redirectToPasskeyConfirmation({ returnTo: currentPath });
}

What to show the person

Map codes to your own copy, in your own voice, telling them what to do next. A denial at your callback is a path your product supports — tell them what happens now and what they can still do — not an exception screen.

Do not leak whether a resource exists. A 404 on someone else's application and a 404 on a deleted one look identical on purpose, and your interface should keep them that way.

What this does not do

  • There is no error that tells you why a person was refused. By design. If your product needs to know, ask the person.
  • Messages are not a contract. The API renames them to be more useful — the invalid_scope message now names the scope — and will again.

Verified against API commit 234dba22 on 2026-10-18. The routes, scopes, checklist, events and limits on these pages are read from that export, not typed.