Examples
Python: primary model with fallback#
Install the WebSocket client, set your VoiceRun PAT, and provide a PCM16, mono, 16 kHz WAV file:
pip install "websockets>=14" export VOICERUN_API_KEY="your-voicerun-pat" python quickstart.py call.wav
Save this as quickstart.py. It streams the WAV to VoiceRun STT Model with Deepgram and OpenAI
as ordered fallbacks, prints completed turns, and closes cleanly.
import asyncio import base64 import json import os import sys import wave import websockets URL = "wss://api.voicerun.com/v1/stt" async def transcribe(wav_path: str): with wave.open(wav_path, "rb") as wav: if (wav.getnchannels(), wav.getsampwidth(), wav.getframerate()) != (1, 2, 16000): raise ValueError("WAV must be PCM16, mono, at 16 kHz") pcm = wav.readframes(wav.getnframes()) 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", "gpt-4o-transcribe"], "turn_detection": "server", "language": "en", "input_audio_format": "pcm16", "sample_rate": 16000, })) updated = json.loads(await ws.recv()) assert updated["type"] == "session.updated" 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( f'Fallback: {event["from_model"]} -> {event["to_model"]} ' f'({event["replayed_audio_ms"]} ms replayed)' ) elif event["type"] == "error": raise RuntimeError(f'{event["code"]}: {event["message"]}') elif event["type"] == "session.closed": return receiver = asyncio.create_task(receive()) for offset in range(0, len(pcm), 3200): # 100 ms at PCM16/16 kHz/mono await ws.send(json.dumps({ "type": "input_audio_buffer.append", "audio": base64.b64encode(pcm[offset:offset + 3200]).decode(), })) await asyncio.sleep(0.1) # Provider endpointing needs to hear the final pause. silence = bytes(32000) # one second at PCM16/16 kHz/mono await ws.send(json.dumps({ "type": "input_audio_buffer.append", "audio": base64.b64encode(silence).decode(), })) await asyncio.sleep(2) await ws.send(json.dumps({"type": "session.close"})) await receiver if len(sys.argv) != 2: raise SystemExit("Usage: python quickstart.py call.wav") asyncio.run(transcribe(sys.argv[1]))
Node.js: primary model with fallback#
Install ws, set your VoiceRun PAT, and use the same WAV format as the Python example:
npm install ws export VOICERUN_API_KEY="your-voicerun-pat" node quickstart.mjs call.wav
import fs from "node:fs"; import WebSocket from "ws"; const wavPath = process.argv[2]; if (!wavPath) throw new Error("Usage: node quickstart.mjs call.wav"); const wav = fs.readFileSync(wavPath); if (wav.toString("ascii", 0, 4) !== "RIFF" || wav.toString("ascii", 8, 12) !== "WAVE") { throw new Error("Input must be a WAV file"); } const channels = wav.readUInt16LE(22); const sampleRate = wav.readUInt32LE(24); const bitsPerSample = wav.readUInt16LE(34); if (channels !== 1 || sampleRate !== 16000 || bitsPerSample !== 16) { throw new Error("WAV must be PCM16, mono, at 16 kHz"); } const dataOffset = wav.indexOf(Buffer.from("data")); if (dataOffset < 0) throw new Error("WAV has no data chunk"); const dataLength = wav.readUInt32LE(dataOffset + 4); const pcm = wav.subarray(dataOffset + 8, dataOffset + 8 + dataLength); const ws = new WebSocket("wss://api.voicerun.com/v1/stt", { headers: { Authorization: `Bearer ${process.env.VOICERUN_API_KEY}` }, }); const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function streamAudio() { for (let offset = 0; offset < pcm.length; offset += 3200) { appendAudio(pcm.subarray(offset, offset + 3200)); await sleep(100); } appendAudio(Buffer.alloc(32000)); // one second of trailing silence await sleep(2000); ws.send(JSON.stringify({ type: "session.close" })); } ws.on("message", async (data) => { const event = JSON.parse(data.toString()); if (event.type === "session.created") { ws.send(JSON.stringify({ type: "session.update", model: "voicerun-asr-realtime-v1", fallback_models: ["gpt-4o-mini-transcribe", "nova-3"], turn_detection: "server", language: "en", input_audio_format: "pcm16", sample_rate: 16000, })); } else if (event.type === "session.updated") { await streamAudio(); } else if (event.type === "session.model_changed") { console.log(`Fallback: ${event.from_model} -> ${event.to_model}`); } else if (event.type === "transcription.delta") { process.stdout.write(`\r${event.text}`); } else if (event.type === "turn.ended") { console.log(`\nFinal: ${event.text}`); } else if (event.type === "error") { throw new Error(`${event.code}: ${event.message}`); } else if (event.type === "session.closed") { ws.close(); } }); function appendAudio(pcm16) { ws.send(JSON.stringify({ type: "input_audio_buffer.append", audio: pcm16.toString("base64"), })); }
Browser: connection and authentication check#
The browser WebSocket API cannot set an Authorization header, so browser-only integrations may pass
the VoiceRun key in the connection query. Paste this into the browser console; it connects, configures
a session, reports success, and closes. Use a short-lived test PAT—query-string credentials may appear
in browser history or infrastructure logs, so a server-side connection is preferred for production.
const key = encodeURIComponent(prompt("VoiceRun PAT")); const ws = new WebSocket(`wss://api.voicerun.com/v1/stt?token=${key}`); ws.addEventListener("message", ({ data }) => { const event = JSON.parse(data); if (event.type === "session.created") { ws.send(JSON.stringify({ type: "session.update", model: "voicerun-asr-realtime-v1", turn_detection: "server", language: "en", })); } else if (event.type === "session.updated") { console.log("VoiceRun STT Router connection succeeded"); ws.send(JSON.stringify({ type: "session.close" })); } else if (event.type === "session.closed") { ws.close(); } else if (event.type === "error") { console.error(event.code, event.message); } });
