Errors and status codes
Errors are JSON, always the same shape, and always carry a stable machine-readable code. Branch on the code, not on the message.
The shape
Every failure returns an object with a human-readable error and a stable code. The message is written for a person and may be reworded; the code is a contract and will not change under you.
{
"error": "That file is larger than the 200 MB limit on your plan.",
"code": "VALIDATION_FAILED"
}Codes you should handle
| Code | HTTP | What it means |
|---|---|---|
| NOT_AUTHENTICATED | 401 | Missing or malformed bearer token |
| FORBIDDEN | 403 | The key is valid but not allowed to do this |
| NOT_ENTITLED | 402 | The account's plan does not include API access |
| VALIDATION_FAILED | 400 / 413 | Bad request, unsupported conversion, or file too large |
| NOT_FOUND | 404 | No such job, or it belongs to another account |
| RATE_LIMITED | 429 | Hourly limit reached. Wait and retry. |
| INTERNAL | 500 | Something broke on our side. Safe to retry. |
What is worth retrying
429 and 500 are worth retrying, with a delay — back off rather than looping, because retries count against your hourly limit like any other request.
Everything else is permanent for that request. Retrying a 400, a 402 or a 404 produces the same answer and spends your rate limit finding out.
A 502 or 503 without a JSON body did not come from the application — that is the proxy in front of it, and it is always worth retrying.