Errors
Every error is application/problem+json (RFC 9457) with three extra members: code, request_id and, when one field is to blame, param.
{ "type": "https://api.moan.hot/docs/errors#input_rejected", "title": "Input rejected", "status": 422, "detail": "The input could not be processed. You were not charged.", "code": "input_rejected", "param": "inputs.image", "request_id": "req_2b7xk1v0p9q3s4"}| Member | Meaning |
|---|---|
code |
The stable machine-readable code. Branch on this. Each code maps to exactly one HTTP status |
type |
A link to this code’s entry on this page |
title |
Fixed text per code |
status |
The HTTP status, repeated |
detail |
Human-readable text that may change. Show it in logs; never parse it |
param |
The request field at fault, such as variant.duration |
errors[] |
For invalid_input: one {param, message} per bad field |
request_id |
Also in the X-Request-Id header. Quote it when you contact support |
When to retry
Section titled “When to retry”Retry only codes marked Retryable below: rate_limited, queue_full, idempotency_in_progress, upstream_unavailable and internal_error.
- Back off exponentially: 1 s, 2 s, 4 s and so on, capped at 60 s, with random jitter.
- When a
Retry-Afterheader is present, wait at least that long. - On
POST /generations, always retry with the sameIdempotency-Keyand body, so a retry can never create a second generation.
Everything else needs a change to the request, the key or the account before it can succeed.
Order of checks on POST /generations
Section titled “Order of checks on POST /generations”When several problems apply at once, the first one in this order wins:
- The key:
invalid_api_key, thenapi_key_revoked, thenapi_key_expired, thenapi_key_disabled, theninsufficient_scope - The account:
account_suspended, oraccount_pending_reviewfor live keys rate_limitedidempotency_key_required, then the idempotency lookup (idempotency_in_progress,idempotency_key_mismatch, or a replay)- The body:
invalid_request, theninvalid_input, thenwebhook_url_rejected - The catalogue:
effect_not_found, theneffect_disabled, thenvariant_unavailable - The inputs:
file_not_foundandimage_*, theninput_rejected(orupstream_unavailableif checks cannot run) queue_fullinsufficient_balance202 Accepted
Generation failures are not HTTP errors
Section titled “Generation failures are not HTTP errors”A generation that fails after 202 ends with status: "failed" and an error.code of input_rejected, gpu_failure, timeout, upstream_unavailable, internal_error or effect_retired. Its hold is released and you are not charged. See Generations.
Every code
Section titled “Every code”Any route can also answer route_not_found (unknown path or method) and invalid_request (malformed body).
invalid_request400Invalid request- When
- Body is not valid JSON / multipart, wrong
Content-Type, unknown top-level field (additionalProperties: false), bothstarting_afterandending_before - Retry
- no
- What to do
- Fix the request shape
- Returned by
POST /v1/filesPOST /v1/generations
route_not_found404Route not found- When
- Unknown path or method under
/v1 - Retry
- no
- What to do
- Check the URL and method against the reference
invalid_api_key401Invalid API key- When
- No
Authorization, notBearer mh_(live|test)_…, or the key hash is unknown - Retry
- no
- What to do
- Send a valid key from the dashboard
- Returned by
GET /v1/accountGET /v1/effectsGET /v1/effects/{slug}POST /v1/filesGET /v1/files/{file_id}GET /v1/generationsPOST /v1/generationsGET /v1/generations/{generation_id}POST /v1/generations/{generation_id}/cancelGET /v1/pricesGET /v1/usage
api_key_expired401API key expired- When
expires_atis in the past- Retry
- no
- What to do
- Create a new key; rotate before expiry
- Returned by
GET /v1/accountGET /v1/effectsGET /v1/effects/{slug}POST /v1/filesGET /v1/files/{file_id}GET /v1/generationsPOST /v1/generationsGET /v1/generations/{generation_id}POST /v1/generations/{generation_id}/cancelGET /v1/pricesGET /v1/usage
api_key_revoked401API key revoked- When
revoked_atis set- Retry
- no
- What to do
- Use another key
- Returned by
GET /v1/accountGET /v1/effectsGET /v1/effects/{slug}POST /v1/filesGET /v1/files/{file_id}GET /v1/generationsPOST /v1/generationsGET /v1/generations/{generation_id}POST /v1/generations/{generation_id}/cancelGET /v1/pricesGET /v1/usage
api_key_disabled403API key disabled- When
- The key was disabled in the dashboard (
disabled_atis set); reversible - Retry
- no
- What to do
- Enable the key in the dashboard, or use another key
- Returned by
GET /v1/accountGET /v1/effectsGET /v1/effects/{slug}POST /v1/filesGET /v1/files/{file_id}GET /v1/generationsPOST /v1/generationsGET /v1/generations/{generation_id}POST /v1/generations/{generation_id}/cancelGET /v1/pricesGET /v1/usage
insufficient_scope403Insufficient scope- When
- Key lacks the scope the route needs (
generations:write,generations:read,account:read; effects/prices/files needgenerations:reador:write) - Retry
- no
- What to do
- Create a key with the scope
- Returned by
GET /v1/accountGET /v1/effectsGET /v1/effects/{slug}POST /v1/filesGET /v1/files/{file_id}GET /v1/generationsPOST /v1/generationsGET /v1/generations/{generation_id}POST /v1/generations/{generation_id}/cancelGET /v1/pricesGET /v1/usage
account_pending_review403Account pending review- When
- Live key on a
pendingaccount callsPOST /generations. Test keys work while pending - Retry
- no
- What to do
- Wait for review; integrate in test mode meanwhile
- Returned by
POST /v1/generations
account_suspended403Account suspended- When
- Account is
suspendedorrejected(all routes exceptGET /account) - Retry
- no
- What to do
- Contact support
- Returned by
GET /v1/effectsGET /v1/effects/{slug}POST /v1/filesGET /v1/files/{file_id}GET /v1/generationsPOST /v1/generationsGET /v1/generations/{generation_id}POST /v1/generations/{generation_id}/cancelGET /v1/pricesGET /v1/usage
insufficient_balance402Insufficient balance- When
- Live create:
available_micros< variant price - Retry
- no (until topped up)
- What to do
- Top up, then retry with the same key and body
- Returned by
POST /v1/generations
rate_limited429Rate limitedRetryable- When
- Per-key requests-per-minute window exhausted
- Retry
- yes, after
Retry-After - What to do
- Back off; spread requests
- Returned by
GET /v1/accountGET /v1/effectsGET /v1/effects/{slug}POST /v1/filesGET /v1/files/{file_id}GET /v1/generationsPOST /v1/generationsGET /v1/generations/{generation_id}POST /v1/generations/{generation_id}/cancelGET /v1/pricesGET /v1/usage
queue_full429Queue fullRetryable- When
- Account has
max_concurrentrunning andqueue_capqueued already - Retry
- yes, after
Retry-After - What to do
- Back off, or request a higher limit
- Returned by
POST /v1/generations
effect_not_found404Effect not found- When
- Unknown slug, or not B2B-enabled, or retired past
retires_at - Retry
- no
- What to do
- Refresh the catalogue (
GET /effects) - Returned by
GET /v1/effects/{slug}POST /v1/generations
effect_disabled403Effect disabled- When
- Effect exists but the account disabled it (or auto-enable is off and it is unreviewed)
- Retry
- no
- What to do
- Enable it in the dashboard
- Returned by
POST /v1/generations
invalid_input422Invalid input- When
- Schema-valid JSON with a bad value: missing required slot, metadata over limits, bad cursor, bad date range;
errors[]lists fields - Retry
- no
- What to do
- Fix the fields named in
errors - Returned by
GET /v1/effectsGET /v1/generationsPOST /v1/generationsGET /v1/usage
file_not_found404File not found- When
file_id unknown, other account or mode, or past its 24 h TTL- Retry
- no
- What to do
- Upload again
- Returned by
GET /v1/files/{file_id}POST /v1/generations
image_unreadable422Image unreadable- When
- Bytes do not decode as JPEG/PNG/WebP, zero-size, below min side, or the
image_urlfetch failed / was not an image - Retry
- no
- What to do
- Send a valid image
- Returned by
POST /v1/filesPOST /v1/generations
image_too_large413Image too large- When
- Over 20 MB or over 8192 px long side
- Retry
- no
- What to do
- Downscale
- Returned by
POST /v1/filesPOST /v1/generations
unsupported_media_type415Unsupported media type- When
- Image format other than JPEG/PNG/WebP (HEIC, GIF, ...)
- Retry
- no
- What to do
- Convert
- Returned by
POST /v1/filesPOST /v1/generations
input_rejected422Input rejected- When
- The input could not be processed. No reason is exposed. Never charged (hold released)
- Retry
- no
- What to do
- Use a different input
- Returned by
POST /v1/generations
webhook_url_rejected422Webhook URL rejected- When
webhook_urlnot https, too long, literal private IP, or one of our own addresses- Retry
- no
- What to do
- Use a public https URL
- Returned by
POST /v1/generations
idempotency_key_required400Idempotency key required- When
POST /generationswithoutIdempotency-Key- Retry
- no
- What to do
- Mint a UUID per logical create
- Returned by
POST /v1/generations
idempotency_key_mismatch409Idempotency key reused- When
- Same account and mode+key within 24 h, different body hash
- Retry
- no
- What to do
- Use a new key for a different request
- Returned by
POST /v1/generations
idempotency_in_progress409Request in progressRetryable- When
- Same key while the first request is still executing
- Retry
- yes, after ~1 s
- What to do
- Retry with the same key; you will get the stored answer
- Returned by
POST /v1/generations
generation_not_found404Generation not found- When
- Unknown id, or other account or mode
- Retry
- no
- What to do
- Check the id and key mode
- Returned by
GET /v1/generations/{generation_id}POST /v1/generations/{generation_id}/cancel
generation_not_cancelable409Generation not cancelable- When
- Cancel on
succeededorfailed - Retry
- no
- What to do
- Nothing to do
- Returned by
POST /v1/generations/{generation_id}/cancel
internal_error500Internal errorRetryable- When
- Unhandled exception
- Retry
- yes
- What to do
- Retry with backoff and the same key; quote
request_idif it persists - Returned by
GET /v1/accountGET /v1/effectsGET /v1/effects/{slug}POST /v1/filesGET /v1/files/{file_id}GET /v1/generationsPOST /v1/generationsGET /v1/generations/{generation_id}POST /v1/generations/{generation_id}/cancelGET /v1/pricesGET /v1/usage