Errors
Every /v1 failure returns the same structured envelope and an unambiguous HTTP
status:
{
"error": {
"code": "invalid-input",
"message": "The request data could not be rendered.",
"requestId": "req_…"
}
}
code— a stable, machine-readable string. It is an open enum: new codes can be added additively, so treat an unrecognized code by its HTTP status class, not by exact match.message— a safe, human-readable summary. Internal engine/template details are never leaked here.requestId— the correlation id. It's also returned in thex-request-idresponse header on success. Quote it in support requests.
Main status codes
| HTTP | code | When |
|---|---|---|
400 | invalid-input | Malformed / unparseable request body (bad JSON). |
401 | unauthorized | Missing, invalid, or revoked API key. See Authentication. |
402 | insufficient-credits | The account is out of credits. Top up to continue — don't retry blindly. |
403 | forbidden | Valid key but the subscription/capability can't perform the action (e.g. lapsed to read-only, or missing canEditVariables). |
404 | template-not-found | Unknown template (or a tmpl_/wtpl_/bat_ id outside your account — no existence disclosure). |
422 | invalid-input | Well-formed but rejected: oversized values, an unsupported target/format, or template data that can't be resolved. |
429 | rate-limited | Per-key rate-limit window exceeded. Carries Retry-After (seconds). |
5xx | render-timeout (504) · engine-unavailable (503) · render-failed (502) | A transient or internal render-side failure. |
Retrying safely
429— waitRetry-Afterseconds, then retry.503/504— safe to retry with backoff. Send the sameIdempotency-Keyso a retry that actually succeeded server-side isn't charged twice.402— the balance is empty; top up rather than retrying.400/422— the request is wrong; fix it before resending.
On the render endpoint the 422 body is returned with the response's accepted content
type; for every other endpoint the envelope is application/json.