Errors and rate limits
What an error looks like, what each status means and how to retry.
Error body
Every error has the same shape:
{
"error": {
"code": "validation_failed",
"message": "Check the highlighted fields.",
"details": { "fields": { "email": "Enter a valid email address." } }
}
}Branch on code, which is stable; message is for people and may change.
Status codes
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request could not be read: malformed JSON or an unknown field. |
| 401 | unauthenticated | No token, or an unknown, expired or revoked one. |
| 402 | plan_required | The workspace has no active plan: it is read-only until one is chosen. |
| 402 | limit_reached | The workspace's plan limit for this kind of record is reached. |
| 403 | forbidden | Your role or permissions do not allow this. |
| 403 | token_scope | The token's scopes do not cover this operation. |
| 403 | token_not_allowed | This part of the API is only available in the app. |
| 404 | not_found | The record does not exist in the token's workspace. |
| 409 | (varies) | The record's state does not allow this, for example issuing an issued invoice. |
| 422 | validation_failed | Some fields are invalid; details.fields names each one. |
| 429 | rate_limited | Too many requests; wait as Retry-After says. |
| 5xx | Something went wrong on our side; retry later. |
Rate limits
Each token may make 600 requests a minute. Beyond that the API answers 429 rate_limited with a
Retry-After header in seconds. Wait that long, then continue; spread bulk jobs out rather than retrying in a
tight loop.
Retrying safely
- Retry
429and5xxwith a growing delay. - Do not blindly retry a
POSTthat timed out: the record may have been created. List or get it first. 4xxanswers other than429will fail again unchanged: fix the request instead.