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-1b0e2f8d4a71It 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 appears | What you can do with it |
|---|---|
The x-correlation-id response header | The copy your client receives. Log it alongside your own request id. |
| Your organization's audit log entry | Open Settings → Audit log, expand the request, and read the same id. |
Our error tracking, on a 500 | Lets 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-idon 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
| Code | Status | Meaning |
|---|---|---|
unauthorized | 401 | No credential, or one we could not verify. |
ambiguous_authentication | 401 | More than one credential on the request. Send one. |
credential_revoked | 401 | The key was revoked, or the grant behind it was. |
credential_expired | 401 | The key passed its expiry. |
insufficient_scope | 403 | Valid credential, missing the scope named in requiredScope. |
insufficient_role | 403 | You hold the scope, but your role in the organization does not reach it. |
organization_not_permitted | 403 | The credential is not bound to the organization you asked for. |
not_found | 404 | No such record — or one you cannot see. See below. |
invalid_request | 400 | Malformed input; the message says what. |
revocation_unavailable | 503 | We could not confirm your credential is still live. Retry. |
internal_error | 500 | Our 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:readandaudit:readneed 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