> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cartesia.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Caching Audio for Stock Responses

> Pre-generate stock TTS phrases as raw PCM, then interleave them with live WebSocket audio to cut latency and credits

## Goal

Stock phrases (greetings, hold messages, sign-offs) repeat across calls. Regenerating them every time adds latency and costs credits. Instead, pre-generate those phrases once as raw PCM, cache them, and splice them into a live TTS stream at runtime. Cached clips skip the API, so those segments are faster and free.

## Before you start

You need:

* **API key** — Get your API key from [play.cartesia.ai](https://play.cartesia.ai). If you're new to Cartesia, see the [Realtime TTS Quickstart](/get-started/realtime-text-to-speech-quickstart).
* **Raw container** — Set `container: "raw"` (headerless PCM). Containers like WAV or MP3 add headers that break concatenation. See [TTS Output Format](/build-with-cartesia/capability-guides/tts-output-audio-format).
* **Matching encoding and sample rate** — Cached clips must match your live stream exactly (e.g., `pcm_s16le` at 24000 Hz).
* **Matching model, voice, and language** — Pin a dated model snapshot in production (e.g., `sonic-3.5-2026-05-04`) so cached and live audio sound identical.

Cached and live audio must be byte-compatible so they concatenate without clicks or distortion.

## Project setup

To follow along with this guide, initialize a python `uv` project called `tts-caching` as follows:

```bash theme={null}
uv init tts-caching
cd tts-caching
uv venv
source .venv/bin/activate
uv add 'cartesia[websockets]'
export CARTESIA_API_KEY=sk_...
```

## Step 1: Build the phrase cache

Generate the audio phrase cache with the below script. It generates each phrase in `PHRASES` and saves the raw audio PCM bytes in a `phrase-cache` directory.

```python build_cache.py theme={null}
from cartesia import Cartesia
import os

CACHE_DIR = "phrase-cache"
MODEL = "sonic-3.5"  # Pin a snapshot in production: "sonic-3.5-2026-05-04"
VOICE_ID = "47c38ca4-5f35-497b-b1a3-415245fb35e1" # Choose a voice that suits your use case at play.cartesia.ai
OUTPUT_FORMAT = {"container": "raw", "encoding": "pcm_s16le", "sample_rate": 24000} # this should match the audio format that you use in "live" TTS generation during production

CUSTOMER_NAME="Elon"

# Stock phrases to cache.
# If you need to create custom word pronunciations for your brand
# see https://docs.cartesia.ai/build-with-cartesia/capability-guides/custom-pronunciations
PHRASES = {
    "greeting": "Thanks for calling Acme support. How can I help you today?",
    "hold": f"Please hold while I look that up for you {CUSTOMER_NAME}.",
    "closing": "Is there anything else I can help you with?",
}

os.makedirs(CACHE_DIR, exist_ok=True)

client = Cartesia(api_key=os.environ["CARTESIA_API_KEY"])

# Generate the TTS audio phrases.
with client.tts.websocket_connect() as ws:
    for phrase_name, phrase in PHRASES.items():
        ctx = ws.context(
            model_id=MODEL,
            voice={"mode": "id", "id": VOICE_ID},
            output_format=OUTPUT_FORMAT,
            language="en",
        )
        ctx.push(phrase)
        ctx.no_more_inputs()

        audio = bytearray()
        for response in ctx.receive():
            if response.type == "chunk" and response.audio:
                audio += response.audio
            elif response.type == "done":
                break

        path = os.path.join(CACHE_DIR, f"{phrase_name}.raw")
        with open(path, "wb") as f:
            f.write(audio)
        print(f"Cached '{phrase_name}': {len(audio)} bytes -> {path}")
```

Run once:

```bash theme={null}
uv run build_cache.py
```

## Step 2: Interleave at runtime

At runtime, process a sequence of cached and live phrases into one output buffer.

```python interleave_tts.py theme={null}
from cartesia import Cartesia
import os

client = Cartesia(api_key=os.environ["CARTESIA_API_KEY"])

# Must match the configs in build_cache.py
CACHE_DIR = "phrase-cache"
MODEL = "sonic-3.5"
VOICE_ID = "47c38ca4-5f35-497b-b1a3-415245fb35e1"
OUTPUT_FORMAT = {"container": "raw", "encoding": "pcm_s16le", "sample_rate": 24000}

CUSTOMER_NAME="Elon"

def load_cached_raw_file(name: str) -> bytes:
    with open(os.path.join(CACHE_DIR, f"{name}.raw"), "rb") as f:
        return f.read()


def call_llm(index: int, context: str = "") -> str:
    """In production: call your reasoning LLM with conversation context.
    For demo, return hardcoded responses."""
    responses = [
        "Ok, sure. Let me pull up order number AKQ 4245 for you.",
        f"Ok! Good news {CUSTOMER_NAME}. Your order shipped yesterday and should arrive tomorrow.",
    ]
    return responses[index]


def call_cartesia_tts(ws, text: str) -> bytes:
    ctx = ws.context(
        model_id=MODEL,
        voice={"mode": "id", "id": VOICE_ID},
        output_format=OUTPUT_FORMAT,
        language="en",
    )
    ctx.push(text)
    ctx.no_more_inputs()

    audio = bytearray()
    for response in ctx.receive():
        if response.type == "chunk" and response.audio:
            audio += response.audio
        elif response.type == "done":
            break
    return bytes(audio)


# "cached" = load from local cache (zero credits), "live" = generate via Cartesia TTS (costs credits)
interleaved_sequence: list[dict[str, str]] = [
    {"cached": "greeting"},
    {"live": call_llm(0)},
    {"cached": "hold"},
    {"live": call_llm(1)},
    {"cached": "closing"},
]

output = bytearray()

# Concatenate the audio from the sequence of cached and live phrases.
with client.tts.websocket_connect() as ws:
    for item in interleaved_sequence:
        if "cached" in item:
            output += load_cached_raw_file(item["cached"])  # Zero credits
        else:
            output += call_cartesia_tts(ws, item["live"])  # Costs credits

# Write WAV so you can listen to the spliced PCM.
import wave
os.makedirs("./output", exist_ok=True)
with wave.open("./output/spliced.wav", "wb") as w:
    w.setnchannels(1)
    w.setsampwidth(2)  # 16-bit pcm_s16le
    w.setframerate(OUTPUT_FORMAT["sample_rate"])
    w.writeframes(output)

print(f"Wrote spliced.wav ({len(output)} bytes)")
```

Run it:

```bash theme={null}
uv run interleave_tts.py
```

Listen to `spliced.wav`. The cached phrases blend seamlessly with the live-generated audio.

### Design pattern

In `interleave_tts.py` the WebSocket stays open for the whole sequence. Only "live" phrases call Cartesia TTS. Cached phrases come from your store (disk, S3, anywhere), so you pay credits only for the live parts.

## Important things to note

* **Audio Format mismatch**: If cached clips use a different encoding or sample rate than live audio, you'll hear clicks or distortion at the joins.
* **Pin your model**: Using different TTS model versions can affect the voice generation even if it's the same voice ID. Pin a dated snapshot (eg `sonic-3.5-2026-05-04`) so cached and live audio stay in sync.
* **5-minute idle timeout**: TTS WebSockets [close after 5 minutes of inactivity](/use-the-api/concurrency-limits-and-timeouts#tts-websocket-timeouts). Don't disconnect just to play a cached clip; that only adds reconnect cost. If you have long gaps between live generations, close and reopen rather than holding the connection idle.
* **Full phrases splice cleanly**: Sonic TTS uses surrounding words as context for emotiveness and tone. Aim to make your TTS clips start and end as a complete sentence.
* **WebSocket pattern**: This guide uses TTS WebSocket, which keeps one connection open across multiple generations. The Bytes and SSE endpoints work one request per generation, so there is no open connection to interleave with. See [TTS endpoint types](/use-the-api/compare-tts-endpoints).
