Errors, retries and rate limits
v3 and 2.1 report failures in two different shapes, but enforce the same rate limits and the same retry story. Knowing which API answered tells you how to read the failure — knowing what you called tells you whether replaying it is safe.
Before you read this
Telling an authorization failure from a validation one starts with knowing what a key's scopes actually let it do. Review API keys and scopes
The v3 error envelope
A v3 failure is a JSON object with a machine-readable code and a human-readable message, both nested under error:
{
"error": {
"code": "NOT_FOUND",
"message": "Container not found"
}
}
| Code | Meaning |
|---|---|
| NOT_FOUND | The requested resource does not exist. |
| NO_MESSAGE | No message is attached to the container yet. |
| MESSAGE_MISSING | The operation requires a message that has not been recorded. |
| DATA_INVALID | The request body failed validation against its expected shape. |
| EMPTY_UPDATE | A PATCH request carried no field to update. |
| VALIDATION_ERROR | The request failed validation for a reason not covered above. |
| FORBIDDEN | The key is valid but is not scoped or constrained for this request. |
| UNAUTHORIZED | The key is missing, unknown, or otherwise not authenticated. |
The 2.1 error envelope
A 2.1 response carries a numeric status instead of an HTTP-style code — 0 for success, 1 for failure — with a short error string identifying what went wrong:
{
"status": 1,
"error": "crid_unknown"
}
| Code | Meaning |
|---|---|
| key_invalid | The key segment in the path does not match a known key. |
| forbidden | The key is valid but not scoped for this route. |
| auth_unavailable | The key could not be checked because the auth dependency was unreachable. |
| crid_unknown | The container id in the path does not exist. |
| data_invalid | The request body failed validation. |
| no_message_for_this_crid | The container exists but carries no message. |
| message_missing | The operation requires a message that has not been recorded. |
| no_language_found | No language could be resolved for the response. |
| no_pdf_template | No PDF template is configured to render the card. |
| card_renderer_unavailable | The card renderer could not be reached. |
| card_renderer_failed | The card renderer was reached but failed to render. |
| webhook_not_found | The referenced webhook does not exist. |
| internal_error | An unexpected failure occurred on the server. |
Rate limits, the same on both APIs
Both APIs enforce the same two-tier limit, regardless of which one answers the request.
- A per-IP limiter applies before authentication, ahead of any key check.
- Once authenticated, a key is limited to 1000 requests per 15 minutes.
- The two limits are identical on v3 and on 2.1 — there is no separate quota to plan for.
What is safe to retry
Container creation is idempotent on the crid you supply: replaying a POST /containers call for a crid that already exists returns the existing container's links without creating a second one or mutating it. That guarantee does not extend to every call.