Skip to content
LUNAROUTEDocs

Errors & rate limits

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 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.

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).

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.

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.

  • 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-After on 429.