error field tells you the exact reason, and message
is safe to surface to end users (no stack traces, no internal info).
HTTP status codes
| Code | Category | Typical |
|---|---|---|
200 | Success | Sync result returned |
201 | Resource created | File uploaded, key minted, webhook subscribed |
202 | Accepted (async) | Task queued; poll /tasks/{id} |
204 | No content | Image/key/webhook deleted |
302 | Redirect | GET /runs/{id}/download |
400 | Bad request | Validation or preflight error |
401 | Unauthorized | Bad / missing API key |
402 | Payment required | Out of credits |
403 | Forbidden | Key valid but not permitted |
404 | Not found | Run / file / image doesn’t exist or belongs to another org |
408 | Timeout | Sync ceiling exceeded; response tells you to retry async |
409 | Conflict | Idempotency collision or invalid state |
410 | Gone | Resource existed but was deleted / expired |
413 | Payload too large | Upload exceeds tier limit |
422 | Validation | Field-level rejection on the request body |
429 | Rate limited | See Retry-After header |
500 | Server error | Our bug. Gets paged. |
501 | Not implemented | Endpoint exists but feature isn’t live yet |
502 | Upstream error | A dependency we rely on (model provider, storage, or rendering pipeline) failed |
503 | Service unavailable | Capacity exhausted or maintenance |
Error codes
Authentication
| Code | When | Remedy |
|---|---|---|
invalid_api_key | Missing, malformed, or revoked sk_live_* | Check Authorization header; rotate if compromised |
forbidden | Key OK but lacks permission (suspended org, wrong tier) | Check org status on GET /verify |
Billing
| Code | When | Remedy |
|---|---|---|
insufficient_credits | Org balance below the format’s minimum | Top up via dashboard or contact sales |
Validation
| Code | When | Remedy |
|---|---|---|
invalid_request | Missing required field, bad enum value | Fix payload per error message |
validation_error | Pydantic field-level error | Error message has JSON pointer to the bad field |
preflight_failed | LLM validator rejected (missing assets) | Attach the missing assets or rephrase the prompt |
payload_too_large | File exceeds tier limit | Compress or upgrade tier |
Resources
| Code | When | Remedy |
|---|---|---|
not_found | Run / file / image / webhook ID doesn’t exist OR belongs to another org | Check ID spelling; confirm you created it under the same org |
gone | Resource expired (30-day retention) | Start a new run |
conflict | Idempotency key reused with different body, or resource in wrong state | Different key, or wait for terminal state |
Runtime
| Code | When | Remedy |
|---|---|---|
sync_timeout_upgrade_async | Sync request hit 300s ceiling | Resubmit with async: true; poll /tasks/{id} |
rate_limit_exceeded | Over per-key tier limit | Retry after the Retry-After header seconds |
internal_error | Unhandled server exception | Include X-Request-Id in support ticket |
upstream_error | LLM provider / storage outage | Retry with exponential backoff |
service_unavailable | Pool saturated, scheduled maintenance | Retry after 30-60s |
not_implemented | Endpoint exists (for shape compatibility) but the feature isn’t available on your tier / deployment | Contact support |
Handling errors in code
Debugging failed runs
Capture therun_id and task_id from any response (successful or not)
and include them on support tickets — they’re the fastest way for us to
pull the matching ledger / trace rows on our side.
For terminal failed states on async tasks, the error field on
GET /tasks/{task_id} carries the structured failure:
Rate limits
Every response includes rate-limit headers:| Tier | Requests/min | Concurrent runs |
|---|---|---|
free | 10 | 2 |
pro | 60 | 10 |
enterprise | 600 | 50 |
