Theme
Errors, limits, and security
Read error codes, handle rate limits, and retry without duplicating paid work.
Error envelope
Standard JSON errors follow this shape:
json
{
"message": "A user-safe explanation.",
"error": {
"code": "stable_machine_code",
"fields": {
"language": "Choose a supported language."
}
},
"request_id": "req_01J..."
}Use error.code in program logic. error.fields is optional. Message wording can change; do not parse it for error handling.
LLM handler errors have a different message location:
json
{
"error": {
"message": "LLM usage needs reconciliation. Contact support before making another request.",
"type": "request_error",
"code": "llm_usage_pending"
},
"request_id": "req_example"
}Shared authentication errors can still use the standard envelope. LLM clients must handle both. After SSE starts, an error can arrive inside a data event while HTTP status remains 200. See LLM errors.
Common HTTP statuses
| Status | Meaning | What to do |
|---|---|---|
200 | Successful response or stream opened | Read the response; LLM SSE must finish with [DONE] |
201 | Resource or realtime ticket created | Store the returned ID/ticket |
202 | Audio processing or webhook delivery accepted for background processing | Poll the returned resource, export or delivery |
204 | Successful request with no body | Do not parse JSON; follow the endpoint's update or deletion rules |
400 | Malformed JSON, multipart, cursor, or idempotency key | Correct the request |
401 | Missing, invalid, expired, disabled, or revoked credential | Replace or refresh the credential |
402 | Insufficient credits or API-key credit limit | Check the balance and the key's configured limit |
403 | Scope, IP, or origin policy denied the request | Change the key policy; do not retry unchanged |
404 | Resource or feature is not available | Refresh IDs/catalog; do not guess a route |
409 | Idempotency, edit, or resource state conflict | Follow the endpoint-specific recovery |
413 | Body or audio is too large | Upload a smaller payload |
415 | Media type is unsupported | Use a supported audio format |
422 | Unsupported fields, model settings, or audio | Read error.code and any error.fields; correct the request |
429 | Rate or capacity limit | Wait for Retry-After when present |
500 | Unexpected server error | Retry only with the same idempotency key |
503 | Dependency outage or unresolved LLM usage | Inspect the code; llm_model_temporarily_unavailable / legacy llm_usage_pending require support, otherwise follow endpoint retry rules |
Do not blindly retry POST requests without an idempotency key. For TTS, STT, and Voice Isolator uploads, retries after a timeout should reuse the same key and identical input, including processing options.
For LLM completions, identical retries with the same key return 409 request_already_submitted and X-LLM-Request-ID. No response is replayed and no new generation starts. Read the original request's status. A changed body with the same key returns 409 idempotency_conflict. See LLM retry rules.
Rate limits and backoff
LLM and current REST TTS/STT admission allows 70 requests per second, with a burst of 70, per account on each API replica. Product reads and submissions share their product's account budget. Multiple keys do not multiply it. This is an HTTP admission limit, not 70 completed model generations per second or a cluster-wide quota.
| Capacity | Bound |
|---|---|
| Running developer LLM requests | 70/account across keys, models and API replicas; 256 total/replica |
| Unsettled LLM usage | 8/account/upstream model, counted separately from healthy running requests |
| LLM file uploads | 4/account, 32/replica |
| Queued/processing TTS or STT jobs | 70/account/product |
| Pending STT upload storage | 2 GiB/account |
X-RateLimit-Policy distinguishes rps, concurrency and queue limits. LLM model discovery also returns current rate and concurrency values in limits. Provider capacity and available credits still apply; simultaneous requests require enough credits for their reservations. Voice enrollment has separate account abuse limits.
The exact account policy can vary. When the response includes Retry-After, wait at least that many seconds. For retryable errors, otherwise use exponential backoff with jitter, for example 1s, 2s, 4s, then stop after a small number of attempts.
Do not retry:
401,403,404, or validation422without changing the request;- an idempotency conflict with the same changed payload;
- a permanently revoked key;
- any error explicitly marked
retryable:false; llm_model_temporarily_unavailableor legacyllm_usage_pending, which requires support reconciliation rather than waiting for a cooldown.
Voice Isolator errors and limits
Voice Isolator accepts up to 300 MiB of uploaded audio, or that combined total for a batch of 1–4 files. Each file must contain at least 0.5 seconds of decodable audio. There is no separate maximum recording duration; decode resource budgets and processing timeouts still apply.
| Status | Code | Recovery |
|---|---|---|
402 | insufficient_credits | Add enough account credits for the request |
402 | api_key_credit_limit | Check the key limit, recorded billable usage, and pending Voice Isolator reservations |
404 | voice_isolation_disabled, not_found | Check feature availability and account ownership |
409 | idempotency_conflict | Reuse the original input for a retry; use a fresh UUID for changed audio, title, or options |
409 | audio_not_ready | Continue polling before requesting an export |
413 | audio_too_large | Reduce the uploaded audio size |
422 | invalid_audio, invalid_speech_restoration, invalid_restoration_model, invalid_export_format | Correct the audio or options described in the endpoint reference |
429 | queue_full | Wait for capacity and respect Retry-After |
503 | voice_isolation_unavailable, speech_restoration_unavailable | Retry after Retry-After; changed processing options require a new idempotency key |
A job with status=completed may still have a pending MP3 export. Wait for audio_status=completed and audio_url before downloading the default result. Refresh the job to get fresh signed URLs, and do not send an API key to storage URLs. Deleting a job only hides it from history; it does not erase stored audio or cancel accepted work.
API-key security checklist
- Store keys in a server-side secret manager.
- Give each service its own key and the smallest permissions it needs.
- Set
expires_atfor temporary or employee-owned integrations. - Set
allowed_ipsfor fixed server egress addresses. - Keep
restrict_keyandauto_disable_if_leakedenabled unless you have a documented reason not to. - Revoke a leaked key immediately; do not wait for expiration.
- Do not put API keys in Git, Docker images, frontend JavaScript, URLs, query strings, analytics events, or exception messages.
- Treat signed audio URLs and realtime tickets as secrets too.
- Keep
request_idwhen reporting a failure, but never include credentials in support logs.
Ownership and privacy
Every API-key request is owner-scoped. A key from account A cannot read or delete account B's TTS generations, STT transcriptions, voices, or audio.
Audio URLs are short-lived and private. Download the object when needed rather than storing the URL permanently. The API never returns provider credentials, internal model names, raw upstream responses, or account IDs in normal product responses.
What is not a developer API yet
These dashboard and internal routes do not accept developer API keys:
/api/v1/account/*product/profile and dashboard voice-cloning workflows, except the API-key management and dashboard analytics endpoints documented here; use developer Voice Cloning at/v1/voice-enrollmentsand/v1/voices/{id}for API-key integrations;/api/v1/voice-isolations*dashboard JWT routes; use the documented/v1/voice-isolationsdeveloper API instead;/api/v1/llm/*and/api/v1/chats/*JWT-only Ask routes; use the LLM developer API for server integrations;/api/v1/voice-agent/*website/LiveKit voice-agent session routes;/api/v1/public-preview/*anonymous preview routes;- retired TTS job aliases and retired subtitle list/download aliases;
- arbitrary LiveKit token generation.
Use the documented /v1 routes for product integrations. Undocumented routes and legacy proxy behavior can change without notice.