Migrating Speech-to-Text from ElevenLabs to Sarvam

View as Markdown

This guide covers migrating ElevenLabs speech-to-text (transcription) API calls to Sarvam. If you’re building a full voice agent rather than transcribing audio directly, 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 model_id for model, upload the audio file directly instead of passing a source_url, and pick the right Sarvam API for the job: REST for short synchronous requests, Batch for long-form or multi-speaker audio, and WebSocket for real-time streaming.

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 synchronous transcription call:

1from elevenlabs import ElevenLabs
2
3client = ElevenLabs(api_key="YOUR_ELEVENLABS_API_KEY")
4
5with open("audio.mp3", "rb") as f:
6 transcription = client.speech_to_text.convert(
7 file=f,
8 model_id="scribe_v2",
9 language_code="eng",
10 tag_audio_events=True,
11 )
12
13print(transcription.text)

with_diarization and num_speakers are Batch API parameters. For a request like this one under 30 seconds, use the REST call above; for diarized or longer audio, use the Batch workflow below.

The rest of this page covers the full endpoint and parameter mapping, diarization via the Batch API, streaming, 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 transcriptionPOST /v1/speech-to-textPOST /speech-to-text (up to 30 seconds of audio per request)
Long-form or multi-speaker transcriptionPOST /v1/speech-to-text with webhook: trueBatch API: POST /speech-to-text/job/v1 (create), upload, start, then poll or receive a webhook callback, up to 2 hours per file and 20 files per job
Real-time streaming transcriptionwss://.../v1/speech-to-text/realtimeGET /speech-to-text/ws

Parameter concept map

ElevenLabsSarvamNote
model_id (scribe_v1 or scribe_v2 for REST and webhook requests; scribe_v2_realtime is a separate value used only on the WebSocket)model (saaras:v3)One current model across REST, Batch, and Streaming, rather than a different model_id per endpoint
file or source_url (fetch directly from a hosted URL)file (upload the audio/video file directly)Download the source once, then upload it: a single, predictable step for every request
language_code (optional, ISO-639-1/3, e.g. eng)language_code (optional, BCP-47, e.g. ta-IN; pass unknown to auto-detect)Same idea; unknown gives you the same auto-detect behavior as leaving ElevenLabs’ language_code unset
diarize (boolean)with_diarization (boolean, Batch API)Same concept; on Sarvam this is a Batch job parameter alongside num_speakers
tag_audio_events (boolean, tags laughter/applause, default true)Not applicableSarvam transcripts contain spoken words only, so there’s nothing to toggle for non-speech event tags
webhook (boolean flag on the same endpoint)callback (url + auth_token, set when creating a Batch job)Async processing lives in the Batch API rather than a flag on the sync endpoint; same idea, one dedicated workflow for longer audio
Response: text + words[] (word-level timing, speaker IDs)Response: transcript + diarized_transcript.entries[] (Batch, with speaker_id and segment timing)Field names differ; see the timestamps note below for granularity

Sarvam’s Batch API returns timestamps per chunk (sentence or phrase segment), which is precise enough for subtitle alignment and audio navigation. If your ElevenLabs integration reads individual words[].start/end for word-by-word highlighting, adjust it to work at the chunk level instead.

Diarization and long-form audio

ElevenLabs’ /v1/speech-to-text has no separate mode for this; Sarvam’s Batch API handles both:

1from elevenlabs import ElevenLabs
2
3client = ElevenLabs(api_key="YOUR_ELEVENLABS_API_KEY")
4
5transcription = client.speech_to_text.convert(
6 file=open("meeting.mp3", "rb"),
7 model_id="scribe_v2",
8 diarize=True,
9 num_speakers=3,
10 webhook=True,
11)

See the Batch API guide for webhook setup and its Speaker Diarization section for the current per-job speaker limit.

Streaming

ElevenLabsSarvam
Connection configRealtime WebSocket (wss://.../v1/speech-to-text/realtime): model_id: scribe_v2_realtime, language_code, audio_formatWebSocket API (GET /speech-to-text/ws): model="saaras:v3", mode, language_code
Streamed eventspartial_transcript events, then committed_transcript (or committed_transcript_with_timestamps) once a segment finalizesTranscript for your chosen mode directly, plus START_SPEECH/END_SPEECH events when vad_signals=true
Finalizing a segmentcommit flag on the audio chunk, or VAD commit_strategyflush_signal plus an explicit ws.flush() call, or automatic end-of-speech detection via high_vad_sensitivity

What’s different by design

  • One current model, not a version matrix. ElevenLabs’ scribe_v1, scribe_v2, and scribe_v2_realtime become a single saaras:v3, used the same way across REST, Batch, and Streaming.
  • Direct file upload, no third-party fetch. Sarvam transcribes the file you send it. Download the source once (from a URL, cloud storage, or elsewhere) and upload it directly, so every request starts from the same predictable input.
  • Diarization and long-form audio share one workflow. The Batch API handles speaker diarization, files up to 2 hours, and multiple files per job in a single job-based flow, using the same with_diarization/num_speakers parameters whether the request is a short call or a large batch.
  • A consistent response shape. Whether diarization is on or off, Sarvam always returns the same transcript (plus diarized_transcript when enabled), rather than switching structure based on a multichannel or output-style flag.
  • Indian language depth is Sarvam’s specific strength. Saaras v3 covers 22 Indian languages, including Assamese, Konkani, Kashmiri, Sindhi, Sanskrit, Santali, Manipuri, Bodo, Maithili, and Dogri, with dedicated verbatim, translit, and codemix output modes for code-mixed speech like Hinglish.

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 transcribe(path: str) -> str:
6 with open(path, "rb") as f:
7 transcription = client.speech_to_text.convert(
8 file=f,
9 model_id="scribe_v2",
10 language_code="eng",
11 tag_audio_events=True,
12 )
13 return transcription.text

Common mistakes

Sarvam’s file parameter expects an open file object or byte stream, not a URL string. If your ElevenLabs code used source_url to pull audio from a hosted link, download it once and upload the local file instead.

with_diarization is a Batch API parameter, not something the synchronous REST endpoint accepts. If you need diarized output, send the request through the Batch workflow, even for a short file.

Batch API timestamps cover sentence or phrase chunks, not individual words. If your ElevenLabs integration builds word-by-word highlighting from words[].start/end, adjust it to highlight at the chunk level instead.

Calling the Batch endpoints directly over HTTP (rather than through the SDK) requires nesting parameters under job_parameters, e.g. {"job_parameters": {"model": "saaras:v3", ...}}. A flat body modeled on ElevenLabs’ request shape won’t be picked up.

There’s only one current-generation model, saaras:v3, across every endpoint. Older saarika/saaras:v2.5 model names you’ll see elsewhere in Sarvam’s docs are legacy and being phased out, not a version choice to replicate.

See also