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 API key from SPEECHREVOLUTIONS_API_KEY, and its host from SPEECHREVOLUTIONS_BASE_URL if you need to point at something other than production. For installation and authentication, see the SDK pages: Python, JavaScript, Go, C#.
The cookbook repo has each recipe as a tested, runnable script with its own command-line arguments.
- 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
transcribe() uploads the file, waits for the result, and parses it into a transcript object. Pass 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() detects an http(s) URL automatically. 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
To avoid holding a connection open for the whole job, use submit(). It uploads the audio, queues the job, 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 you can fetch results 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
Submit every file first, then gather the results. submit() holds no connection open per job, so you can queue a whole directory up front and poll for completion afterwards.
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")transcribe() keeps one connection open per file until it finishes. Use it for single files. submit() separates queuing from retrieval, so throughput doesn't depend on how many connections you can hold open. To skip polling, combine it with a webhook.
Get notified with a webhook
Pass callback_url to receive a signed JSON POST when the job finishes, with no polling and no open connection. Use this 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 object, with a constant-time comparison. Copy your signing secret from the console (API Keys → Webhook signing secret) into SPEECHREVOLUTIONS_WEBHOOK_SECRET; the webhooks guide covers the payload, retries and rotation.
import hashlib
import hmac
import json
import os
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 = os.environ["SPEECHREVOLUTIONS_WEBHOOK_SECRET"] # Console → API Keys → Webhook signing secret
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) and honors the server's Retry-After header. Configure the retry budget and backoff on the client. Requests that create a job are never retried once the audio has been sent, because a retry would bill a second transcription.
Errors are typed and include the HTTP status and the server request ID. Include the request ID when you contact support.
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 has .utterances (contiguous speaker turns), and every word in .words has a speaker. See the speaker diarization guide.
result = client.transcribe("meeting.mp3", speaker_labels=True)
for u in result.utterances:
print(f"{u.speaker}: {u.text}")Multilingual audio
You don't need to set a language. The spoken language is detected automatically, including in audio that switches languages mid-file, and each word has its own language. 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, see the benchmarks page.
Pin the language
If you know the recording's language, pass it as language: an ISO 639-1 code such as "en", "ru" or "de". Detection is skipped and the whole file is transcribed in that language. Leave it out (or pass "auto") to keep automatic detection, the default.
result = client.transcribe("interview.mp3", language="ru")
print(result.text)When to pin, and when to rely on detection:
- Pin when every file in the workload is in one language you know in advance, for example strongly accented speech that automatic detection could mistake for another language.
- Rely on detection when the language is unknown, varies from file to file, or changes within a file. Pinning turns off mid-file language switching: every word is transcribed in the pinned language.
Pinning the wrong language does not produce an error. The model translates the speech into the pinned language: Russian audio pinned to "en" comes back as an English translation. Only pin a language you are sure of.
An unsupported code is rejected with HTTP 422 and a message naming the bad value. The supported codes are Whisper's 99-language set:
af am ar as az ba be bg bn bo br bs ca cs cy da de el en es et eu fa fi fo fr gl gu ha haw he hi hr ht hu hy id is it ja jw ka kk km kn ko la lb ln lo lt lv mg mi mk ml mn mr ms mt my ne nl nn no oc pa pl ps pt ro ru sa sd si sk sl sn so sq sr su sv sw ta te tg th tk tl tr tt uk ur uz vi yi yo zh
Subtitles (SRT / VTT)
Set output_type to srt or vtt and the server returns a finished subtitle file, with no client-side formatting needed. 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 fileBuilding 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.