Skip to main content

Partner API: errors and the message_key contract

Every error shares one JSON envelope. This is what is in it, what each HTTP status means, and why your code should branch on message_key rather than on the English message.

Written by Vaibhav Kashyap

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). Example message_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.

Did this answer your question?