On this page
TL;DR — Branch on
error.code, not the message. Retry only the codes marked Yes, and network errors, with the sameIdempotency-Key. Each error links to its own section on this page.
Errors are JSON: {"error": {"code": "...", "message": "..."}}. Branch on code; the message is for people and may change. Every response has an X-Request-Id header: quote it to support.
Retry only the codes marked Yes below and network errors, with the same Idempotency-Key. Wait for Retry-After when the response has it, otherwise back off with jitter. Never retry the others: the same request will fail the same way.
Error body and headers
Rolling out. Every /v2 error answer, in both shapes (runs and Jobs API), gains these, on top of every field it already has:
| Where | Name | What it says |
|---|---|---|
| Body | error.docs_url |
A link to this code's section on this page, for example https://docs.wandura.ai/developers/api-v2/errors#product-busy. A code without its own section links to this page. |
| Body | error.retryable |
true when the same request can succeed later unchanged: every 429 and 5xx (for example product_busy, service_unavailable). false for input and auth errors. Jobs API errors already have it. |
| Header | Wandura-Error-Code |
The same value as error.code. |
| Header | Wandura-Should-Retry |
true or false, the same value as error.retryable. |
Retry-After and X-Request-Id stay as they are. All four are optional: ignore them if you do not use them, and treat a missing retryable like the table below.
HTTP/1.1 429
Retry-After: 5
Wandura-Error-Code: product_busy
Wandura-Should-Retry: true
{"error":{"code":"product_busy","message":"…","limit":20,"retry_after_s":5,"retryable":true,"docs_url":"https://docs.wandura.ai/developers/api-v2/errors#product-busy"}}Run errors
| HTTP | code |
Retry? |
|---|---|---|
| 400 | invalid_request |
No |
| 400 | idempotency_key_required |
No |
| 400 | end_user_id_required |
No |
| 401 | invalid_api_key |
No |
| 401 | api_key_expired |
No |
| 402 | wallet_empty |
No |
| 403 | insufficient_scope |
No |
| 403 | style_not_allowed |
No |
| 404 | style_not_found |
No |
| 404 | run_not_found |
No |
| 404 | not_found |
No |
| 409 | idempotency_key_reused |
No |
| 409 | run_not_cancelable |
No |
| 413 | file_too_large |
No |
| 415 | unsupported_content_type |
No |
| 429 | end_user_cap_reached |
Yes |
| 429 | gateway_rate_limited |
Yes |
| 429 | uploads_rate_limited |
Yes |
| 429 | product_busy |
Yes |
| 500 | internal_error |
Yes |
| 503 | style_unavailable |
Yes |
| 503 | uploads_unavailable |
Yes |
| 503 | service_unavailable |
Yes |
Submit answers, 429 ones included, carry X-Concurrency-Limit, X-Concurrency-Running and X-Concurrency-Waiting. Each product_busy and gateway_rate_limited answer is a refusal: it is listed at GET /v2/queue/refusals. See Queue and congestion.
A run that fails after it was accepted is not an HTTP error: the run ends failed or nsfw with a free-run reason, and you are not charged.
invalid_request
400, do not retry. The body or a parameter is wrong; the message says which.
idempotency_key_required
400, do not retry. Send an Idempotency-Key header (max 255 chars) on every submit.
end_user_id_required
400, do not retry. end_user_id is required: the id of the person in your app this run is for.
invalid_api_key
401, do not retry. Missing, unknown or revoked API key.
api_key_expired
401, do not retry. This API key has expired. Create or rotate a key.
wallet_empty
402, do not retry. The product wallet and the org pool cannot cover this run. Add money, then retry.
insufficient_scope
403, do not retry. Rolling out. This key has the Read only permission: it can read runs and styles, not submit, cancel, share or upload. Use a key with the Run styles permission.
style_not_allowed
403, do not retry. Rolling out. This key may run only some styles, and this is not one of them. Use a key that lists it, or ask your org admin.
A 403 only comes from a key made with a permission or a list of allowed styles (see API keys). A key made without them never gets one.
style_not_found
404, do not retry. No style with that id for your org (or not public).
run_not_found
404, do not retry. No run with that id for this product.
not_found
404, do not retry. No such endpoint.
idempotency_key_reused
409, do not retry. This Idempotency-Key was already used with a different body. Use a new key for a new run.
run_not_cancelable
409, do not retry. Only queued runs can be canceled.
file_too_large
413, do not retry. The file is over the size limit for its type.
unsupported_content_type
415, do not retry. Uploads accept JPEG, PNG, WEBP, MP4, MOV and WEBM.
end_user_cap_reached
429, retry. This end user reached the daily run cap. Retry after the Retry-After header (resets 00:00 UTC).
gateway_rate_limited
429, retry. A gateway key's product reached its runs-per-minute limit. Retry after the Retry-After header.
uploads_rate_limited
429, retry. Your org reached its presigned uploads per minute. Retry after the Retry-After header.
product_busy
429, retry. Your product's concurrency slots and its queue are full. Retry after the Retry-After header (about 5 s).
internal_error
500, retry. Something went wrong on our side. Retry with the same Idempotency-Key; quote X-Request-Id to support.
style_unavailable
503, retry. This style cannot run on a live key right now (no model behind it). Test keys still return a sample.
uploads_unavailable
503, retry. Uploads are not available on this deployment right now.
service_unavailable
503, retry. The job queue is briefly unavailable; nothing was created or charged. Retry with the same Idempotency-Key after the Retry-After header.
Jobs API errors
The Jobs API (/v2/jobs, /v2/models, GET /v2/styles) answers {"error": {"code", "message", "retryable"}}, plus details for INVALID_INPUT, with its own codes:
| HTTP | code |
retryable |
|---|---|---|
| 422 | INVALID_INPUT |
false |
| 401 | UNAUTHORIZED |
false |
| 402 | INSUFFICIENT_CREDITS |
false |
| 403 | FORBIDDEN |
false |
| 404 | NOT_FOUND |
false |
| 409 | IDEMPOTENCY_KEY_REUSED |
false |
| 409 | JOB_NOT_CANCELLABLE |
false |
| 429 | RATE_LIMITED |
true |
| 429 | PRODUCT_BUSY |
true |
| 429 | END_USER_LIMIT_REACHED |
true |
| 500 | INTERNAL_ERROR |
true |
| 503 | SERVICE_UNAVAILABLE |
true |
INVALID_INPUT
422, do not retry. The body or a header is wrong; details[] lists each {field, reason}. The run endpoints (POST /v2/runs, POST /v2/styles/{style}) answer the same code with 400 when a resolution or the number of files does not fit the style.
UNAUTHORIZED
401, do not retry. Missing, unknown, revoked or expired API key.
INSUFFICIENT_CREDITS
402, do not retry. The product's wallet cannot cover the job. Add funds, then retry.
FORBIDDEN
403, do not retry. The key is valid but may not do this. Rolling out: a key with the Read only permission submitting or cancelling, or a key limited to other styles (see API keys). Not returned before that.
NOT_FOUND
404, do not retry. No such job, model or endpoint for this key.
IDEMPOTENCY_KEY_REUSED
409, do not retry. This Idempotency-Key was already used with a different body. Use a new key for a new job.
JOB_NOT_CANCELLABLE
409, do not retry. Only queued jobs can be cancelled.
RATE_LIMITED
429, retry. Too many jobs submitted this minute for this product. Retry after the Retry-After header.
PRODUCT_BUSY
429, retry. Your product's concurrency slots and its queue are full. Retry after the Retry-After header (about 5 s).
END_USER_LIMIT_REACHED
429, retry. This end user reached the daily job cap. Retry after the Retry-After header (resets 00:00 UTC).
INTERNAL_ERROR
500, retry. Something went wrong on Wandura's side. Retry with the same Idempotency-Key.
SERVICE_UNAVAILABLE
503, retry. This style cannot run on a live key right now (test keys still return a sample). Or the job queue is briefly unavailable: nothing was created or charged; retry after Retry-After with the same Idempotency-Key.
A job that ends without a result (failed, timeout) is not an HTTP error: its error.code is listed under Statuses on the Jobs API page.
Updated 16 Oct 2026 · Change history (4)