API reference / Errors
Errors
When a request fails, the API returns JSON (not a PDF) with a consistent envelope and the matching HTTP status.
{
"error": {
"code": "invalid_data",
"message": "Variable 'items' must be a list, but got a string."
}
}The code is a stable, machine-readable string from the closed set below. The message is a
human-readable explanation, safe to log. Branch your handling on code, show or log message.
The complete set
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
unauthorized | 401 | Missing, invalid, or revoked API key. | Check the Authorization header and that the key is active. |
payment_required | 402 | A paid plan's subscription is not active. | Update billing or re-subscribe. The Free plan is never blocked this way. |
account_suspended | 403 | The account was suspended by the platform. | Not retryable. Contact support if you believe this is a mistake. |
rate_limited | 429 | Too many requests in a short window. | Slow down; honor the Retry-After header. See limits. |
limit_exceeded | 429 | Your plan's monthly quota is used up. | Wait for the next month (UTC) or upgrade. |
template_not_found | 404 | No template with that templateId exists on your account. | Check the tpl_… code. |
template_not_published | 422 | The template exists but has no published version. | Publish the template, then retry. |
invalid_json | 400 | The request body is not valid JSON. | Fix the JSON syntax and the Content-Type. |
invalid_data | 422 | Valid JSON, but the wrong shape or data that does not satisfy the template's variables. | Read message; it names every offending variable. |
idempotency_conflict | 409 | The same Idempotency-Key was reused with a different request body. | Use a new key for a different request. See Idempotency. |
render_failed | 502 | Rendering failed, or an external resource was blocked, unreachable, or timed out. | Check the template; for assets, message names the URL. See assets. |
service_unavailable | 503 | The render engine is momentarily at capacity. | Transient. Retry shortly with backoff. |
internal_error | 500 | The document rendered but recording it failed, so the call is reported as failed. | Retry. If it persists, contact support. |
Reading an invalid_data message
This is the error you hit most while integrating, so its message is written to tell you
exactly which field is wrong and what shape it expects. It names the offending top-level
variable and, for a missing one, the shape the template uses it as:
{
"error": {
"code": "invalid_data",
"message": "Missing required variable 'total' (expected a simple value)."
}
}A value of the wrong shape reports both what was expected and what it received:
{
"error": {
"code": "invalid_data",
"message": "Variable 'items' must be a list, but got a string."
}
}When several fields are wrong, every problem is listed so you can fix them in one pass instead of discovering them one request at a time:
The data does not match the template:
- Missing required variable 'total' (expected a simple value).
- Variable 'items' must be a list, but got a string.The variable named is always a top-level key of your data object (the template's required
inputs), so the fix is to add or correct that key and retry.
Quota and errors
A failed call never consumes quota. Only a successful generation increments your monthly counter (and it does so in the same transaction that records the success, so usage can never drift from what actually rendered). This means retrying a failed request is always safe with respect to billing.
What shows up in your history
The failures you can act on are recorded in your generation history with
an actionable message: payment_required, account_suspended, limit_exceeded,
template_not_found, template_not_published, invalid_data, and render_failed. The rest
(unauthorized, rate_limited, service_unavailable, internal_error) are not logged, because
there is either no trusted account to attribute them to or nothing for you to fix in the request
itself.
Handling errors well
- Fix, do not retry, the 4xx request errors (
template_not_found,template_not_published,invalid_json,invalid_data,idempotency_conflict): retrying the same request will fail the same way. - Back off and retry the transient ones (
rate_limited,service_unavailable, and usuallyinternal_error). Forrate_limited, wait at least as long asRetry-Aftersays. See Rate limits & quotas for a retry strategy. - Resolve account state for
unauthorized(rotate or fix the key),payment_required(billing), andaccount_suspended(contact support).