Skip to content
All pages

Concepts

Errors that can be told apart

Every refusal shares one shape, so you write your handling once: a stable machine-readable error code, and a message in prose.

The contract

Branch on error, never on message: the code never changes for a given condition, while the sentence may be reworded between releases. Some refusals carry one extra field named in the table below, such as required_ability on a 403 or the per-field errors bag on validation.

The four refusals worth telling apart are the ones whose next step differs: 401 rotate the credential, 402 change the plan (the message names the tier that lifts it), 403 grant the ability, 404 the id is not this team's. A plan limit never arrives disguised as a permissions error.

A refusal 403
{
    "error": "missing_ability",
    "message": "This token does not have the reports:export ability.",
    "required_ability": "reports:export"
}
Validation, per field 422
{
    "error": "validation_failed",
    "message": "The request could not be accepted as sent.",
    "errors": {
        "window_start": [
            "The period has to start in the past. The weather has not happened yet."
        ]
    }
}

Every status, every code

Status Code Meaning What to do
401 unauthenticated The credential is missing, unknown, expired or revoked. Check the Authorization header and rotate the token if it has been revoked.
402 plan_limit The plan does not include this, or its allowance is spent. The message names the tier that would lift it. Change the plan, or wait for the allowance to renew.
403 missing_ability The credential is valid but was not granted the ability this endpoint requires. Grant the ability named in required_ability, or use a token that holds it.
404 not_found No such record for this team. Another team's id answers the same way, deliberately. Check the id. A 404 never confirms whether the id exists elsewhere.
405 method_not_allowed The path exists but not with this verb. Check the method against the reference.
409 conflict The request is at odds with current state: a duplicate address, a reused Idempotency-Key, a document not yet generated. The message says which. Each 409 in the reference states what to do.
422 validation_failed A field failed validation. errors names each field with a sentence. Fix the named fields and resend.
422 invalid The request was well formed but cannot be acted on: unreadable GeoJSON, an address that cannot be located. The message is the explanation. Fix the input rather than retrying.
429 rate_limited Too many requests in the window. Wait the interval in the Retry-After header, then resume.
500 server_error Something failed on our side. The response never carries internals. Retry, and contact support if it persists.
503 unavailable A dependency of the endpoint is down, such as the PDF renderer. An outage of ours, not a billing problem. Retry later.

What a 500 never carries

A failure on our side is reported on our side. The response says so in the same shape as every other refusal and never carries internals: no stack, no query, nothing that belongs in our logs rather than yours.

esc
move open 72 places