Migrating Text-to-Speech from ElevenLabs to Sarvam

View as Markdown

This guide covers migrating ElevenLabs 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 voice_id for speaker, add a required target_language_code, and change your response handling: ElevenLabs 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 ElevenLabs migration, so they’re folded in here rather than in a separate page:

ElevenLabsSarvam
Base URLhttps://api.elevenlabs.iohttps://api.sarvam.ai
Auth headerxi-api-keyapi-subscription-key
Auth failure status401403

With that, replace the synthesis call and add the required language code:

1from elevenlabs import ElevenLabs
2
3client = ElevenLabs(api_key="YOUR_ELEVENLABS_API_KEY")
4
5audio = client.text_to_speech.convert(
6 voice_id="21m00Tcm4TlvDq8ikWAM",
7 model_id="eleven_multilingual_v2",
8 text="Welcome to our platform!",
9 voice_settings={
10 "stability": 0.5,
11 "similarity_boost": 0.75,
12 "style": 0.0,
13 "use_speaker_boost": True,
14 "speed": 1.0,
15 },
16)
17
18with open("output.mp3", "wb") as f:
19 for chunk in audio:
20 f.write(chunk)

ElevenLabs’ Python and JavaScript SDKs hand back an iterable/stream of audio chunks you can write directly. 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.

ElevenLabsSarvam
Synchronous synthesisPOST /v1/text-to-speech/{voice_id}POST /text-to-speech
HTTP streamingPOST /v1/text-to-speech/{voice_id}/streamPOST /text-to-speech/stream
WebSocket streamingGET wss://.../v1/text-to-speech/{voice_id}/stream-inputGET /text-to-speech/ws
Voice discoveryGET /v2/voices (paginated, searchable)No API call needed; speaker names are a fixed list on the Voices page

Parameter concept map

ElevenLabsSarvamNote
voice_id (opaque ID, e.g. 21m00Tcm4TlvDq8ikWAM)speaker (plain lowercase name, e.g. shubh, priya)No lookup call required, pick a name directly from the Voices page
model_id (eleven_multilingual_v2, eleven_flash_v2_5, eleven_v3, etc.)model (bulbul:v2 or bulbul:v3)Two model versions total, not a family of speed/quality tiers
voice_settings.stabilityBuilt into the voiceEvery Bulbul speaker is pre-tuned for consistent delivery, so there’s no separate dial to calibrate
voice_settings.similarity_boostNot neededSarvam speakers are production-ready studio voices rather than per-request clones, so there’s nothing to tune
voice_settings.styleSpeaker choicePick the speaker whose baseline delivery already matches the expressiveness you want; see Voices
voice_settings.use_speaker_boostNot neededClarity is tuned in by default on every speaker
voice_settings.speed (default 1.0)pace (default 1.0, range 0.5 to 2.0 on v3, 0.3 to 3.0 on v2)Same idea, wider range on Sarvam’s side
language_code (optional, most models infer language from voice/model)target_language_code (required on every request)Guarantees the right pronunciation and prosody on every call, which matters most for code-mixed Indian-language text
output_format (single string, e.g. mp3_44100_128)speech_sample_rate + output_audio_codec (separate params, available on REST and streaming)Defaults to WAV at 24000 Hz; set output_audio_codec (mp3, opus, flac, aac, wav, etc.) and speech_sample_rate independently to change either
pronunciation_dictionary_locatorsdict_idBoth are scoped and reusable across requests, format differs, see Pronunciation dictionary below
Response: raw binary audio (application/octet-stream or per output_format)Response: JSON, audio as base64 strings in audiosStructured response makes it straightforward to pair audio with per-request metadata; decode .audios[0] before writing to disk

pitch and loudness give you fine-grained tone and volume control on bulbul:v2. On bulbul:v3, which adds a larger voice set and stronger code-mixed handling, tone comes from speaker choice instead, so pick a speaker whose baseline delivery already fits rather than reaching for these two params.

Streaming

ElevenLabsSarvam
HTTP streamingPOST /v1/text-to-speech/{voice_id}/streamHTTP Stream API (POST /text-to-speech/stream): both return audio progressively over one request, no persistent connection
WebSocket.../stream-input: initial message carrying voice_settings, then SendText messages (optional flush flag), closed with an empty-text messageWebSocket API: ws.configure(...) once, then repeated ws.convert(text) and ws.flush() on the same connection
Character limitsA single large call, e.g. up to 10,000 characters on eleven_multilingual_v2WebSocket up to 2500 chars/message (under 500 recommended for lowest latency), REST up to 2500 on bulbul:v3 (1500 on bulbul:v2), HTTP streaming up to 3500 regardless of model — may need chunking logic that didn’t previously exist

Pronunciation dictionary

Both platforms let you fix mispronounced brand names and acronyms without retraining a model, but the mechanism differs:

ElevenLabsSarvam
Rule typesPhoneme rules (IPA) and word-alias rulesWord-alias (plain text substitution), simple to author and audit
ScopeReferenced via pronunciation_dictionary_locators in the requestReferenced via a single dict_id per request
Language handlingRules can be tied to specific words regardless of languageRules are nested per target_language_code; only entries matching the request’s language apply
LimitsVaries by plan10 dictionaries per user, 100 words per dictionary, 1 MB per upload, 1 dictionary per request
Model supportAvailable across ElevenLabs’ model tiersbulbul:v3 only: a request can’t combine a pronunciation dictionary with the v2-only pitch/loudness params

If your ElevenLabs dictionary relies on IPA phoneme rules for precise pronunciation control, plan to rewrite it: Sarvam dictionaries use plain word-to-word text replacements (e.g. "NAIC": "N A I C"), which are simpler to author and audit than phonetic transcriptions. 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.
  • 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 where auto-detection is prone to guessing wrong.
  • A simpler tuning surface. Tone and delivery come from picking the right speaker plus pace (and pitch/loudness on v2), instead of calibrating stability, similarity_boost, style, and use_speaker_boost separately. Fewer knobs, less trial and error to land on a voice that sounds right.
  • 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. If your product needs those languages, this is the reason to migrate, not just a side effect of it.
  • Instant voice discovery. Every speaker is listed with audio previews on the Voices page: no API call, no pagination, nothing to query.

Full example

A small wrapper function, before and after, showing the complete set of changes together:

1from elevenlabs import ElevenLabs
2
3client = ElevenLabs(api_key="YOUR_ELEVENLABS_API_KEY")
4
5def synthesize(text: str, out_path: str):
6 audio = client.text_to_speech.convert(
7 voice_id="21m00Tcm4TlvDq8ikWAM",
8 model_id="eleven_multilingual_v2",
9 text=text,
10 voice_settings={"stability": 0.5, "similarity_boost": 0.75},
11 )
12 with open(out_path, "wb") as f:
13 for chunk in audio:
14 f.write(chunk)

Common mistakes

The single most common migration bug. ElevenLabs’ 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.

It’s required on every request, by design: this is what guarantees Sarvam applies the correct pronunciation and prosody instead of guessing from the speaker name. Include it from the start rather than treating it as optional.

pace and pitch aren’t drop-in replacements for stability or style. Rather than compensating with those params, pick a speaker whose baseline delivery already matches what you need; see the Voices page for per-speaker tone descriptions.

Both params are accepted syntactically but only take effect on bulbul:v2. If output sounds unchanged after setting them, confirm which model version the request is actually using.

Sarvam’s dictionary format is plain word-to-word substitution, not phoneme rules. An ElevenLabs dictionary built on IPA transcriptions needs to be rewritten as text replacements, not just re-uploaded.

See also