Migrating from Deepgram to Speech Revolutions

Deepgram's pre-recorded API is a single synchronous POST: you send raw audio bytes to /v1/listen and get the full transcript back in one response. Speech Revolutions uses a short upload-then-wait flow, but the SDK hides it behind one transcribe() call — so in practice the migration is a rename, not a rewrite. This guide maps every piece across, including a to_deepgram() helper that returns Deepgram-shaped JSON so your existing response parsing keeps working.

The one-line version

Deepgram's diarize=true is supported verbatim — Speech Revolutions accepts diarize as an alias for speaker_labels. And result.to_deepgram() reshapes the Speech Revolutions output into the results.channels[0].alternatives[0] structure you already parse.

Authentication

Deepgram authenticates with an Authorization: Token <key> header. Speech Revolutions uses an X-API-Key header, and the SDKs read it from the environment for you.

DeepgramSpeech Revolutions
Authorization: Token DEEPGRAM_API_KEYX-API-Key: SPEECHREVOLUTIONS_API_KEY
DEEPGRAM_API_KEY env varSPEECHREVOLUTIONS_API_KEY
bash
# was
export DEEPGRAM_API_KEY=...
# now
export SPEECHREVOLUTIONS_API_KEY=stt_...

Endpoint & method mapping

Deepgram is one synchronous endpoint. Speech Revolutions splits creation and retrieval, but the SDK's transcribe() drives the whole flow and blocks until the transcript is ready — the closest analogue to a single Deepgram call.

DeepgramSpeech Revolutions RESTSpeech Revolutions SDK
POST /v1/listen (sync)POST /api/v1/upload → PUT to presigned URL → POST /api/v1/upload/completetranscribe() (blocks) or submit() (non-blocking)
— (response is inline)GET /api/v1/jobs/{id}/stream (SSE progress)on_progress callback
— (response is inline)GET /api/v1/jobs/{id}get_job_status() / get_transcript()
POST/api/v1/upload

Upload differences

Deepgram takes raw audio bytes directly in the request body with a Content-Type matching the file. Speech Revolutions uploads through a presigned URL, which means large files stream straight to object storage instead of through the API — but the SDK does the presign, PUT, and complete handshake for you, so you still pass a path, URL, bytes, or file object to transcribe(). Speech Revolutions also reports real upload and transcribe progress; Deepgram exposes no percentage for pre-recorded audio.

Response shape

Deepgram nests everything under results.channels[0].alternatives[0], with word-level speakers as integers. Speech Revolutions returns a transcript-first object. Map it like this:

Deepgram fieldSpeech Revolutions
...alternatives[0].transcriptresult.text (or result.transcript)
...alternatives[0].words[] (word, start, end)result.words (text, start, end)
per-word speaker (integer, e.g. 0)per-word speaker (string, e.g. speaker_0) plus result.utterances (grouped speaker turns)
results.channels[0].detected_languageresult.languages
the whole Deepgram JSONresult.to_deepgram() reproduces it

If your codebase already digs into results.channels[0].alternatives[0], call result.to_deepgram() and feed that dict to your existing code unchanged.

python
dg = result.to_deepgram()
print(dg["results"]["channels"][0]["alternatives"][0]["transcript"])

Diarization

Deepgram diarizes with diarize=true, tagging each word with an integer speaker. Speech Revolutions accepts the same diarize flag (an alias for speaker_labels), and additionally groups the words into result.utterances — ready-made speaker turns you would otherwise have to reconstruct from per-word integers. Diarization quality is one of Zephyr's strongest results; see the benchmarks and the comparison table for measured numbers.

Timestamps

Both return per-word start/end times in seconds. On Speech Revolutions word timestamps are on by default (word_timestamps=true); the values live on result.words.

Language selection

Deepgram takes a BCP-47 language query param, or detect_language=true to auto-detect. Speech Revolutions always auto-detects, including code-switching mid-file — there's no language parameter to set. result.languages is a list of {start, end, language} segments covering the whole file, and every word in result.words also carries a language.

Side by side

Diarized transcription, before and after. The "before" column is deepgram-sdk v3, which is what most existing integrations are running; Deepgram has since reshaped its client, so if you are on v4 or newer your code will differ from the left-hand side. The right-hand side is unaffected either way.

from deepgram import DeepgramClient, PrerecordedOptions

dg = DeepgramClient()  # DEEPGRAM_API_KEY

with open("meeting.mp3", "rb") as f:
    source = {"buffer": f.read(), "mimetype": "audio/mp3"}

options = PrerecordedOptions(model="nova-3", diarize=True, smart_format=True)
resp = dg.listen.rest.v("1").transcribe_file(source, options)

alt = resp["results"]["channels"][0]["alternatives"][0]
print(alt["transcript"])
for w in alt["words"]:
    print(w["speaker"], w["word"], w["start"], w["end"])

Common pitfalls

  • Auth header. It's X-API-Key, not Authorization: Token — the SDK sets it, but hand-rolled HTTP calls need updating.
  • Speaker type changes. Speech Revolutions speakers are strings (speaker_0), not integers. Use result.utterances instead of grouping words yourself, or call to_deepgram() for the integer form.
  • No inline response. There is a job to wait on. transcribe() hides this; if you call the REST API directly, follow the upload → complete → poll/stream flow.
  • Keyword biasing. Deepgram's repeatable keyterm becomes the custom_vocabulary list.

See the general migration playbook for cutover strategy, and the benchmarks for how the two compare on accuracy and diarization.