Cookbook
Short, copy-pasteable recipes for common Speech Revolutions tasks. Each one is a complete snippet in Python, JavaScript, Go and C#. Every client reads your key from SPEECHREVOLUTIONS_API_KEY, and its host from SPEECHREVOLUTIONS_BASE_URL if you need to point at something other than production — see any SDK page for install and auth: Python, JavaScript, Go, C#.
Want them as files instead? The cookbook repo has each recipe as a runnable script with its own command-line arguments, and every one is tested on each change.
- Transcribe a local file
- Transcribe from a URL
- Submit and poll
- Batch many files
- Get notified with a webhook
- Retries and timeouts
- Speaker labels
- Multilingual audio
- Subtitles (SRT / VTT)
Transcribe a local file
The one-liner. transcribe() uploads the file, waits for the result, and parses it into a transcript object. Point it at a path, raw bytes, or a file object.
from speechrevolutions import SpeechRevolutions
client = SpeechRevolutions()
result = client.transcribe("meeting.mp3")
print(result.text)Transcribe from a URL
transcribe() auto-detects an http(s) URL; the transcribe_url() / transcribeUrl() aliases make the intent explicit.
# auto-detected
result = client.transcribe("https://example.com/audio.mp3")
# explicit alias
result = client.transcribe_url("https://example.com/audio.mp3")Submit and poll
When you don't want to hold a connection open for the whole job, submit() uploads the audio, enqueues it, and returns a job_id immediately. Fetch the result later with get_job_status() and get_transcript(). A job's status is one of processing, completed, or failed; the download URL is regenerated on demand, so results are fetchable long after the upload.
import time
from speechrevolutions import SpeechRevolutions
from speechrevolutions.exceptions import JobFailedError
client = SpeechRevolutions()
job_id = client.submit("meeting.mp3", speaker_labels=True) # returns immediately
print("submitted", job_id)
def poll_until_done(job_id, interval=3.0):
while True:
status = client.get_job_status(job_id) # processing | completed | failed
if status.is_completed:
return client.get_transcript(job_id) # downloads + parses
if status.is_failed:
raise JobFailedError(
f"job {job_id} failed", step=status.failed_stage, reason=status.reason
)
time.sleep(interval)
result = poll_until_done(job_id)
print(result.text)Batch many files
The scalable pattern: submit every file first, then gather the results. Because submit() holds no long-lived connection per job, you can enqueue a whole directory up front and poll for completion afterwards — nothing stays open while the server works.
import time
from pathlib import Path
from speechrevolutions import SpeechRevolutions
client = SpeechRevolutions()
# 1. Submit all files up front — each call returns as soon as the audio is enqueued.
jobs = {} # job_id -> source path
for path in Path("audio/").glob("*.mp3"):
job_id = client.submit(str(path))
jobs[job_id] = path
print(f"submitted {len(jobs)} jobs")
# 2. Gather: poll the pending set until every job is done.
pending = set(jobs)
results = {}
while pending:
for job_id in list(pending):
status = client.get_job_status(job_id)
if status.is_completed:
results[job_id] = client.get_transcript(job_id)
pending.discard(job_id)
elif status.is_failed:
print(f"{jobs[job_id]} failed: {status.failed_stage} {status.reason}")
pending.discard(job_id)
if pending:
time.sleep(3)
for job_id, result in results.items():
print(jobs[job_id], "->", len(result.text), "chars")Why submit + poll for batches
transcribe() keeps one connection open per file until it finishes — fine for a single clip, wasteful for hundreds. submit() decouples enqueue from retrieval, so throughput is bounded by the platform, not by how many sockets you can hold open. For fully hands-off delivery, combine it with a webhook and skip polling entirely.
Get notified with a webhook
Pass callback_url and the platform POSTs a signed JSON notification when the job finishes — no polling, no open connection. This is the right pattern for server and background workloads.
The body is {job_id, status: "completed" | "failed", download_url?, step?, reason?}, signed with HMAC-SHA256 over the raw body in the X-SR-Signature: sha256=<hex> header (plus X-SR-Event with the status and a unique X-SR-Delivery id). Always verify against the raw bytes you received — not a re-serialized dict — with a constant-time comparison.
import hashlib
import hmac
import json
from fastapi import FastAPI, Request, HTTPException
from speechrevolutions import SpeechRevolutions
client = SpeechRevolutions()
# 1. Submit with a webhook. Use submit() so you return without waiting.
client.submit("meeting.mp3", callback_url="https://you.example.com/hook")
# 2. Receive + verify the notification.
app = FastAPI()
SECRET = "your-signing-secret" # the same secret configured server-side
def verify_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header or "")
@app.post("/hook")
async def hook(request: Request):
raw = await request.body()
if not verify_signature(raw, request.headers.get("X-SR-Signature", ""), SECRET):
raise HTTPException(status_code=401, detail="bad signature")
event = json.loads(raw)
if event["status"] == "completed":
... # mark done; fetch event["download_url"]
else:
... # event["step"], event["reason"]
return {"ok": True} # a 2xx acks delivery (we retry on 5xx)Retries and timeouts
Every client retries transient failures automatically — 429, 5xx, and network errors — honoring the server's Retry-After header. Tune the budget and backoff on the client. Requests that create a job are the exception: they are never retried after the bytes leave you, because a retry there would bill you for a second transcription.
Errors are typed and carry the HTTP status and the server request id, so a failure is enough for support to find the job in our logs without you reproducing it.
from speechrevolutions import SpeechRevolutions
from speechrevolutions.exceptions import RateLimitError
client = SpeechRevolutions(
max_retries=3,
retry_backoff=0.5, # seconds, exponential
proxies={"https": "http://proxy.internal:8080"},
)
try:
result = client.transcribe("meeting.mp3")
except RateLimitError as e:
print(e.status_code, e.request_id) # e.g. 429 "req_..."Speaker labels
speaker_labels (on by default) labels who spoke each segment. diarize is a Deepgram-compatible alias for the same flag. The result exposes .utterances — contiguous speaker turns — and every word in .words carries a speaker. Full detail in the speaker diarization guide.
result = client.transcribe("meeting.mp3", speaker_labels=True)
for u in result.utterances:
print(f"Speaker {u.speaker}: {u.text}")Multilingual audio
There is no language flag to set. Speech Revolutions detects the spoken language and transcribes it, including audio that switches languages mid-file. Just call transcribe() as usual. For domain-specific names and jargon, pass custom_vocabulary to bias the model toward those terms.
result = client.transcribe(
"entrevista.mp3",
custom_vocabulary=["Barcelona", "paella"], # optional domain terms
)
print(result.text)For per-language accuracy across our benchmark suite, see the benchmarks page.
Subtitles (SRT / VTT)
Set output_type to srt or vtt and the server returns ready-to-use subtitle bytes — no client-side formatting. For subtitles, .text holds the decoded file and .save() writes it (inferring the extension from the output type). See output formats & subtitles for all six formats and when to use each.
# SubRip (.srt)
srt = client.transcribe("meeting.mp3", output_type="srt")
srt.save("meeting") # -> meeting.srt
# WebVTT (.vtt)
vtt = client.transcribe("meeting.mp3", output_type="vtt")
print(vtt.text) # the decoded WEBVTT fileKeep going
Building a live progress bar for a web app? See Live progress for web apps. Working with timings? See Timestamps. Migrating from another provider? Start with the migration playbook.