Every non-2xx response of the public API has the same envelope:
{
"error": {
"code": "not_found",
"message": "User not found",
"status": 404,
"details": [ { "location": "query.per_page", "message": "..." } ]
}
}
code — stable, machine-readable identifier. Branch on this, never on the
message text.message — human-readable English description (may change over time).status — the HTTP status, duplicated for convenience.details — optional structured context (e.g. per-field validation errors).| Status | Code | When |
|---|---|---|
| 400 | bad_request | The request is syntactically valid but cannot be processed. |
| 401 | unauthorized | Missing, malformed, wrong, expired or revoked API key. |
| 403 | forbidden | Valid key, but a missing scope or an inactive module. |
| 404 | not_found | The resource does not exist in your company (malformed ids included). |
| 405 | method_not_allowed | Wrong HTTP method for the path. |
| 409 | conflict | Concurrent operation in progress (e.g. an in-flight idempotent request). |
| 422 | validation_error | A parameter failed validation — see details. |
| 429 | rate_limit_exceeded | A rate-limit window is exhausted — see Retry-After. |
| 500 | internal_error | Unexpected server error. Safe to retry with backoff. |
422 responses carry one entry per offending parameter:
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"status": 422,
"details": [
{ "location": "query.per_page", "message": "Input should be less than or equal to 100" }
]
}
}
Requesting an id that belongs to another company returns 404, not 403 —
the API never confirms the existence of data outside your tenant.