/tts/bytes, /tts/sse) use a four-field envelope; /voices/clone uses a shorter two-field shape.
Error Format
/voices/clone uses the shorter { "error": "...", "message": "..." } shape (no
error_code/detail). 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 instead of a silent done — 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.
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)
A single request can synthesize at most ~46.5 seconds of audio — the generator’s hard ceiling. The limit is on predicted spoken duration, not character count: the estimate is digit-aware and per-language, and a digit is spoken as a whole word (4 → “four”), so number-heavy text hits the limit with far less text. As a rough en-US guide, that’s ~900 characters of normal prose but only ~130 digits (phone numbers, amounts, codes, dates). The exact threshold shifts slightly by voice language. The check runs before synthesis, so nothing is billed on a rejection.
On /tts/bytes and /tts/sse this is an HTTP 400 (on SSE it’s returned before the event stream opens, as a normal HTTP body — not an in-stream event):
continue: true, keeping each turn under the limit.
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). This endpoint uses the shorter { error, message } shape.
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,22050are allowed) container: "wav"on a streaming endpoint (/tts/sse,/tts/websocketareraw-only)
payment_required (402)
The organization’s credit balance can’t cover the request.
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.