API errors
Every code the Imagera API returns, what causes it, and whether retrying can help.
Read the code, not the status
Errors are RFC 9457 problem documents, served as application/problem+json. Branch on code, which is stable forever — not on the HTTP status, which several codes share. Three codes return 404, two return 409, two return 429 and two return 400.
{
"type": "https://imagera.ai/developers/errors#invalid_request",
"title": "Invalid request",
"status": 422,
"detail": "A prompt is required for this feature.",
"code": "invalid_request"
}type links straight to the section for that code on this page. detail is the only free-text field, and it never names a supplier or an upstream model.
A 401 is deliberately identical for a missing, malformed, unknown, revoked or expired key — distinguishing them would let anyone probe for valid keys. Getting a 403 instead is the signal that your key itself authenticated.
All 27 codes
Ordered by HTTP status.
request_not_ready
HTTP 202Request not readyThe request exists and is still running. This is not a failure — it is the normal answer while a generation is in flight.
What causes it
- You fetched the result before the generation reached a terminal state.
What to do
Back off and poll the `status_url` again. The response carries `Retry-After`, so you can honour it without parsing prose. Prefer a webhook over polling for anything longer than a few seconds.
Also carries deprecated_code.
Until 2026-11-17 this also carries `deprecated_code: "request_not_found"`, because these three states were once collapsed into one 404. Read `code`, not `deprecated_code`.
unsupported_parameter
HTTP 400Unsupported parameterThe request parsed and asked for something this version does not honour. Distinct from `malformed_body`, the other 400, which never parsed at all.
What causes it
- A parameter that exists in the schema but is not served by v1 for this model.
What to do
Remove the parameter. The model document at `/v1/models/{model}` is authoritative for what is accepted.
malformed_body
HTTP 400Malformed request bodyThe body could not be parsed at all, so validation never ran. Distinct from `unsupported_parameter`, the other 400, where the body parsed and one field was not honoured.
What causes it
- Invalid JSON.
- A missing or wrong `Content-Type` header.
What to do
Send `Content-Type: application/json` and a syntactically valid JSON body.
idempotency_key_required
HTTP 400Idempotency key requiredA board run was sent without an `Idempotency-Key` header, so nothing was started or charged. Distinct from `malformed_body` and `unsupported_parameter`, the other 400s: the body was never read.
What causes it
- `POST /v1/workflows/{workflow_id}/runs` without the header. A board run starts every step of the board in one request, so a retry after a lost response must be recognisable as the same run.
What to do
Send `Idempotency-Key` with a unique value (at most 255 characters) per intended run, and resend the same value when you retry — the retry returns the run already started instead of starting a second one.
insufficient_credits
HTTP 402Insufficient creditsThe account behind the key does not hold enough credits for this generation.
What causes it
- Balance below the cost of the request at submit time.
What to do
Top up at the page in `top_up_url`, or call the pricing quote endpoint first and check the balance before submitting.
Also carries credits_needed, credits_available and top_up_url.
forbidden_scope
HTTP 403Insufficient scopeThe key is valid but was not issued with the scope this route requires.
What causes it
- Calling `/v1/models/{model}` or the pricing quote with a key scoped only to `generate`. (The model list, `GET /v1/models`, needs no key and no scope.)
- Calling a write route with a read-only key.
What to do
Issue a key carrying the scope the route needs. Scopes are fixed at creation; a key cannot be widened after the fact.
A 403 here — as opposed to a 401 — is a useful diagnostic: it proves the credential itself authenticated.
model_not_found
HTTP 404Model not foundNo model is published under that public id. One of three codes that return 404 — this one means the route was right and the model id was not.
What causes it
- A typo in the model id.
- An internal id used where a public id is required.
What to do
List the catalogue with `GET /v1/models` and use the `id` field verbatim.
model_retired
HTTP 410Model retiredThe model id was valid once and has been retired. The response names the successor in `extensions.successor`; nothing was queued and nothing was charged.
What causes it
- An integration still sending a public id we have withdrawn in favour of a newer generation.
What to do
Switch to the `successor` id in the response. GET /v1/models lists everything currently published.
Retirements are listed in shared-config/retired-models.ts with the date and the reason.
request_not_found
HTTP 404Request not foundNo request exists with that id for this key’s owner. One of three codes that return 404; unlike `model_not_found` and `not_found`, the path and the model were both fine.
What causes it
- An unknown request id.
- A request id belonging to a different account.
What to do
Use the `request_id` returned by the submit call, with a key on the same account.
Cross-account ids answer identically to unknown ones, on purpose.
not_found
HTTP 404Not foundNo route is published at that path. Distinct from `model_not_found`, which means the route was right and the model id was not.
What causes it
- A path outside the published surface.
What to do
Check the path against the API reference.
method_not_allowed
HTTP 405Method not allowedThe route exists but not for that HTTP method.
What causes it
- A `GET` where the route takes `POST`, or the reverse.
What to do
Read the `Allow` response header, which lists the methods the route accepts.
request_cancelled
HTTP 409Request cancelledThe request was cancelled and will never produce a result.
What causes it
- A prior successful call to the cancel endpoint.
What to do
Stop polling and submit a new request if you still need the output.
Also carries deprecated_code.
Shares HTTP 409 with `idempotency_conflict`. The `code` is the branch point, not the status.
idempotency_conflict
HTTP 409Idempotency key in flightYou reused an `Idempotency-Key` whose original request is still running.
What causes it
- A retry fired before the first attempt reached a terminal state.
What to do
Poll the original request rather than resubmitting. This is a “wait”, not a permanent failure — which is exactly what distinguishes it from `request_cancelled`, the other 409.
409 rather than 202 because you have not been handed a `request_id` for this attempt, so answering “accepted” would be a lie.
project_limit_reached
HTTP 409Project limit reachedA board run needs a project to land in, and the account is at its plan’s project limit. Shares HTTP 409 with `idempotency_conflict` and `request_cancelled`; nothing was started or charged.
What causes it
- The first API board run on an account that already keeps as many projects as its plan allows. Every API run lands in one project called “API runs”, created on first use.
What to do
Close a project you have finished with in Imagera, then retry. After the “API runs” project exists, later runs reuse it and do not count against the limit again.
workflow_changed
HTTP 409Workflow changedThe board was saved between the price check and the start of a board run, so nothing was started or charged. Shares HTTP 409 with `project_limit_reached`, `idempotency_conflict` and `request_cancelled`.
What causes it
- The board was edited in Canvas (or saved from another tab) while the run request was being priced.
What to do
Retry the same request. It is priced again against the board as it is now, and `max_credits` is checked against that price.
limit_request_pending
HTTP 409Limit request pendingYou asked for a higher parallel limit while an earlier request is still under review, so nothing new was filed. The body carries its `request_id`. Shares HTTP 409 with `project_limit_reached`, `workflow_changed`, `idempotency_conflict` and `request_cancelled`.
What causes it
- `POST /v1/limits/requests` while a request filed from the API or from Settings is still pending (one at a time per account).
What to do
Check its status with `GET /v1/limits` (`latest_request`). Once it is decided you can ask again.
invalid_request
HTTP 422Invalid requestThe body parsed but failed validation against the model’s published input schema.
What causes it
- A missing required parameter, such as `prompt`.
- A parameter the schema does not declare — every model schema is `additionalProperties: false`.
- A value of the wrong type, or outside the allowed range or enum.
What to do
Read `detail`, which names the offending parameters, and compare against `GET /v1/models/{model}`. Note that image inputs are `image_urls` — an array — on most models.
Cancelling a request that has already left the pending state also answers with this code. In that one case `detail` is generic rather than naming a parameter: the request is already running and cannot be cancelled — poll it to completion instead. Board runs have three more 422 codes — `unpriced_steps`, `credit_cap_exceeded` and `workflow_not_runnable` — for refusals that are about the board or its price rather than your fields; on those routes `invalid_request` means the inputs themselves (`errors[].path` names them).
unpriced_steps
HTTP 422Unpriced stepsA board run was refused because at least one step has no firm price. One of four 422 codes; unlike `invalid_request`, your fields were valid.
What causes it
- A step no model can run, or one the deployment could not price with these inputs.
- A step priced from the measured length of a clip or track it reads, sent without `"allow_input_length_pricing": true`.
What to do
For steps that cannot be priced, fix them in Canvas or run to an earlier step with `target`. For length-priced steps, send `"allow_input_length_pricing": true` to accept `credits_quoted` as a minimum. `POST /v1/workflows/{id}/quote` shows which steps and why.
Also carries unpriced_steps, input_length_steps and credits_quoted.
credit_cap_exceeded
HTTP 422Credit cap exceededThe board’s quote for this run is above the `max_credits` you sent, so nothing was started. Shares HTTP 422 with `unpriced_steps`.
What causes it
- `max_credits` lower than the priced steps of the board for these inputs.
What to do
Quote the run with `POST /v1/workflows/{id}/quote`, then raise `max_credits` or run less of the board with `target`. `credits_quoted` is in the response.
Also carries credits_quoted and max_credits.
`max_credits` is checked against the quote before the run starts, and again as it opens. While it runs, a dispatch that would take it past `max_credits` is not bought: the run ends `failed` with a "Budget reached" error, keeping what it already made. A step whose model falls back to another during the run is billed at that model’s price.
workflow_not_runnable
HTTP 422Workflow not runnableThe board is unfinished — the Canvas lists issues on it — so it cannot run. Shares HTTP 422 with `invalid_request`: here the board needs editing, not your request.
What causes it
- A step with a missing model, a broken wire, or a prompt that reads an input the board no longer has.
What to do
Open the board in Canvas, fix the listed `issues`, and retry. `GET /v1/workflows/{id}` shows them too.
Also carries issues.
concurrency_limit_reached
HTTP 429Concurrency limit reachedThe account already has as many generations in flight as its tier permits.
What causes it
- Submitting while `max_parallel` generations are already running.
What to do
Wait for one to finish, then submit. This is a concurrency ceiling, not a request-rate ceiling — see `rate_limit_exceeded` for that.
Also carries in_use, max_parallel, tier_key and upgrade_available.
rate_limit_exceeded
HTTP 429Rate limit exceededreturned by the edge, before the gatewayToo many requests from one credential or one address inside the window. Shares HTTP 429 with `concurrency_limit_reached`, which is a different ceiling: that one counts generations running at once, this one counts requests per minute.
What causes it
- A runaway retry loop.
- Bulk polling where a webhook would do.
What to do
Honour `Retry-After` and the `RateLimit` headers, which are exposed on every response. This ceiling is set far above any real integration; hitting it usually means a retry loop rather than genuine volume.
Also carries limit, remaining, window_seconds and retry_after_seconds.
Emitted by the edge proxy in front of the gateway, so it is the one code that never originates from the generation path.
content_policy
HTTP 451Content policyThe input was refused by the content-safety gate.
What causes it
- A prompt or an input image that the safety check declined.
What to do
Change the input. Retrying the same request unchanged will always fail.
internal_error
HTTP 500Internal errorSomething unexpected failed on our side.
What causes it
- A fault in the gateway or a dependency of it.
What to do
Retry with backoff. If it persists, contact support and quote the `request_id` from the response body.
Also carries request_id.
Carries a `request_id` you can quote to support, and never a stack trace.
not_implemented
HTTP 501Not implementedA capability this version does not serve yet.
What causes it
- A route or option that is published but not live in v1.
What to do
Stated rather than failed silently, so you can branch on it. Check the reference for what v1 serves.
provider_error
HTTP 502Generation service errorThe generation service failed after the request was accepted.
What causes it
- An upstream fault.
- An input that passed our schema but was refused further down — model schemas do not publish every upstream constraint.
What to do
Retry with backoff. If it repeats for one specific input, treat the input as the cause and vary it.
The message is fully sanitised, so it never names a supplier or an upstream model.
Still stuck?
Quote the code and, for a 500, the request_id from the response body — those two identify the request exactly.
Updated