Errors & limits
Every failure the Generate API returns uses one JSON error shape, one table of codes, and a shared rate limit. This page is the complete reference for all three, plus retention and wire conventions.
Error responses
When a request fails, the response body is a JSON object with a single error key holding a machine-readable code and a human-readable message:
{
"error": {
"code": "invalid_request",
"message": "input.image_urls is required for image-to-3d"
}
}Match on error.code, not on the message text — messages may be reworded, codes are stable.
Exception: insufficient balance
A 402 insufficient_balance response carries two extra top-level fields so you can act without a follow-up call to GET /v1/balance:
{
"error": {
"code": "insufficient_balance",
"message": "balance is below the minimum required to start a job"
},
"balance_usd": 0.42,
"min_balance_usd": 1.0
}Exception: authentication failures
A missing, invalid, or revoked API key is rejected with 403 Forbidden before the request ever reaches the API's handlers. The body is the API gateway's own {"message":"Forbidden"}, not the { error: … } shape — treat any 403 as an auth problem and check your key.
Error codes
The complete set of error codes, the HTTP status each rides on, and what to do about it:
| Code · HTTP status | Meaning & remedy |
|---|---|
| invalid_request · 400 | The request body failed validation, was malformed JSON, or carried an invalid parameter value. Fix the payload and retry — the message names the offending field. |
| invalid_cursor · 400 | The cursor query parameter on GET /v1/jobs is not a token this API issued. Resume listing from a fresh response. |
| insufficient_balance · 402 | Your balance is below min_balance_usd ($1.00), so a new job was refused. The body also carries top-level balance_usd and min_balance_usd. Top up on the dashboard and retry. |
| not_found · 404 | No such resource — an unknown job id, or a job owned by another account (ownership is never leaked; another user's id looks identical to a nonexistent one). Check the id or list your jobs. |
| not_configured · 404 / 409 | No webhook endpoint is configured for your account. PUT a URL to /v1/webhook-endpoint first, then retry. |
| internal_error · 500 | Something failed on our side. Retry with backoff; if it persists, contact support with the job id and timestamp. |
Statuses not in this table — such as 429 and 502 — can still occur at the infrastructure layer. See rate limits below.
Rate limits
Requests are limited to 50 per second with a burst allowance of 100. The limit is shared across the whole API — every endpoint draws from the same bucket, and there are no per-key limits yet.
There is no dedicated 429 contract. If you receive a 429 or any 5xx, back off exponentially and retry. Clients that poll should stay at a gentle cadence — 2–5 seconds between status checks is plenty — and switch to webhooks at volume.
Data retention
Task records are kept for 90 days. After that, a job id stops resolving to its task object — poll or list your jobs and store anything you need for long-term history.
Model and thumbnail URLs are permanent. Once a job succeeds, its
model_urlsandthumbnail_urlkeep working indefinitely — download or hotlink them whenever you like.
Conventions
All timestamps are ISO 8601 UTC, e.g.
2026-10-01T12:00:00Z.All ids — job ids, event ids — are
ULIDs: 26-character sortable strings.Requests and responses are JSON only. Send
Content-Type: application/jsonon every request with a body.