Other clients
Cartesia API drop-in
Cartesia's own text-to-speech API, bytes, SSE and WebSocket, through the cache: keep Cartesia's SDK, change the base URL and key.
Cartesia's Python SDK#
Cartesia’s text-to-speech API, mirrored under https://api.ttscacherouter.dev/cartesia. Point Cartesia’s SDK at the router and use your router API key as the API key.
import osfrom cartesia import Cartesiaclient = Cartesia( api_key=os.environ["TTS_CACHE_ROUTER_API_KEY"], base_url="https://api.ttscacherouter.dev/cartesia",)response = client.tts.generate( model_id="sonic-3", transcript="Your order has shipped. It arrives on Friday.", voice={"mode": "id", "id": "f786b574-daa5-4673-aa0c-cbe3e8534c02"}, language="en", output_format={"container": "wav", "encoding": "pcm_s16le", "sample_rate": 24000},)response.write_to_file("speech.wav")import asyncioimport osfrom cartesia import AsyncCartesiaasync def main() -> None: client = AsyncCartesia( api_key=os.environ["TTS_CACHE_ROUTER_API_KEY"], websocket_base_url="wss://api.ttscacherouter.dev/cartesia", ) pcm = bytearray() async with client.tts.websocket_connect() as connection: ctx = connection.context( model_id="sonic-3", voice={"mode": "id", "id": "f786b574-daa5-4673-aa0c-cbe3e8534c02"}, language="en", output_format={"container": "raw", "encoding": "pcm_s16le", "sample_rate": 24000}, ) await ctx.push("Your order has shipped. ") await ctx.push("It arrives on Friday.") await ctx.no_more_inputs() async for response in ctx.receive(): if response.type == "chunk" and response.audio: pcm += response.audio elif response.type == "done": break with open("speech.pcm", "wb") as f: f.write(pcm)asyncio.run(main())The SDK takes the router in base_url (bytes, SSE and voices) and websocket_base_url. generate, generate_sse and websocket_connect work through the router. Requests, responses, messages and errors are Cartesia’s own, sent to Cartesia with the Cartesia key stored under Provider keys. For a LiveKit agent, see Cartesia with LiveKit.
WebSocket#
wss://api.ttscacherouter.dev/cartesia/tts/websocket speaks Cartesia’s WebSocket protocol: contexts by context_id, answered with chunk, timestamps, flush_done and done messages.
Send your router API key as Cartesia’s clients send theirs, and the API version in Cartesia-Version or the cartesia_version query parameter, as at Cartesia. Contexts run side by side on one connection, each with its own settings, and a connection with no message for 300 seconds is closed, as at Cartesia.
The router splits each context’s text into sentences and serves each sentence from the cache or synthesizes it at Cartesia, so the messages are Cartesia’s with these differences:
- Each sentence is a take of its own: it doesn’t continue from the audio of the sentence before, as text in one Cartesia context does, and the pauses between sentences are shorter.
- With
max_buffer_delay_ms: 0(as LiveKit’s plugin sends it), each message’s text is synthesized as it comes. With buffering, a sentence is synthesized once it ends, and a trailing fragment aftermax_buffer_delay_mswithout more text. - Word timestamps run on across a context, as Cartesia’s do.
add_phoneme_timestampsis refused with an error for that context.canceldrops the context’s sentences not yet sent to Cartesia; one being synthesized finishes and is cached. You getdoneat once.- Cartesia checks a context’s settings when its first sentence is synthesized, so an error for a bad setting comes then. A context served entirely from the cache isn’t checked.
Bytes, SSE and voices#
POST /cartesia/tts/bytes: the audio in the body’soutput_format, with Cartesia’s ownContent-Type.POST /cartesia/tts/sse: Cartesia’s server-sent events,chunkandtimestampsthendone.GET /cartesia/voicesandGET /cartesia/voices/{id}: Cartesia’s voice library and your own voices, never cached. GET /v1/voices lists them in the router’s own shape.
Any other path under /cartesia answers Cartesia’s 404 page not found.
curl "https://api.ttscacherouter.dev/cartesia/tts/bytes" \ -H "X-API-Key: $TTS_CACHE_ROUTER_API_KEY" \ -H "Cartesia-Version: 2026-08-14" \ -H "Content-Type: application/json" \ -d '{"model_id": "sonic-3", "transcript": "Your order has shipped.", "voice": {"mode": "id", "id": "f786b574-daa5-4673-aa0c-cbe3e8534c02"}, "language": "en", "output_format": {"container": "mp3", "sample_rate": 44100, "bit_rate": 128000}}' \ --output speech.mp3curl -N "https://api.ttscacherouter.dev/cartesia/tts/sse" \ -H "X-API-Key: $TTS_CACHE_ROUTER_API_KEY" \ -H "Cartesia-Version: 2026-08-14" \ -H "Content-Type: application/json" \ -d '{"model_id": "sonic-3", "transcript": "Your order has shipped.", "voice": {"mode": "id", "id": "f786b574-daa5-4673-aa0c-cbe3e8534c02"}, "language": "en", "output_format": {"container": "raw", "encoding": "pcm_s16le", "sample_rate": 24000}, "add_timestamps": true}'curl "https://api.ttscacherouter.dev/cartesia/voices?limit=20" \ -H "X-API-Key: $TTS_CACHE_ROUTER_API_KEY" \ -H "Cartesia-Version: 2026-08-14"What is cached#
- Bytes and SSE: each request as a whole. The same body again replays the same audio (on SSE, whatever its
context_id). - WebSocket: each sentence, so a sentence you have sent before with the same settings comes from the cache whatever came around it. Sentences share the cache with the router’s own /v1/stream when the settings are the same.
- A request with
pronunciation_dict_idis cached by the dictionary’s id: if you edit the dictionary, entries cached before keep the old pronunciation until your cache retention expires them.
Errors#
Errors come in the shape of your API version: Title: Message text before 2026-03-01, JSON with error_code, title, message and request_id from it on. A missing or wrong router key gets Cartesia’s own refusal, HTTP 401, at the WebSocket handshake too. An org with no Cartesia key stored gets the same, with a message that links to the dashboard. Errors from Cartesia, such as an unknown voice, reach you as Cartesia sent them; on the WebSocket, as an error message for that context, and the connection stays open.
HTTP/1.1 401 Unauthorized{"error_code": null, "message": "Invalid API key.", "request_id": null, "title": "Unauthorized"}