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

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 file

Keep 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.