Skip to content

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 ​

StatusMeaningWhat to do
200Successful response or stream openedRead the response; LLM SSE must finish with [DONE]
201Resource or realtime ticket createdStore the returned ID/ticket
202Audio processing or webhook delivery accepted for background processingPoll the returned resource, export or delivery
204Successful request with no bodyDo not parse JSON; follow the endpoint's update or deletion rules
400Malformed JSON, multipart, cursor, or idempotency keyCorrect the request
401Missing, invalid, expired, disabled, or revoked credentialReplace or refresh the credential
402Insufficient credits or API-key credit limitCheck the balance and the key's configured limit
403Scope, IP, or origin policy denied the requestChange the key policy; do not retry unchanged
404Resource or feature is not availableRefresh IDs/catalog; do not guess a route
409Idempotency, edit, or resource state conflictFollow the endpoint-specific recovery
413Body or audio is too largeUpload a smaller payload
415Media type is unsupportedUse a supported audio format
422Unsupported fields, model settings, or audioRead error.code and any error.fields; correct the request
429Rate or capacity limitWait for Retry-After when present
500Unexpected server errorRetry only with the same idempotency key
503Dependency outage or unresolved LLM usageInspect 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.

CapacityBound
Running developer LLM requests70/account across keys, models and API replicas; 256 total/replica
Unsettled LLM usage8/account/upstream model, counted separately from healthy running requests
LLM file uploads4/account, 32/replica
Queued/processing TTS or STT jobs70/account/product
Pending STT upload storage2 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 validation 422 without changing the request;
  • an idempotency conflict with the same changed payload;
  • a permanently revoked key;
  • any error explicitly marked retryable:false;
  • llm_model_temporarily_unavailable or legacy llm_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.

StatusCodeRecovery
402insufficient_creditsAdd enough account credits for the request
402api_key_credit_limitCheck the key limit, recorded billable usage, and pending Voice Isolator reservations
404voice_isolation_disabled, not_foundCheck feature availability and account ownership
409idempotency_conflictReuse the original input for a retry; use a fresh UUID for changed audio, title, or options
409audio_not_readyContinue polling before requesting an export
413audio_too_largeReduce the uploaded audio size
422invalid_audio, invalid_speech_restoration, invalid_restoration_model, invalid_export_formatCorrect the audio or options described in the endpoint reference
429queue_fullWait for capacity and respect Retry-After
503voice_isolation_unavailable, speech_restoration_unavailableRetry 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_at for temporary or employee-owned integrations.
  • Set allowed_ips for fixed server egress addresses.
  • Keep restrict_key and auto_disable_if_leaked enabled 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_id when 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-enrollments and /v1/voices/{id} for API-key integrations;
  • /api/v1/voice-isolations* dashboard JWT routes; use the documented /v1/voice-isolations developer 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.