/tts/bytes, /tts/sse) use a four-field envelope; /voices/clone uses two shorter shapes (see Voice cloning errors).
Error Format
/voices/clone returns { "detail": "..." } for audio validation errors and
{ "error": "...", "message": "..." } for everything else. WebSocket errors arrive as a
Cartesia-shaped error frame with error_code,
status_code, context_id, and done: true.Error Code Reference
Errors
unauthorized (401)
The API key is missing, malformed, invalid, or revoked.
Authorization: Bearer sk_996_... (or ?api_key= for WebSockets).
missing_transcript (400)
The transcript is empty, whitespace-only, or (on WebSocket) omitted. On the WebSocket a message that omits transcript returns this error frame and the socket closes — but an empty string with continue: false is the valid end-of-turn flush marker, not an error.
missing_voice / missing_voice_id / unknown_voice_mode (400)
The voice field is required and must be the nested object { "mode": "id", "id": "<voice-id>" }. There is no default voice — a request that doesn’t resolve to a voice is rejected (it is no longer silently substituted). A top-level voice_id is not supported. On the WebSocket these arrive as an error frame and the socket closes.
voice_not_found (404)
The voice.id doesn’t exist or belongs to another organization.
GET /voices and pass an id your org owns.
input_too_long (400)
transcript is longer than 2,000 characters. The limit applies per /tts/bytes or /tts/sse request and per WebSocket message. Characters are counted the same way as for billing — Unicode code points of the string as sent, including leading and trailing whitespace and newlines — so you can check before sending (len(text) in Python, [...text].length in JavaScript; see Length limit). The check runs before any work starts, so nothing is billed.
On /tts/bytes and /tts/sse it’s an HTTP 400 (on SSE, before the event stream opens):
error_code: "input_too_long", status_code: 400). The over-long message isn’t synthesized or billed, and the socket and your other contexts carry on. If that message would have ended a turn already in progress, the turn still completes with its earlier sentences and sends done.
Fix: Split longer text across multiple requests, or send it over the WebSocket as several messages with continue: true.
payload_too_large (413)
On /tts/bytes and /tts/sse, the request body exceeds the 64 KB (65536-byte) cap. Normal requests are nowhere near this — it’s a safety limit. Uses the shorter { error, message } shape.
unknown_model (400)
The model_id is not a supported model. Send gepard-1.0.
invalid_language (400)
On POST /voices/clone, the language form field is not a well-formed BCP-47 tag. Send the full dialect (e.g. en-US, en-GB, es-MX, pt-BR, nl-NL); malformed tags (en_US, en-USA, english_, 123) are rejected. The value is canonicalized on save (en-us → en-US). Uses the { error, message } shape.
Voice cloning errors
POST /voices/clone returns errors in two shapes.
Audio validation errors use { "detail": "..." }:
{ "error": "...", "message": "..." }:
To handle both, read
error if it’s present and fall back to detail.
Invalid or malformed body (400)
The request body isn’t valid JSON, or a required field (model_id, transcript, voice, output_format) is missing.
Invalid output_format (400)
The output_format uses an unsupported container, encoding, or sample_rate. Validation is strict — there’s no silent fallback. The message names the offending field.
Common causes:
- Removed formats:
container: "mp3",encoding: "pcm_f32le" - Unsupported
sample_rate(only8000,16000,22050,24000,44100,48000are allowed) container: "wav"on a streaming endpoint (/tts/sse,/tts/websocketareraw-only)
payment_required (402)
The organization’s balance is zero or below. Billing is postpaid: new requests start only while the balance is positive, so the last request before you run out can take it slightly below zero. On the WebSocket this arrives as an insufficient_credits (402) frame and the socket closes — including mid-stream, if the balance reaches zero while you’re streaming.
rate_limited / concurrent_limit (429)
You’ve exceeded your org’s per-minute request rate or concurrency cap (shared across all your API keys). See Rate Limits.
Fix: Honor the Retry-After header and retry. For concurrent_limit, retry the rejected turn on a fresh context_id — only that context was rejected, not the whole connection.
overloaded (429)
The synthesis servers are at capacity. This is separate from your tier’s concurrent_limit: it can happen even when you’re well under your own limits.
Retry-After header for this error.
engine_error (502) / engine_unavailable (503)
engine_error means generation failed; engine_unavailable means the synthesis engine is temporarily unavailable. For both, the message is passed through from the engine and varies (for example, All connection attempts failed for engine_unavailable), so don’t match on it.
engine_error is usually 502, but it can carry a 4xx status passed through from the engine. Key on error_code, not the HTTP status.
engine_unavailable, wait a moment first.
Generation errors by endpoint
overloaded, engine_error, and engine_unavailable arrive differently depending on the endpoint:
-
/tts/bytes— a normal HTTP error with the four-field envelope above./tts/bytesis all-or-nothing, so a failed request returns no audio and isn’t charged. -
/tts/sse— either a normal HTTP error before the stream opens, or mid-stream as anerrorevent inside the HTTP 200 response. The stream ends after theerrorevent, with nodone, so handleerrorevents as well aschunkanddone. If at least one audio chunk was delivered before the error, the request is charged for the full transcript. -
/tts/websocket— an error frame for the affected context. The socket stays open; that context’s queued sentences are dropped and other contexts keep running. Text not yet billed for that turn isn’t charged; 1,000-character batches already billed stay charged. Retry the turn on a freshcontext_id.
missing_context_id (400) / context_not_found (404)
WebSocket only, and only on connections opened with ?cancel_ack=true. A cancel without a context_id returns missing_context_id; a cancel for a context that isn’t active (unknown, already finished, or already cancelled) returns context_not_found. Both arrive as error frames and the socket stays open.
If a cancel races a context that finishes on its own, you’ll get the normal done followed by context_not_found — safe to ignore. See WebSocket Streaming.