Errors & limits
Every failure returns the same JSON shape with a stable machine-readable code. Branch on code, show message, log request_id.
Error shape
{
"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
invalid_request A field is missing or malformed. The message names the field. unauthorized Missing, malformed or revoked key. quota_exhausted Free characters are used up and no payment method is on file. payload_too_large Audio upload exceeded the size limit for a single request. unsupported_media Audio container or codec could not be decoded. rate_limited Per-key rate limit hit. Retry after the interval in the header. internal_error Our fault. Safe to retry with backoff; quote the request id. 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:
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:
402means 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.
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.