Realtime WebSocket

VoiceRun STT Model is available through the same public realtime protocol as every model on VoiceRun STT Router:

wss://api.voicerun.com/v1/stt

Authenticate with a VoiceRun API key, select voicerun-asr-realtime-v1, and stream audio. Provider credentials are neither required nor accepted.

Connect#

import os import websockets ws = await websockets.connect( "wss://api.voicerun.com/v1/stt", additional_headers={ "Authorization": f"Bearer {os.environ['VOICERUN_API_KEY']}" }, )

The server first emits session.created. Configure the model before sending audio:

{ "type": "session.update", "model": "voicerun-asr-realtime-v1", "fallback_models": ["nova-3", "gpt-4o-transcribe"], "turn_detection": "server", "context": "Dairy Queen; Blizzard; drive-through", "language": "en", "allowed_languages": "en,es", "input_audio_format": "pcm16", "sample_rate": 16000 }

The Router replies with session.updated. It may then emit a warning listing settings that the selected model cannot honor; warnings do not close the session.

Model-specific configuration#

VoiceRun STT Model supports these live controls:

FieldDescription
context or promptVocabulary and conversational context applied to recognition
languageLanguage code, auto, or an empty string to return to auto-detection
allowed_languagesComma-separated language allow-list
eou_thresholdSemantic end-of-turn threshold, as a number or per-language object
eot_timeout_msHard maximum wait for an end of turn
vad_stop_msSilence before semantic end-of-turn scoring begins

Send another session.update between turns to update these fields without reconnecting. The public Router keeps the primary model and fallback_models fixed after audio begins.

{ "type": "session.update", "context": "offered times: 6:30 PM, 6:45 PM, 7:00 PM", "language": "en", "eou_threshold": 0.55 }

A successful update produces session.updated. If the active model cannot update settings on a live connection, the Router emits an unsupported_for_model error.

Stream audio#

Append base64-encoded audio chunks. PCM16 little-endian is the default; μ-law is also accepted.

{"type":"input_audio_buffer.append","audio":"AAAA//8AAAEAAAD/////AAAB..."}

Set sample_rate from 8,000 through 48,000 Hz. Chunks around 20–100 ms are a good fit for realtime calls. The Router converts accepted input to the rate required by the active model.

VoiceRun STT Model uses server-side turn-taking. Do not send client commit messages through the public Router. For a finite recording, append trailing silence so the model can close the final turn.

Receive transcripts and turns#

transcription.delta contains the current full hypothesis. Replace the previous hypothesis instead of concatenating it:

{"type":"transcription.delta","text":"I'd like a table","language":"en"}

turn.ended is the completed turn:

{ "type": "turn.ended", "text": "I'd like a table for four.", "language": "en", "reason": "provider" }

See the shared message reference for warnings, errors, fallback, and the complete public event vocabulary.

Automatic fallback#

Configure fallback_models when the session starts. If the active model has a terminal provider failure, the Router can replay up to eight seconds of buffered audio into the next model without replacing the client WebSocket.

{ "type": "session.model_changed", "from_model": "voicerun-asr-realtime-v1", "to_model": "nova-3", "reason": "provider_unavailable", "replayed_audio_ms": 2400 }

Unsupported VoiceRun-specific settings are ignored by a fallback model that cannot apply them, so failover itself is not rejected. See Automatic model fallback.

Complete Python example#

import asyncio import base64 import json import os import websockets URL = "wss://api.voicerun.com/v1/stt" async def transcribe(path: str): async with websockets.connect( URL, additional_headers={ "Authorization": f"Bearer {os.environ['VOICERUN_API_KEY']}" }, ) as ws: created = json.loads(await ws.recv()) assert created["type"] == "session.created" await ws.send(json.dumps({ "type": "session.update", "model": "voicerun-asr-realtime-v1", "fallback_models": ["nova-3"], "turn_detection": "server", "context": "reservation, booking, party size", "language": "en", "input_audio_format": "pcm16", "sample_rate": 16000, })) async def receive(): async for raw in ws: event = json.loads(raw) if event["type"] == "transcription.delta": print("Partial:", event["text"], end="\r") elif event["type"] == "turn.ended": print("Final: ", event["text"]) elif event["type"] == "session.model_changed": print("Fallback:", event["to_model"]) elif event["type"] == "error": raise RuntimeError( f'{event["code"]}: {event["message"]}' ) receiver = asyncio.create_task(receive()) with open(path, "rb") as audio: while chunk := audio.read(3200): await ws.send(json.dumps({ "type": "input_audio_buffer.append", "audio": base64.b64encode(chunk).decode(), })) await asyncio.sleep(0.1) await ws.send(json.dumps({ "type": "input_audio_buffer.append", "audio": base64.b64encode(bytes(32000)).decode(), })) await asyncio.sleep(2) await ws.send(json.dumps({"type": "session.close"})) await receiver asyncio.run(transcribe("audio.pcm"))

The input file in this example is headerless PCM16 little-endian, mono, at 16 kHz.

asrsttrealtimewebsockettranscription