VoiceRun STT Model

Context‑aware speech‑to‑text built for real conversations

VoiceRun's first-party speech-to-text model, built for the turns, language changes, and difficult audio that voice agents encounter in production. Use it through the shared realtime STT API with dynamic context, semantic turn-taking, and guardrails around every decode.

Accuracy
5.01%
average WER across the Open ASR Leaderboard English suites
Turn-taking
Semantic
the server closes turns when the utterance reads finished, not on a timer
Availability
Global
one geo-routed endpoint — the nearest healthy region answers

A speech-to-text model built for agent turns

Generic speech-to-text gives you a transcript. An agent needs to know that “four” meant the party size it just offered, that the caller switched languages mid-sentence, and — hardest of all — that they have actually finished talking. Get any of that wrong and the transcript is technically accurate while the conversation falls apart.

VoiceRun STT Model is our first-party model, built on the open Qwen3-ASR foundation and hardened for real phone lines, heavy accents, code-switching, background noise, and half-finished sentences. It handles live production calls for VoiceRun agents today.

Speech-to-text should be dynamic

Most STT models are used statically: you configure a language and hint string once at connect time, then hope. But a conversation isn't static. The vocabulary that matters changes with every agent turn — the options just offered, the name just spelled. The language can change with every caller. Even the pace changes, turn by turn. VoiceRun STT Model is built dynamic: re-bias the context and re-target the language as the conversation moves, on one socket, while the server decides when each turn ends.

A conversation moves — its vocabulary, its language, its rhythm. VoiceRun STT Model is speech-to-text that moves with it.

Context biasing that updates mid-call

Send the vocabulary that matters right now — the options your agent just offered, the name being spelled, the SKUs on screen — and change it at any turn boundary on the same socket. Most STT APIs take a hint string once at connect time; this one re-applies yours at every turn's decode.

Server-side semantic turn-taking

An end-of-turn model runs beside the STT model and scores each pause, so a turn closes when the caller actually sounds finished — typically 250–400 ms after end-of-speech. Thresholds are tunable per language, with a hard cap so a turn is never slower than you allow.

Context that never comes back as the transcript

A known failure of every context-biased STT model: on light audio it can emit your injected prompt as if the caller said it. A hallucination guard strips the echo, keeps genuine speech, and returns an empty turn rather than a fabricated one — which is what makes aggressive biasing safe in production.

A language-ID guard that can’t drift

Short, accented, or code-switched speech makes raw models jump languages entirely. Out-of-set detections are re-decoded against your allow-list, correcting the language without touching the audio — so output stays in the script your downstream expects.

Mid-call language updates

Auto-detect within a list you control, then pin a language mid-call when you know what you're hearing — no reconnect. Pin one outside the set and the same request becomes a translation.

Multilingual, on your terms

Broadly multilingual with code-switching support. You provide a language allow-list and auto-detect stays inside it — the model still chooses within the list by listening, so recognition never drifts into a language you don't support.

A familiar realtime protocol

An OpenAI-Realtime-style WebSocket carries config, audio, incremental hypotheses, and turn events. Recorded audio streams through the same socket as live calls, and an OpenAI-compatible batch endpoint is there for file-at-a-time work.

One endpoint, geo-routed

Globally available behind a single hostname — every connection automatically lands in the nearest healthy region. Inference runs close to your callers, with no region picking and no per-region endpoints to configure.

Accuracy you can verify

We ran the Open ASR Leaderboard evaluation — its datasets, its normalizer, its scoring — against this API's live public endpoint on the full test sets, averaging 5.01% word error rate across its seven English suites and 5.02% across its complete multilingual coverage.

Average WER, Open ASR Leaderboard English suites

lower is better
View the 14 entries ahead of VoiceRun (implied No. 15)

VoiceRun is not listed on the leaderboard. Comparing its independently measured 5.01% with the leaderboard's current cleaned seven-suite public results places it after these 14 entries—an implied No. 15, not an official rank.

modulate/vfast4.43%
HojoAI/Hojo-ASR-V14.46%
microsoft/azure-speech-06-20264.51%
reson8/resonant-14.62%
bosonai/higgs-audio-v3-stt4.62%
AutoArk-AI/ARK-ASR-3B4.64%
elevenlabs/scribe_v24.65%
zoom/scribe_v14.65%
reson8/resonant-1-flash4.66%
OpenMOSS-Team/MOSS-Transcribe-preview-2B4.66%
bosonai/higgs-audio-v3-8b-stt-v24.73%
ibm-granite/granite-speech-4.1-2b4.90%
ibm-granite/granite-speech-4.1-2b-nar4.95%
Qwen/Qwen3-ASR-1.7B-hf4.98%
VoiceRun STT Model (voicerun-asr-realtime-v1)5.01%
AssemblyAI universal-3-5-pro5.03%
Gladia solaria-35.34%
Speechmatics enhanced5.90%
OpenAI whisper-large-v36.55%
View per-dataset results
LibriSpeech test-clean1.25%
SPGISpeech2.58%
LibriSpeech test-other2.90%
VoxPopuli AA-cleaned2.93%
GigaSpeech cleaned7.21%
AMI cleaned (meetings)8.37%
Earnings229.86%

Measured with the leaderboard's open-source harness against api.voicerun.com/v1/stt, full test sets, default decoding, no context biasing — the floor, not the ceiling. Comparison figures recomputed from the leaderboard's published per-dataset results over the same seven public suites. VoiceRun STT Model is not listed on the leaderboard. The collapsed list above uses its current cleaned seven-suite public results as of August 20, 2026; No. 15 is a comparison with those published entries, not an official leaderboard rank.

It holds across languages — on the leaderboard's complete multilingual coverage (five languages, each across FLEURS, Common Voice, and Multilingual LibriSpeech) it averages 5.02%: Spanish 3.8, German 3.9, Italian 5.4, French 5.7, Portuguese 6.3. And every number here is the floor, not the ceiling: these runs used none of the dynamic context biasing this API exists to provide. The benchmark can't pass your menu, your SKUs, or your caller's name to the model mid-conversation. Your agent can.

How a session moves

The server closes turns semantically, your agent re-biases the context with the options it just offered, and when the caller switches to Spanish you lock the language in — all on one socket, no reconnects:

wss://api.voicerun.com/v1/sttlive replay
connecting…
View the raw wire messages
wss://api.voicerun.com/v1/stt · Authorization: Bearer $VOICERUN_API_KEY
  {"type": "session.created"}

# start: language "" = auto-detect, constrained to your allow-list
  {"type": "session.update", "turn_detection": "server",
    "language": "",
    "allowed_languages": "en,es",
    "context": "reservation, booking, party size, high chair"}
  {"type": "session.updated", "turn_detection": "server"}

# turn 1 — just stream audio; the server segments the turns
  {"type": "input_audio_buffer.append", "audio": "<base64 pcm16 @ 16kHz>"}   × N
  {"type": "speech_started"}
  {"type": "transcription.delta", "text": "I'd like to book a table", "language": "en"}
  {"type": "turn.ended", "text": "I'd like to book a table for four tonight.",
    "language": "en", "reason": "acoustic", "eou_prob": 0.94}

# your agent offers 6:30 / 6:45 / 7:00 — bias the next turn with exactly that
  {"type": "session.update", "context": "offered times: 6:30 PM, 6:45 PM, 7:00 PM"}

# turn 2 — "six forty-five" resolves against the offer, not against guesswork
  {"type": "turn.ended", "text": "Six forty-five works.",
    "language": "en", "reason": "acoustic", "eou_prob": 0.91}

# next it needs a name — re-bias again, this time for spelled-out letters
  {"type": "session.update", "context": "caller name, spelling, letter by letter"}

# turn 3 — the letters survive because the context expects them
  {"type": "turn.ended", "text": "It's Maria — M-A-R-I-A.",
    "language": "en", "reason": "acoustic", "eou_prob": 0.90}

# turn 4 — the caller switches to Spanish; auto-detect follows...
  {"type": "turn.ended", "text": "Perdón, ¿puede agregar una silla alta?",
    "language": "es", "reason": "acoustic", "eou_prob": 0.89}

# ...and your agent locks it in for the rest of the call
  {"type": "session.update", "language": "es"}

# turn 5 — a trailing-off turn rides to the hard cap instead of hanging
  {"type": "turn.ended", "text": "Eh... un momento...",
    "language": "es", "reason": "timeout", "eou_prob": 0.12}

# turn 6 — your agent reads the booking back — the caller barges in;
# speech_started is your cue to cut the TTS
  {"type": "speech_started"}
  {"type": "turn.ended", "text": "¡Espera! Mejor a las siete.",
    "language": "es", "reason": "acoustic", "eou_prob": 0.92}

# re-bias one last time with the corrected booking for the confirm
  {"type": "session.update",
    "context": "confirmar: viernes 7:00 PM, mesa para cuatro, María, silla alta"}

# turn 7 — locked to Spanish, biased on the exact summary, the booking closes
  {"type": "turn.ended", "text": "Sí, confirmado — a las siete, a nombre de María.",
    "language": "es", "reason": "acoustic", "eou_prob": 0.95}

Transcribe a complete file

For file-at-a-time workflows, send a multipart request to the batch transcription endpoint. The realtime WebSocket stays at /v1/stt; batch transcription uses /v1/stt/transcriptions.

POST /v1/stt/transcriptions
curl https://api.voicerun.com/v1/stt/transcriptions \
  -H "Authorization: Bearer $VOICERUN_API_KEY" \
  -F model="voicerun-asr-realtime-v1" \
  -F file="@call.wav" \
  -F prompt="reservation, booking, party size" \
  -F language="en"

Start in about thirty lines

A complete working client — connect, turn on server-side turn-taking, stream audio, print finished turns. Your existing VoiceRun API key works as-is:

quickstart.py
import asyncio, base64, json, wave, websockets  # pip install "websockets>=14"

URL = "wss://api.voicerun.com/v1/stt"
API_KEY = "YOUR_VOICERUN_API_KEY"

async def transcribe(wav_path):
    pcm = wave.open(wav_path, "rb").readframes(1 << 30)   # PCM16 mono 16 kHz
    pcm += b"\x00" * 32000       # 1 s of silence — the server closes a turn when it HEARS the
                                 # pause, and a file (unlike a mic) stops sending at the last word
    async with websockets.connect(
        URL, additional_headers={"Authorization": f"Bearer {API_KEY}"}
    ) as ws:
        await ws.recv()  # session.created

        # server-side turn-taking, auto-detect within your languages, + domain context
        await ws.send(json.dumps({
            "type": "session.update",
            "turn_detection": "server",
            "language": "auto",             # detect per turn (pin a code later to lock it in)
            "allowed_languages": "en,es",   # auto-detect never leaves this list
            # turn-taking, tunable per language — raise a threshold to cut callers off less
            "eou_threshold": {"default": 0.5, "es": 0.6},
            "vad_stop_ms": 200,             # silence before the first end-of-turn consult
            "eot_timeout_ms": 600,          # hard cap: close by here even if the model never agrees
            "context": "reservation, booking, party size, high chair",
        }))

        async def send_audio():
            for i in range(0, len(pcm), 3200):            # ~100 ms chunks
                await ws.send(json.dumps({"type": "input_audio_buffer.append",
                                          "audio": base64.b64encode(pcm[i:i+3200]).decode()}))
                await asyncio.sleep(0.1)
        asyncio.create_task(send_audio())

        async for raw in ws:
            m = json.loads(raw)
            if m["type"] == "turn.ended" and m["text"]:
                print(f"[{m['reason']}/{m['language']}] {m['text']}")

asyncio.run(transcribe("call.wav"))