Errors
Status codes, the error body, and how to retry safely.
Errors use an HTTP status and a JSON body:
{
"defined": true,
"code": "BAD_REQUEST",
"status": 400,
"message": "An archived batch takes no new jobs"
}Log the status, message, request path, and your own correlation id. Never log the full API key.
| Status | code | Meaning | What to do |
|---|---|---|---|
| 400 | BAD_REQUEST | Invalid value or reference, such as a layer that does not exist or a page that is not in the workspace. Job and batch requests list every problem in data.issues. | Fix the request. |
| 401 | UNAUTHORIZED | Missing, invalid, or revoked key. | Check the x-api-key header. |
| 402 | PAYMENT_REQUIRED | Not enough credits to run, or the feature needs an active subscription (webhooks and callback URLs). | Add credits, reduce the work, or subscribe. |
| 403 | FORBIDDEN | Your role or key lacks the permission, the key cannot use this workspace, or the key has no workspace or access set (a legacy key). | Check the permission on the endpoint page, or edit the key. |
| 404 | NOT_FOUND | The resource does not exist in this workspace, or the workspace does not exist or you are not a member (Workspace not found). | Check the id and workspace. |
| 409 | CONFLICT | The state does not allow it, such as running a job that has already rendered, or updating a template that changed since you read it. | Refresh the resource first. |
| 413 | none | The body of a job or batch write is larger than 4 MiB. The body is { "error": "request_too_large", "message": "Request exceeds 4 MiB." }. | Send fewer rows, or split the batch. |
| 422 | UNPROCESSABLE_ENTITY | The input is well formed but not acceptable. | Correct the fields. |
| 429 | TOO_MANY_REQUESTS | The key exceeded 100 requests per minute. The message is API key rate limit exceeded. | Back off and retry. |
| 500 | INTERNAL_SERVER_ERROR | Unexpected failure. Some rejected runs are also reported as 500 with the reason in message. | Read the message; retry reads. |
| 503 | SERVICE_UNAVAILABLE | Temporarily unavailable. | Retry after a delay. |
Requests that fail schema validation, such as a missing field or a value of the wrong type, are rejected with 400 before reaching the endpoint. The message is Input validation failed and data.issues lists each problem with its path:
{
"defined": false,
"code": "BAD_REQUEST",
"status": 400,
"message": "Input validation failed",
"data": {
"issues": [
{ "expected": "string", "code": "invalid_type", "path": ["workspace"], "message": "Invalid input: expected string, received undefined" }
]
}
}Job and batch requests that fail on their values answer 400 with every problem at once. Each entry has a field, a code and a message, plus the item's job index or the row's row index:
{
"defined": true,
"code": "BAD_REQUEST",
"status": 400,
"message": "Invalid rows. rows[1].Headlin: No layer \"Headlin\" in this template. Its layers: Headline, Photo, Badge.",
"data": {
"issues": [
{ "row": 1, "field": "Headlin", "code": "unknown_layer", "message": "No layer \"Headlin\" in this template. Its layers: Headline, Photo, Badge." }
]
}
}The codes are listed in Modifications.
A 5xx body carries data.traceId. Quote it when you contact support, so the failed request can be found.
Retry policy
Retry GET requests on 429, 500, and 503 with exponential backoff and random jitter, and cap the number of attempts. For POST requests that create something, check whether the resource already exists before retrying so a lost response does not create a duplicate.
Do not retry 400, 401, 403, 404, or 409 without changing the request or the state it depends on.
Updated 9 Nov 2026