Migrating Text-to-Speech from Cartesia to Sarvam
Migrating Text-to-Speech from Cartesia to Sarvam
This guide covers migrating Cartesia text-to-speech API calls to Sarvam. If you’re building a full voice agent rather than replacing individual TTS calls, start from the LiveKit or Pipecat voice agent build guides instead: they aren’t a 1:1 API swap and aren’t covered here.
In short: swap the voice object for a plain speaker name, drop the nested output_format object for two flat params, and change your response handling: Cartesia returns raw audio bytes, Sarvam returns JSON with base64-encoded audio in an audios array. Sarvam’s structured response is what makes it easy to pair audio with per-request metadata; that decode step is the change most migrations look for first.
Quick start
Base URL and auth header change the same way for every Cartesia migration, so they’re folded in here rather than in a separate page:
With that, replace the synthesis call and add the required language code:
Before (Cartesia)
After (Sarvam)
Cartesia’s generate() returns a binary response with a write_to_file() (Python) or arrayBuffer() (JavaScript) helper. Sarvam’s convert() returns a JSON object; the audio lives in response.audios as a list of base64 strings, one per input text. Join and decode before writing to disk, or use the Python SDK’s sarvamai.play.save() helper, which does this for you.
The REST response is JSON with a base64 audios array, not a file. Decode .audios[0] before writing it out; see the REST API guide for the full decode snippet.
The rest of this page covers the full endpoint and parameter mapping, streaming, pronunciation dictionaries, and common mistakes.
Endpoint map
The tables below aren’t a checklist to apply field-by-field: Quick Start above already shows the whole call swapped over in one go. Use these tables as a reference for what changed and why, not as a sequence of edits to make yourself.
Parameter concept map
Streaming
Pronunciation dictionary
Both platforms use plain text-to-alias substitution rather than phonetic transcription, so the underlying concept carries over directly:
Since both dictionaries are simple text replacements, porting entries over is mostly a data migration, not a rewrite: copy each text/alias pair into Sarvam’s format and nest it under the right target_language_code. See the Pronunciation Dictionary guide for the full format and upload flow.
What’s different by design
- Ready-to-use voices, no cloning step. Sarvam ships a curated, named set of 30+ Bulbul v3 voices, professionally tuned and ready to call immediately, with no per-request cloning or training step to manage.
- Plain speaker names, no voice object. Sarvam’s
speakeris a lowercase name you pick straight from the Voices page, rather than a{"mode": "id", "id": "<uuid>"}object you construct or fetch from/voicesfirst. - Flat output settings.
speech_sample_rateandoutput_audio_codecare top-level params rather than a nestedoutput_formatobject, so there’s less request-building code either way. - One continuous pace control.
paceworks the same way acrossbulbul:v2andbulbul:v3, rather than switching between aslow/normal/fastenum on older models and a continuous0.6-1.5float on newer ones. - Explicit language targeting. Every Sarvam request carries
target_language_code, so pronunciation and prosody are correct by design on every call, especially valuable for Hindi, Hinglish, and other code-mixed Indian-language text. - Indian language depth is Sarvam’s specific strength. Sarvam covers Hindi plus 9 other Indian languages (Bengali, Tamil, Telugu, Kannada, Malayalam, Marathi, Gujarati, Punjabi, Odia), natively tuned for code-mixed input like Hinglish, alongside dedicated
verbatim,translit, andcodemixmodes on the speech-to-text side.
Full example
A small wrapper function, before and after, showing the complete set of changes together:
Before (Cartesia)
After (Sarvam)
Common mistakes
Writing the response body directly as an audio file
The single most common migration bug. Cartesia’s REST response is the audio file itself; Sarvam’s is JSON. Code that does f.write(response.content) unmodified will produce a file full of JSON text, not audio. Decode response.audios[0] from base64 first.
Forgetting target_language_code
It’s required on every request, by design: this is what guarantees Sarvam applies the correct pronunciation and prosody instead of relying on the optional, best-effort language Cartesia infers from the voice. Include it from the start rather than treating it as optional.
Looking for a named-emotion equivalent
There isn’t a direct swap for generation_config.emotion, and that’s fine: pick a speaker whose baseline delivery already matches what you need instead of trying to force pace to compensate; see Voices for per-speaker tone descriptions.
Assuming pronunciation dictionaries work on bulbul:v2
Like Cartesia’s own sonic-3 restriction, Sarvam’s dict_id only applies on bulbul:v3. If you migrate a dictionary-dependent request onto bulbul:v2, the dictionary is silently ignored.
Porting a dated model_id pin as-is
Cartesia’s dated pins (e.g. sonic-3-2026-01-12) don’t have a Sarvam equivalent to match one-to-one. Map them to the corresponding model family instead: any sonic-3* pin to bulbul:v3, any older pin to bulbul:v2.
See also
- Migration overview: base URL, auth, and SDK client changes
- Migrating Speech-to-Text
- Which Text-to-Speech API to Use
- TTS Best Practices
- Voices
- Pronunciation Dictionary
- Errors & Troubleshooting