Skip to content

Errors

Errors follow the standard shape:

{ "statusCode": 400, "message": "invalid_grant: code already used", "error": "Bad Request" }

Validation failures return the same shape with message as an array of per-field problems.

Returned by /oauth/token, /oauth/revoke and /oauth/introspect.

HTTP message Cause
401 invalid_client Unknown or inactive client_id, or wrong client secret.
400 invalid_grant Code not found, or refresh token not found / not yours.
400 invalid_grant: code already used Authorization codes are single-use.
400 invalid_grant: expired Code older than 10 minutes, or refresh token older than 30 days.
400 invalid_grant: redirect_uri mismatch redirect_uri at exchange didn’t exactly match the authorize step.
400 invalid_grant: PKCE verification failed code_verifier doesn’t match the challenge sent at authorize.
400 invalid_grant: revoked Refresh token was revoked (by you, the business, or a family burn).
400 invalid_grant: refresh token reuse detected You replayed a used refresh token — the whole token family is now revoked. Re-authorize. See refresh rotation.
400 PKCE is required for public clients Public client attempted the flow without a code challenge/verifier.
400 unsupported_grant_type Only authorization_code and refresh_token are supported.
400 This app has not completed review and can only connect to its own sandbox businesses Unapproved app authorizing against a non-sandbox business. See Go live.
400 invalid_request: authorization request not found / …expired The consent step outlived its 5-minute window — restart authorization.
HTTP Meaning What to do
401 Unauthorized Access token expired, revoked, or malformed. Refresh once (single-flight). If the refresh fails with invalid_grant, the connection is dead — send the user through authorization again.
403 Insufficient scope. Required: … Your token lacks a scope the endpoint requires. Request the missing scope in a new authorization. The consent screen shows the user the added scopes. You cannot widen a live token.
403 endpoint not exposed to OAuth clients The route isn’t part of the OAuth partner surface. Check the API reference for the supported surface; don’t retry.
403 (no insufficient_scope prefix) The consenting user’s role in the business doesn’t permit this operation. Some endpoints (e.g. journal posting, chart-of-accounts changes, payment deletion) require the consenting user to hold an owner or admin role in the business, in addition to a valid scope. A correctly-scoped token from a member-role user still gets this 403. There’s no token fix — the business owner must perform the action, or grant the connecting user a higher role, then re-authorize. See Scopes.
400 Bad Request Validation failure — message lists the offending fields. Fix the payload; don’t retry unchanged.
404 Not Found Resource doesn’t exist in the consenting business. Remember IDs are per-business: a customerId from one business doesn’t exist in another.
429 Too Many Requests Rate limited. Honor Retry-After, back off with jitter. See Rate limits.
5xx Transient server error. Retry with exponential backoff and jitter, max ~3 attempts. Use idempotency keys on writes so retries can’t double-post — see integration patterns.

Most integrations only need this:

  1. 401 on an API call → refresh once → retry once. Refresh failed? → re-authorize.
  2. 403 → configuration problem (scopes or surface), not a retry problem. Fix the app, not the request loop.
  3. 429 / 5xx → back off and retry with the same Idempotency-Key.
  4. 400 → your bug. Log it, alert, don’t retry.

Never log raw tokens while handling errors — log a truncated suffix or hash if you need correlation.