Envelope
{
"error": {
"code": "insufficient_credits",
"message": "Not enough credits to render, need 10, have 4.",
"needed": 10,
"have": 4
}
}
code is stable and safe to switch on. message is for humans and may
change. Extra structured fields are namespaced per error code.
Error codes
| Code | HTTP | Meaning |
|---|---|---|
unauthenticated | 401 | Missing or invalid Authorization header. |
rate_limited | 429 | Per-account rate cap hit. Honor Retry-After header AND retry_after_ms field. |
invalid_args | 400 | Body or query failed validation. |
workflow_not_found | 404 | No workflow with that id, or not owned by this user. |
clip_not_found | 404 | Clip id doesn’t exist in the workflow graph. |
track_not_found | 404 | Track id not derivable from the workflow’s edges. |
job_not_found | 404 | Render job id doesn’t exist. |
insufficient_credits | 402 | Reserve would go below zero. Includes needed, have. |
monthly_cost_cap | 402 | Per-user upstream USD budget exceeded for the month. |
workflow_too_large | 413 | Workflow JSON exceeds 5 MB ceiling. |
ledger_read | 500 | Unrecoverable read error on the ledger; retryable. |
upstream | 502 | An underlying generation provider returned a non-200. |
timeout | 504 | Upstream call exceeded the per-route deadline. |
Handling
const res = await fetch(url, { method: 'POST', headers, body });
if (!res.ok) {
const { error } = await res.json();
if (error.code === 'insufficient_credits') {
// surface upgrade CTA with error.needed / error.have
} else {
// generic error surface, use error.message
}
}
import httpx
r = httpx.post(url, headers=headers, json=body)
if r.status_code >= 400:
err = r.json()["error"]
if err["code"] == "insufficient_credits":
...