Errors

The error envelope and what each code means.

Every /api/v1 error uses the same envelope:

{
  "error": {
    "code": "insufficient_scope",
    "message": "Missing required scope: candidates:read",
    "requiredScope": "candidates:read"
  }
}

requiredScope appears on scope failures so you can fix the key without guessing which permission was missing.

The correlation id

Every response carries an x-correlation-id header — success, error, and rate-limited alike:

x-correlation-id: 3f7c1e02-95a4-4a6d-9c5a-1b0e2f8d4a71

It is a UUID we generate at the very start of handling your request, before we have even looked at your credential, and it identifies that one call. The same value is written to three places:

Where it appearsWhat you can do with it
The x-correlation-id response headerThe copy your client receives. Log it alongside your own request id.
Your organization's audit log entryOpen Settings → Audit log, expand the request, and read the same id.
Our error tracking, on a 500Lets us find the exception for your exact call rather than a similar one.

So it is the one string that answers "what happened to this request". Quoting it in a support request is the difference between us searching for a class of failure and us opening the row.

Log it on failures at minimum. A 500 tells you nothing on its own; a 500 plus a correlation id is a single row on our side.

Two things worth knowing:

  • We generate it; you cannot supply it. Sending your own x-correlation-id on the request has no effect — the value you get back is always ours. Correlate by storing the returned header against your own trace id.
  • It is not secret. It identifies a request, not a caller, and grants nothing. Paste it into a ticket freely.

Codes

CodeStatusMeaning
unauthorized401No credential, or one we could not verify.
ambiguous_authentication401More than one credential on the request. Send one.
credential_revoked401The key was revoked, or the grant behind it was.
credential_expired401The key passed its expiry.
insufficient_scope403Valid credential, missing the scope named in requiredScope.
insufficient_role403You hold the scope, but your role in the organization does not reach it.
organization_not_permitted403The credential is not bound to the organization you asked for.
not_found404No such record — or one you cannot see. See below.
invalid_request400Malformed input; the message says what.
revocation_unavailable503We could not confirm your credential is still live. Retry.
internal_error500Our fault. The correlation id will find it.

Why 404 and not 403

A record belonging to another organization returns 404, not 403.

This is deliberate. A 403 would confirm the id exists somewhere, which turns any detail endpoint into a way to enumerate ids across tenants. "Not found" and "not yours" are the same answer on purpose, so do not read a 404 as proof that a record does not exist.

403 vs 404 on your own data

Getting a 403 where you expected data usually means one of two things:

  • insufficient_scope — the key was created without that permission. Edit its scopes in Settings → API access; you do not need a new key.
  • insufficient_role — the key holds the scope, but its owner's role does not. billing:read and audit:read need an organization admin. Adding the scope again will not help; the role is the limit.

503 is not "your credential is bad"

revocation_unavailable means the store we check revocations against was unreachable, so we refused rather than guess. Your credential is probably fine. Retry shortly — and do not rotate a working key in response to it.

Last updated on

On this page