Reference
API v1RESTWebSocket

Errors & limits

Every failure returns the same JSON shape with a stable machine-readable code. Branch on code, show message, log request_id.

Proposed: not yet implemented The only confirmed error behaviour today is that a rate-limited request returns JSON containing a message. The typed envelope, status-code table and retry matrix below are a proposal pending backend sign-off.

Error shape

4xx / 5xx
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded for this key. Retry in 1.2s.",
    "request_id": "req_8fc21ab4e9"
  }
}

code is stable and safe to branch on. message is human-readable and may change wording, so never parse it. Always log request_id; it is the fastest way for us to find a specific request.

Status codes

HTTPCodeMeaning
400 invalid_request A field is missing or malformed. The message names the field.
401 unauthorized Missing, malformed or revoked key.
402 quota_exhausted Free characters are used up and no payment method is on file.
413 payload_too_large Audio upload exceeded the size limit for a single request.
415 unsupported_media Audio container or codec could not be decoded.
429 rate_limited Per-key rate limit hit. Retry after the interval in the header.
500 internal_error Our fault. Safe to retry with backoff; quote the request id.
503 capacity Briefly out of capacity. Retry with backoff.

Rate limits

Limits are per key, not per account, so you can isolate a noisy workload by giving it its own key. Every response carries your current budget:

headers
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 594
X-RateLimit-Reset: 1755012800
Retry-After: 2

On a 429, wait for Retry-After seconds before trying again. Concurrency itself is not capped, because workers autoscale horizontally, so limits here are about request rate rather than how many streams you may hold open at once.

Retry guidance

  • Retry: 429, 500, 503. Use exponential backoff with jitter, starting near 500 ms.
  • Do not retry: 400, 401, 413, 415. These fail identically every time; fix the request.
  • Handle deliberately: 402 means the account needs a payment method. Surface it to a human rather than looping.
  • Cap total retries (three is usually right) so a real outage fails fast instead of stacking up latency.
python
import time, random, requests

RETRY = {429, 500, 503}

def post_with_retry(url, **kw):
    for attempt in range(3):
        r = requests.post(url, **kw)
        if r.status_code not in RETRY:
            return r
        wait = float(r.headers.get("Retry-After", 0.5 * 2 ** attempt))
        time.sleep(wait + random.random() * 0.2)
    return r

When something looks wrong

If you are seeing sustained 503s, or latency well outside the range you normally observe, send us a handful of request_id values. That is enough to trace the requests end to end. Availability and latency commitments will be published with their measurement method rather than as a bare figure.