Errors & rate limits
Error envelope
Section titled “Error envelope”Errors use a consistent envelope:
{ "error": { "code": "INVALID_API_KEY", "message": "Invalid or revoked API key" } }Anthropic-dialect requests are not uniform: most errors (including auth
failures) use the envelope above, while some Anthropic-shaped errors wrap it as
{ "type": "error", "error": { … } }.
Authentication
lists the auth codes.
Audio preprocessing: field and reason
Section titled “Audio preprocessing: field and reason”Audio preprocessing adds two optional members to
the envelope. Both appear only when they apply — never as null, and never on
errors from other endpoints:
{ "error": { "code": "invalid_option", "message": "an option is not valid for this request", "field": "/level/target_lufs", "reason": "out_of_range" }}field is the JSON pointer to the option that was refused, so you can fix the
request without guessing. reason is a stable token naming the rule that
failed: out_of_range, unsupported_value, mutually_exclusive, duplicate,
too_many, duplicate_member, unknown_field or invalid_type. Neither
member ever echoes the value you sent.
| Status | error.code |
Meaning |
|---|---|---|
| 422 | invalid_option |
An option is out of range, contradicts the selected mode or format, or is not in the schema. Carries field and reason. |
| 422 | feature_not_available |
A reserved value the schema accepts but v1 does not implement (denoise.engine="deepfilternet", an explicit analysis.* = true). Carries field. |
| 406 | not_acceptable |
A present Accept excludes the media type of the selected output.format. |
| 409 | pipeline_version_unavailable |
The pinned LUNAROUTE-PIPELINE-VERSION is not installed. No silent fallback. |
| 413 | output_too_large |
The produced audio would exceed the output cap. No audio is returned. |
The endpoint’s full code table, including its 413/415/422 input refusals, is on Audio preprocessing.
Rate limits (429)
Section titled “Rate limits (429)”LunaRoute meters concurrent requests, not requests per second; see
Concurrency & overflow. When your organization is at its
concurrency allowance, a request does not
fail immediately — it parks and waits for a free slot (up to 60 seconds for
priority requests). You get a 429 only if the queue is full or the park
deadline expires. Both cases return the same 429 with Retry-After.
Other 429s come from capacity further down the stack, not your concurrency
allowance:
embedding_gateway_busy and embedding_backend_busy mean the embedding gateway
or backend is overloaded, and systemone_backend_busy means the System One
upstream is rate-limiting. Treat them the same way — back off and retry.
embedding_backend_busy is returned only after we have waited out a short
backend overload ourselves — twice, when the request has enough deadline left
for the wait and for another attempt like the one that was refused — so
retrying after its Retry-After is worth doing rather than dropping the batch.
Every 429 carries a Retry-After header telling you how long to back off
before retrying, except the two BYOK spend-cap refusals (BYOK_SPEND_CAP_REACHED
and MODEL_UNPRICED): a plain retry does not help until the cap resets, is
raised, or the model gets a price (a smaller max_tokens may fit under the cap). See Model policy & spend caps.
| Surface | error.code |
Retry-After |
|---|---|---|
| Chat, Messages, Responses | CONCURRENT_REQUEST_LIMIT_EXCEEDED |
60 |
| Image generation | concurrent_request_limit_exceeded |
60 |
| System One | systemone_gate_busy |
1 |
| System One — upstream rate limit | systemone_backend_busy |
1 |
| Embeddings / rerank — org concurrency | embedding_lanes_exhausted |
1 |
| Embeddings / rerank — gateway busy | embedding_gateway_busy |
1 |
| Embeddings / rerank — backend overloaded | embedding_backend_busy |
1 |
| Audio preprocessing — org concurrency | audio_lanes_exhausted |
1 |
| Chat, Messages, Responses (BYOK) | BYOK_SPEND_CAP_REACHED |
— |
| Chat, Messages, Responses (BYOK) | MODEL_UNPRICED |
— |
Image generation shares the priority pool, so a refusal there
carries the same flat 60 as chat. System One draws on its own pool
(systemone_gate_busy, never priority or overflow capacity) and, like the
embedding pool, refuses with a flat 1.
A 409 idempotency_in_progress (image generation) means another attempt with
the same idempotency key is already running; it carries Retry-After set to the
remaining lease (1–60 seconds).
Retryable 5xx
Section titled “Retryable 5xx”Some outages are transient and answer 503. Treat the HTTP status as the
signal — for an upstream failure the body’s shape varies (the provider’s own
error JSON, a re-wrapped message, or a generic envelope) and it often carries no
error.code at all.
| Surface | error.code |
Retry-After |
|---|---|---|
| Upstream model provider temporarily unavailable (a serving target shedding load) | varies — often absent | only if the provider sent one |
| Image normalization unavailable | image_normalization_unavailable |
30 |
| Embeddings / rerank billing plane | embeddings_billing_unavailable |
— |
| BYOK key store (chat) | PROVIDER_KEY_UNAVAILABLE |
— |
| Chat, Messages, Responses (BYOK) | BYOK_SPEND_GATE_UNAVAILABLE |
— |
Treat 503 as “temporarily unavailable”, not “not found” — feature kill switches
answer 503 <feature>_disabled rather than removing the route.
The upstream-provider 503 is the retryable one you are most likely to meet: a
serving target is shedding load. Retrying is safe and cannot double-bill you — a
503 releases the attempt’s billing reservation, and only a request that streams
or completes is billed. The upstream can rarely have started work before
shedding; that cost is ours, not yours. Pace retries with your client library’s
backoff — an absent Retry-After is normal here and is not a terminal
signal.
A 503 while no in-house target is usable (INFERENCE_UNAVAILABLE,
NO_HEALTHY_PROVIDER) carries the same status and the same advice: retry, don’t
fail permanently.
PROVIDER_KEY_UNAVAILABLE (orgs bringing their own provider key) means the key
store lookup failed or did not complete in time. The request was rejected
before any generation started — nothing was billed and no usage was
recorded — so it is always safe to retry immediately.
Upstream provider errors
Section titled “Upstream provider errors”If a model provider rate-limits or refuses a request, the error may carry a
cause field, for example:
{ "error": { "code": "…", "message": "…", "cause": "rate_limited" } }cause is not always present — where it is absent, treat the HTTP status as the
signal (see Retryable 5xx above).
Causes are rate_limited, content_policy_violation, and
model_context_length_exceeded. rate_limited means the provider is
throttling — back off and retry, or route to a different provider or model.
content_policy_violation and model_context_length_exceeded need a different
prompt or model. All three are distinct from your own concurrency allowance.
Sizing clients
Section titled “Sizing clients”- Give clients a timeout comfortably above 60 seconds — the priority park deadline is exactly 60s, so a client that times out at 60s may be racing the gate.
- Keep a parallel worker pool no larger than your priority concurrency allowance,
and honor
Retry-Afteron429.