> For clean Markdown of any page, append `.md` to the page URL.
> For a complete documentation index, see https://docs.sarvam.ai/llms.txt.
> For full documentation content in one file, see https://docs.sarvam.ai/llms-full.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.sarvam.ai/_mcp/server.

# Migrating Speech-to-Text from Deepgram to Sarvam

> Endpoint, parameter, and response-format differences between Deepgram's Nova Speech-to-Text API and Sarvam's Speech-to-Text API, with before/after code for REST, batch, and streaming transcription.

This guide covers migrating Deepgram 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](/api-reference-docs/integration/build-voice-agent-with-live-kit) or [Pipecat](/api-reference-docs/integration/build-voice-agent-with-pipecat) voice agent build guides instead: they aren't a 1:1 API swap and aren't covered here.

**In short:** swap `model` for `model`, read the transcript from a flatter response shape, and pick the right Sarvam API for the job: REST for short synchronous requests, Batch for diarized or very long audio, and WebSocket for real-time streaming.

## Quick start

Base URL and auth header change the same way for every Deepgram migration, so they're folded in here rather than in a separate page:

|                     | Deepgram                         | Sarvam                            |
| ------------------- | -------------------------------- | --------------------------------- |
| Base URL            | `https://api.deepgram.com`       | `https://api.sarvam.ai`           |
| Auth header         | `Authorization: Token <api_key>` | `api-subscription-key: <api_key>` |
| Auth failure status | `401`                            | `403`                             |

With that, replace the transcription call:

```python
from deepgram import DeepgramClient

client = DeepgramClient(api_key="YOUR_DEEPGRAM_API_KEY")

with open("audio.wav", "rb") as f:
    response = client.listen.v1.media.transcribe_file(
        request=f.read(),
        model="nova-3",
        language="en",
    )

print(response.results.channels[0].alternatives[0].transcript)
```

```javascript
import { DeepgramClient } from "@deepgram/sdk";
import { createReadStream } from "fs";

const client = new DeepgramClient({ apiKey: "YOUR_DEEPGRAM_API_KEY" });

const response = await client.listen.v1.media.transcribeFile(createReadStream("audio.wav"), {
  model: "nova-3",
  language: "en",
});

console.log(response.results.channels[0].alternatives[0].transcript);
```

```bash
curl -X POST "https://api.deepgram.com/v1/listen?model=nova-3&language=en" \
  -H "Authorization: Token YOUR_DEEPGRAM_API_KEY" \
  -H "Content-Type: audio/wav" \
  --data-binary @audio.wav
```

```python
from sarvamai import SarvamAI

client = SarvamAI(api_subscription_key="YOUR_SARVAM_API_KEY")

response = client.speech_to_text.transcribe(
    file=open("audio.wav", "rb"),
    model="saaras:v3",
    language_code="en-IN",
    mode="transcribe",
)

print(response.transcript)
```

```javascript
import { SarvamAIClient } from "sarvamai";
import fs from "fs";

const client = new SarvamAIClient({ apiSubscriptionKey: "YOUR_SARVAM_API_KEY" });

const response = await client.speechToText.transcribe({
  file: fs.createReadStream("audio.wav"),
  model: "saaras:v3",
  language_code: "en-IN",
  mode: "transcribe",
});

console.log(response.transcript);
```

```bash
curl -X POST https://api.sarvam.ai/speech-to-text \
  -H "api-subscription-key: YOUR_SARVAM_API_KEY" \
  -F model="saaras:v3" \
  -F language_code="en-IN" \
  -F mode="transcribe" \
  -F file=@audio.wav
```

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.

|                                   | Deepgram                                              | Sarvam                                                                                                                            |
| --------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Synchronous transcription         | `POST /v1/listen`                                     | `POST /speech-to-text` (up to 30 seconds per request; use Batch for longer audio)                                                 |
| Diarized or very long audio       | Same `POST /v1/listen` call, with `diarize_model` set | Batch API: `POST /speech-to-text/job/v1` (create), upload, start, then poll or receive a webhook callback, up to 2 hours per file |
| Real-time streaming transcription | `POST /v1/listen` (WebSocket upgrade)                 | `GET /speech-to-text/ws`                                                                                                          |

## Parameter concept map

| Deepgram                                                                | Sarvam                                                                                                 | Note                                                                                                              |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| `model` (`nova-3`, `nova-2`, and other version/domain variants)         | `model` (`saaras:v3`)                                                                                  | One current model to pass, not a family of version and domain-specific variants                                   |
| `request` (raw audio bytes, e.g. `open(...).read()`)                    | `file` (an open file object)                                                                           | Same idea; Sarvam takes the file handle directly rather than pre-reading it into bytes                            |
| `language` (BCP-47, e.g. `en`; optional)                                | `language_code` (BCP-47, e.g. `en-IN`; optional; pass `unknown` to auto-detect)                        | Same concept, region-qualified codes on Sarvam's side                                                             |
| `diarize_model` (enables diarization on the same synchronous call)      | `with_diarization` + `num_speakers` (Batch API only)                                                   | Same concept; on Sarvam, diarization is a Batch job parameter rather than a flag on the fast synchronous endpoint |
| `smart_format` / `punctuate` / `numerals` (formatting toggles)          | Handled automatically                                                                                  | Sarvam applies sensible formatting by default, so there's no separate set of toggles to configure                 |
| Response: `results.channels[0].alternatives[0].transcript` + `.words[]` | Response: `transcript` + `diarized_transcript.entries[]` (Batch, with `speaker_id` and segment timing) | Flatter response shape; no channel/alternative nesting to walk through                                            |

Deepgram's response also supports optional sentiment, topic, intent, and summary detection, and content redaction, layered on top of the transcript. Sarvam's Speech-to-Text focuses on transcription accuracy, output modes (`transcribe`, `translate`, `verbatim`, `translit`, `codemix`), and Indian-language coverage; if you need that kind of downstream text analysis, run Sarvam's transcript through [Chat Completion](/api-reference-docs/api-guides-tutorials/chat-completion/overview) as a second step.

## Diarization and long-form audio

Deepgram enables diarization on the same synchronous call via `diarize_model`; Sarvam's Batch API handles both diarization and long-form audio instead:

```python
from deepgram import DeepgramClient

client = DeepgramClient(api_key="YOUR_DEEPGRAM_API_KEY")

with open("meeting.wav", "rb") as f:
    response = client.listen.v1.media.transcribe_file(
        request=f.read(),
        model="nova-3",
        language="en",
        diarize_model="latest",
    )
```

```python
from sarvamai import SarvamAI

client = SarvamAI(api_subscription_key="YOUR_SARVAM_API_KEY")

job = client.speech_to_text_job.create_job(
    model="saaras:v3",
    mode="transcribe",
    language_code="en-IN",
    with_diarization=True,
    num_speakers=3,
)

job.upload_files(file_paths=["meeting.wav"])
job.start()
job.wait_until_complete()

file_results = job.get_file_results()
for f in file_results["failed"]:
    print(f"Failed: {f['file_name']}: {f['error_message']}")

if file_results["successful"]:
    job.download_outputs(output_dir="./output")
```

See the [Batch API guide](/api-reference-docs/api-guides-tutorials/speech-to-text/batch-api) for webhook setup and its [Speaker Diarization section](/api-reference-docs/api-guides-tutorials/speech-to-text/batch-api#speaker-diarization) for the current per-job speaker limit.

## Streaming

|                     | Deepgram                                                                                                            | Sarvam                                                                                                                                                             |
| ------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Connection config   | WebSocket upgrade of `/v1/listen`: `model`, `language`, `encoding`, `sample_rate`, `interim_results`, `endpointing` | [WebSocket API](/api-reference-docs/api-guides-tutorials/speech-to-text/streaming-api) (`GET /speech-to-text/ws`): `model`, `mode`, `language_code`, `sample_rate` |
| Server messages     | `Results`, `Metadata`, `UtteranceEnd`, `SpeechStarted`                                                              | Transcript for your chosen `mode` directly, plus `START_SPEECH`/`END_SPEECH` events when `vad_signals=true`                                                        |
| Turn-end detection  | `endpointing` (silence duration before finalizing) + `vad_events`                                                   | `high_vad_sensitivity` + `vad_signals`: one automatic "this turn just ended" signal                                                                                |
| Partial transcripts | `interim_results` flag                                                                                              | No separate flag; returns the transcript for your chosen `mode` directly as each segment finalizes                                                                 |
| Finalize / close    | `{"type": "Finalize"}` / `{"type": "CloseStream"}`                                                                  | `flush_signal` plus an explicit `ws.flush()` call / simply closing the connection                                                                                  |

Deepgram also offers **Flux** (`flux-general-en`/`flux-general-multi`), a separate `GET /v2/listen` endpoint purpose-built for conversational turn detection, with its own tuning params (`eager_eot_threshold`, `eot_threshold`). Sarvam's turn-detection features (`high_vad_sensitivity`, `vad_signals`) live in the same WebSocket API used for regular transcription, so there's no separate model or endpoint to choose between for conversational use.

## What's different by design

* **One current model, not a version-and-domain matrix.** Deepgram's `nova-3`, `nova-2-medical`, `nova-2-finance`, and similar domain-specific variants become a single `saaras:v3`, used the same way across REST, Batch, and Streaming.
* **A flatter response shape.** `response.transcript` reads directly, rather than walking `results.channels[0].alternatives[0].transcript`.
* **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, with the same `with_diarization`/`num_speakers` parameters either way.
* **Flexible output modes on one model.** `saaras:v3` supports `transcribe`, `translate`, `verbatim`, `translit`, and `codemix` modes on the same endpoint.
* **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 native handling for code-mixed speech like Hinglish.

## Full example

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

```python
from deepgram import DeepgramClient

client = DeepgramClient(api_key="YOUR_DEEPGRAM_API_KEY")

def transcribe(path: str) -> str:
    with open(path, "rb") as f:
        response = client.listen.v1.media.transcribe_file(
            request=f.read(),
            model="nova-3",
            language="en",
        )
    return response.results.channels[0].alternatives[0].transcript
```

```javascript
import { DeepgramClient } from "@deepgram/sdk";
import { createReadStream } from "fs";

const client = new DeepgramClient({ apiKey: "YOUR_DEEPGRAM_API_KEY" });

async function transcribe(path) {
  const response = await client.listen.v1.media.transcribeFile(createReadStream(path), {
    model: "nova-3",
    language: "en",
  });
  return response.results.channels[0].alternatives[0].transcript;
}
```

```bash
transcribe() {
  local path="$1"
  curl -sS -X POST "https://api.deepgram.com/v1/listen?model=nova-3&language=en" \
    -H "Authorization: Token YOUR_DEEPGRAM_API_KEY" \
    -H "Content-Type: audio/wav" \
    --data-binary @"$path"
}
```

```python
from sarvamai import SarvamAI
from sarvamai.core.api_error import ApiError

client = SarvamAI(api_subscription_key="YOUR_SARVAM_API_KEY")

def transcribe(path: str, language_code: str = "en-IN") -> str:
    try:
        with open(path, "rb") as f:
            response = client.speech_to_text.transcribe(
                file=f,
                model="saaras:v3",
                language_code=language_code,
                mode="transcribe",
            )
    except ApiError as e:
        if e.status_code == 403:
            raise RuntimeError("Invalid API key. Check your credentials.") from e
        elif e.status_code == 429:
            raise RuntimeError("Rate limit exceeded. Wait and retry.") from e
        elif e.status_code == 503:
            raise RuntimeError("Service overloaded. Retry with backoff.") from e
        raise
    return response.transcript
```

```javascript
import { SarvamAIClient, SarvamAIError } from "sarvamai";
import fs from "fs";

const client = new SarvamAIClient({ apiSubscriptionKey: "YOUR_SARVAM_API_KEY" });

async function transcribe(path, languageCode = "en-IN") {
  try {
    const response = await client.speechToText.transcribe({
      file: fs.createReadStream(path),
      model: "saaras:v3",
      language_code: languageCode,
      mode: "transcribe",
    });
    return response.transcript;
  } catch (e) {
    if (e instanceof SarvamAIError) {
      if (e.statusCode === 403) throw new Error("Invalid API key. Check your credentials.");
      if (e.statusCode === 429) throw new Error("Rate limit exceeded. Wait and retry.");
      if (e.statusCode === 503) throw new Error("Service overloaded. Retry with backoff.");
    }
    throw e;
  }
}
```

```bash
transcribe() {
  local path="$1" language_code="${2:-en-IN}"
  local body status
  body=$(curl -sS -w '\n%{http_code}' -X POST https://api.sarvam.ai/speech-to-text \
    -H "api-subscription-key: YOUR_SARVAM_API_KEY" \
    -F model="saaras:v3" \
    -F language_code="$language_code" \
    -F mode="transcribe" \
    -F file=@"$path")
  status="${body##*$'\n'}"
  case "$status" in
    403) echo "Invalid API key. Check your credentials." >&2; return 1 ;;
    429) echo "Rate limit exceeded. Wait and retry." >&2; return 1 ;;
    503) echo "Service overloaded. Retry with backoff." >&2; return 1 ;;
  esac
  # Minimal single-value extraction; use a real JSON parser for production code.
  echo "${body%$'\n'*}" | grep -o '"transcript":"[^"]*"' | cut -d'"' -f4
}
```

## Common mistakes

Sarvam's `file` parameter takes the open file object directly (`file=open(path, "rb")`), unlike Deepgram's `request`, which expects the bytes already read out (`request=f.read()`). Passing a file handle where Deepgram's SDK expects bytes, or vice versa, is the most common porting slip.

Deepgram enables diarization on the same synchronous call via `diarize_model`. Sarvam's `with_diarization` is a Batch API parameter instead, so diarized requests need to go through the Batch workflow even for a short file.

Sarvam's response is flat: `response.transcript`, not `response.results.channels[0].alternatives[0].transcript`. Update any code that destructures the Deepgram response shape.

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 Deepgram's request shape won't be picked up.

## See also

* [Migration overview](/api-reference-docs/migrations/from-deepgram/overview): base URL, auth, and SDK client changes
* [Which Speech-to-Text API to Use](/api-reference-docs/api-guides-tutorials/speech-to-text/which-api-to-use)
* [Batch API](/api-reference-docs/api-guides-tutorials/speech-to-text/batch-api)
* [Streaming API](/api-reference-docs/api-guides-tutorials/speech-to-text/streaming-api)
* [Speaker Diarization](/api-reference-docs/api-guides-tutorials/speech-to-text/batch-api#speaker-diarization)
* [Errors & Troubleshooting](/api-reference-docs/errors-troubleshooting)