One envelope, everywhere
Every error this API returns has the same shape, whether it came from authentication or from the endpoint itself:
{
"error": {
"code": "insufficient_scope",
"message": "Key is missing required scope(s): payroll:read.",
"message_key": "partner_api.insufficient_scope"
}
}
Branch on message_key, never on message
message_key is the stable contract. It is what your code should switch on, log and map to your own error handling. message is English prose written for a human reading a log, and it is free to be reworded, re-punctuated or made more specific at any time without notice. Code that string-matches on message will break, and it will break silently.
code is stable too and is the shorter of the two; either is safe. What is not safe is the prose.
What each status means
400 — A query parameter is missing, unknown, or outside its allowed values. Example
message_key:partner_api.invalid_request.401 — The API key is missing, unknown, revoked, or expired. These are deliberately indistinguishable. Example
message_key:partner_api.unauthorized.403 — The key is valid but not permitted: either it lacks a scope this endpoint requires (
insufficient_scope), or the owning tenant account is not active (tenant_inactive). Examplemessage_key:partner_api.insufficient_scope.404 — The requested record, or an id named in a filter, does not exist for your tenant. An id belonging to another tenant is reported the same way as one that does not exist at all. Example
message_key:partner_api.not_found.429 — Per-key rate limit exceeded. Back off and retry. Unlike every other status here, this response does NOT carry the JSON error envelope: it is produced by the rate limiter before the API is reached.
502 — An Asanify system behind this endpoint failed. The request was not served; retry. Example
message_key:partner_api.upstream_error.503 — Key authentication is temporarily unavailable. Retry. Example
message_key:partner_api.unavailable.
Two that behave differently from the rest
404 never means "we broke". Asanify does not report an internal failure as a 404, so your integration can safely treat one as a real absence — the employee was removed, the run does not exist — rather than as something to retry. An id belonging to another company reads as a 404 too, because the API will not confirm that another company's records exist.
502 is not "no data". It means Asanify failed while reading. Retry it. Do not deactivate records on your side because of a 502.
And the 429 is a special case that gets its own article — see Partner API: rate limits and the 429 response.
