Skip to content

Errors

View .md

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

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-After header is present, wait at least that long.
  • On POST /generations, always retry with the same Idempotency-Key and 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.

When several problems apply at once, the first one in this order wins:

  1. The key: invalid_api_key, then api_key_revoked, then api_key_expired, then api_key_disabled, then insufficient_scope
  2. The account: account_suspended, or account_pending_review for live keys
  3. rate_limited
  4. idempotency_key_required, then the idempotency lookup (idempotency_in_progress, idempotency_key_mismatch, or a replay)
  5. The body: invalid_request, then invalid_input, then webhook_url_rejected
  6. The catalogue: effect_not_found, then effect_disabled, then variant_unavailable
  7. The inputs: file_not_found and image_*, then input_rejected (or upstream_unavailable if checks cannot run)
  8. queue_full
  9. insufficient_balance
  10. 202 Accepted

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.

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), both starting_after and ending_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, not Bearer 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_at is 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_at is 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_at is 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 need generations:read or :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 pending account calls POST /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 suspended or rejected (all routes except GET /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_concurrent running and queue_cap queued 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
variant_unavailable422Variant unavailable
When
The variant combination is not offered for this effect (param: variant.duration etc.)
Retry
no
What to do
Pick a variant listed in the effect's variants
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_url fetch 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_url not 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 /generations without Idempotency-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 succeeded or failed
Retry
no
What to do
Nothing to do
Returned by
POST /v1/generations/{generation_id}/cancel
upstream_unavailable503Upstream unavailableRetryable
When
Input screening, storage, or GPU control plane unreachable before anything was held
Retry
yes
What to do
Retry with backoff and the same key
Returned by
POST /v1/generations
internal_error500Internal errorRetryable
When
Unhandled exception
Retry
yes
What to do
Retry with backoff and the same key; quote request_id if 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