# 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.

**Bytes**

```python
import os

from cartesia import Cartesia

client = 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")
```

**WebSocket**

```python
import asyncio
import os

from cartesia import AsyncCartesia


async 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](https://ttscacherouter.dev/dashboard/provider-keys). For a LiveKit agent, see [Cartesia with LiveKit](https://docs.ttscacherouter.dev/livekit/cartesia.md#provider-plugin).

## 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 after `max_buffer_delay_ms` without more text.
- Word timestamps run on across a context, as Cartesia’s do.
- `add_phoneme_timestamps` is refused with an error for that context.
- `cancel` drops the context’s sentences not yet sent to Cartesia; one being synthesized finishes and is cached. You get `done` at 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’s `output_format`, with Cartesia’s own `Content-Type`.
- `POST /cartesia/tts/sse`: Cartesia’s server-sent events, `chunk` and `timestamps` then `done`.
- `GET /cartesia/voices` and `GET /cartesia/voices/{id}`: Cartesia’s voice library and your own voices, never cached. [GET /v1/voices](https://docs.ttscacherouter.dev/reference/voices.md) lists them in the router’s own shape.

Any other path under `/cartesia` answers Cartesia’s `404 page not found`.

**Bytes**

```shell
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.mp3
```

**SSE**

```shell
curl -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}'
```

**Voices**

```shell
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](https://docs.ttscacherouter.dev/clients/stream.md) when the settings are the same.
- A request with `pronunciation_dict_id` is 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
HTTP/1.1 401 Unauthorized

{"error_code": null, "message": "Invalid API key.", "request_id": null, "title": "Unauthorized"}
```
