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.
| Deepgram | Speech Revolutions |
|---|---|
Authorization: Token DEEPGRAM_API_KEY | X-API-Key: SPEECHREVOLUTIONS_API_KEY |
DEEPGRAM_API_KEY env var | SPEECHREVOLUTIONS_API_KEY |
# 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.
| Deepgram | Speech Revolutions REST | Speech Revolutions SDK |
|---|---|---|
POST /v1/listen (sync) | POST /api/v1/upload → PUT to presigned URL → POST /api/v1/upload/complete | transcribe() (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() |
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 field | Speech Revolutions |
|---|---|
...alternatives[0].transcript | result.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_language | result.languages |
| the whole Deepgram JSON | result.to_deepgram() reproduces it |
Drop-in for existing parsers
If your codebase already digs into results.channels[0].alternatives[0], call result.to_deepgram() and feed that dict to your existing code unchanged.
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, notAuthorization: Token— the SDK sets it, but hand-rolled HTTP calls need updating. - Speaker type changes. Speech Revolutions speakers are strings (
speaker_0), not integers. Useresult.utterancesinstead of grouping words yourself, or callto_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
keytermbecomes thecustom_vocabularylist.
Next steps
See the general migration playbook for cutover strategy, and the benchmarks for how the two compare on accuracy and diarization.