Last updated 2026-05-17

Cartesia Provider

The cartesia provider implements Speaker (text-to-speech) only. Cartesia's Sonic family is built around real-time conversational synthesis with first-token latencies in the ~40 ms range — choose this provider when end-to-end latency is the critical constraint.

Import path

import (
"github.com/elloloop/llmrouter"
"github.com/elloloop/llmrouter/providers/cartesia"
)

Default endpoint

With no WithBaseURL override, the provider targets https://api.cartesia.ai. TTS requests go to {BaseURL}/tts/bytes for non-streaming and {BaseURL}/tts/sse for streaming (SSE).

WebSocket streaming (wss://api.cartesia.ai/tts/websocket) is on the v0.4 roadmap; v0.3 streams via SSE which is sufficient for most server-side pipelines.

Construction

p, err := cartesia.New(
llmrouter.WithAPIKey(os.Getenv("CARTESIA_API_KEY")),
)
if err != nil {
log.Fatal(err)
}

Authentication is via the X-API-Key header. Cartesia also requires a Cartesia-Version header pinned to a stable API version; the provider sets a sensible default (currently 2024-11-13) and will be bumped as Cartesia publishes new versions.

Models

  • sonic-2 — current production model. 15 languages, sub-100 ms first-token latency, the recommended default.
  • sonic-2-2025-03-07 — pinned snapshot of sonic-2. Use this when you need reproducible output.
  • sonic-turbo — even lower latency for English-only use cases. Slightly lower quality.

Voice IDs

Cartesia voices are referenced by UUID-shaped IDs. Pass the ID string in SpeechRequest.Voice. A handful of public voice IDs from Cartesia's library (subject to change):

Voice ID
Newsmand46abd1d-2d02-43e8-819f-51fb652c1c61
Casual British woman156fb8d2-335b-4950-9cb3-a2d33befec77
Friendly Australian man421b3369-f63f-4b03-8980-37a44df1d4e8

For custom-trained voices, use the voice ID returned by Cartesia's voice-cloning endpoint. The library does not wrap voice management — call the Cartesia REST API directly for that.

Output format mapping

Cartesia uses an output_format object with container, encoding, and sample_rate. The library maps SpeechRequest.Format as follows:

Library Format Cartesia output_format
mp3 (default){container:"mp3", sample_rate:44100, bit_rate:128000}
wav{container:"wav", encoding:"pcm_s16le", sample_rate:22050}
pcm{container:"raw", encoding:"pcm_s16le", sample_rate:22050}
opus{container:"webm", encoding:"opus", sample_rate:48000}

For custom sample rates or bit depths, set Format="" and pass the full output_format object via Raw.

Streaming TTS example

package main
import (
"context"
"log"
"os"
"github.com/elloloop/llmrouter"
"github.com/elloloop/llmrouter/providers/cartesia"
)
func main() {
p, err := cartesia.New(llmrouter.WithAPIKey(os.Getenv("CARTESIA_API_KEY")))
if err != nil {
log.Fatal(err)
}
stream, err := p.Speak(context.Background(), llmrouter.SpeechRequest{
Model: "sonic-2",
Input: "Cartesia streams audio with sub-100 millisecond latency.",
Voice: "d46abd1d-2d02-43e8-819f-51fb652c1c61",
Format: "mp3",
Stream: true,
})
if err != nil {
log.Fatal(err)
}
out, _ := os.Create("cartesia.mp3")
defer out.Close()
for chunk := range stream.Chunks() {
out.Write(chunk.Data)
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
log.Printf("wrote cartesia.mp3 (%s)", stream.ContentType)
}

Real-time use cases

Cartesia's defining property is first-token latency low enough for barge-in voice agents. The end-to-end pattern looks like:

  1. Chat provider emits a delta chunk.
  2. You buffer until a sentence boundary or N characters.
  3. You call Speak with Stream=true and pipe the audio to the client (WebRTC / WebSocket / phone).
  4. If the user interrupts, cancel both the chat Stream and the audio AudioStream via context.

Cancellation propagates: cancelling the audio context closes the Cartesia HTTP request, which stops billing immediately.

Speed and emotion

SpeechRequest.Speed maps to Cartesia's speed field (range -1.0 to 1.0; the library rescales 0.5..2.0 to that range). Emotion controls are not on the typed surface; pass them via Raw:

raw := []byte(`{
"voice": {
"mode": "id",
"id": "d46abd1d-2d02-43e8-819f-51fb652c1c61",
"__experimental_controls": {
"emotion": ["positivity:high", "curiosity"],
"speed": "normal"
}
}
}`)
stream, err := p.Speak(ctx, llmrouter.SpeechRequest{
Model: "sonic-2",
Input: "What a wonderful evening!",
Raw: raw,
})

Error handling

Non-2xx responses surface as *llmrouter.ErrUpstream with Provider == "cartesia". Common cases:

  • 401 — bad or missing API key.
  • 402 — quota exhausted.
  • 422 — invalid voice ID or unsupported language/model pair.
  • 400 — invalid output_format.

Caveats

  • No STT. Cartesia is TTS-only. Pair with Deepgram or ElevenLabs Scribe for transcription.
  • WebSocket streaming lands in v0.4. The SSE path is sufficient for server-side use; WebSocket matters for browser-direct streaming where every millisecond of handshake counts.
  • API version header. Cartesia versions its API via a header rather than a path prefix. The provider sets the header; if Cartesia ships a breaking change, bump llmrouter.

See also