Message Reference
The VoiceRun STT v1 protocol is shared by every model at wss://api.voicerun.com/v1/stt.
Client Messages#
session.update#
Send this after session.created. Fields may be flat or nested under session; flat fields win when both are supplied. provider is not required because the gateway resolves it from model.
| Field | Type | Required | Description |
|---|---|---|---|
model | string | Yes | STT model selector. A different model on an active session switches models; see Switching models mid-session |
fallback_models | array | No | Ordered fallback models; up to five, distinct from the primary model. Each entry is a model name or {"model": …, "provider_arguments": {…}} |
language | string | No | Language code, or auto when supported |
context | string | No | Context or keyword biasing, up to 8,000 bytes; support varies by model. prompt is an alias |
input_audio_format | string | No | pcm16 (default) or mulaw |
sample_rate | integer | No | Input sample rate, 8000–48000; default 16000 |
turn_detection | string | No | Who ends a turn: provider (default; alias server), client, silero, smart_turn, or semantic (alias typesafe). See Turn detection |
turn_options | object | No | Silence windows and thresholds for silero, smart_turn, and semantic. See turn_options |
provider_arguments | object | No | Provider-native settings the Router does not model. See Provider arguments |
apply | string | No | When a model switch takes effect: turn_end (default) or now |
stall_ms, max_wait_ms | integer | No | Bounds on a model switch that waits for the turn to end |
endpointing | integer | No | Silence-based endpointing in milliseconds. Supported by Nova, Qwen3, xAI, OpenAI transcription, and ElevenLabs Scribe. Defaults vary; see the mapping below. |
eou_threshold | number or object | No | End-of-turn confidence, 0–1, for VoiceRun STT Model, Deepgram Flux, and Inworld. Language maps are supported only by VoiceRun. |
eot_timeout_ms | integer | No | Advanced end-of-turn timeout for VoiceRun STT Model and Deepgram Flux |
vad_stop_ms | integer | No | VoiceRun STT Model silence before end-of-turn scoring |
allowed_languages | string | No | VoiceRun STT Model language allow-list |
{ "type": "session.update", "session": { "model": "voicerun-asr-realtime-v1", "fallback_models": ["nova-3", "gpt-4o-transcribe"], "language": "en", "context": "insurance, policy, premium", "input_audio_format": "pcm16", "sample_rate": 16000 } }
Provider credential fields are not part of the public protocol and are rejected. Recognized but unsupported model settings produce a separate warning event and are omitted from the applied settings. Unknown field names return invalid_request; use router field names, or pass provider-native names through provider_arguments. provider is ignored with a warning because model determines routing. Settings are limited to 32 KiB.
Send the first session.update within ten seconds of session.created; otherwise the session ends with configuration_timeout. You can stream audio right after it, without waiting for session.ready.
Every fallback model is validated before admission. On an active session, send session.update again to change settings. The Router applies them live where the model allows it, reconnects the provider on the same model where it does not (session.updated with reconnected: true), and switches models when model changes. fallback_models, sample_rate, and input_audio_format cannot change once the session is active.
Fallback models inherit the shared session settings. Unsupported settings are ignored by the newly active model and do not cause the fallback attempt to fail.
Shared turn-detection controls
These fields tune the model's own endpointing, which ends turns under turn_detection: "provider". Use the same router fields across models. Adapters translate them into the native provider configuration; shared names do not mean identical model behavior or calibrated confidence scores.
| Router field | Model/provider | Native setting | Router behavior when omitted |
|---|---|---|---|
endpointing | Deepgram Nova | endpointing | 300 ms |
endpointing | Qwen3 ASR | turn_detection.silence_duration_ms | 500 ms |
endpointing | xAI | endpointing | 800 ms |
endpointing | OpenAI transcription | turn_detection.silence_duration_ms | 500 ms |
endpointing | ElevenLabs Scribe | vad_silence_threshold_secs (milliseconds divided by 1000) | Provider-managed; not sent |
eou_threshold | Deepgram Flux | eot_threshold | 0.8 |
eou_threshold | Inworld | endOfTurnConfidenceThreshold | 0.8 |
eou_threshold | VoiceRun ASR | eou_threshold | Deployment default |
eot_timeout_ms | Deepgram Flux | eot_timeout_ms | 2500 ms |
eot_timeout_ms | VoiceRun ASR | eot_timeout_ms | Deployment default |
For example, change model between qwen3-asr-flash-realtime, gpt-4o-mini-transcribe, and grok-stt while keeping endpointing: 650. For Inworld, use eou_threshold: 0.7 instead of its native field name. Provider-specific accepted ranges still apply; the router does not silently clamp values.
Important distinctions:
- OpenAI's VAD speech-activity threshold is not end-of-turn confidence.
eou_thresholdis not mapped to it. - Scribe's commit-silence threshold differs from its minimum speech-break filter. The latter remains at the adapter's existing 800 ms setting;
endpointingcontrols commit silence separately. - Soniox's maximum endpoint delay and Gradium's processing delay are not mapped to silence endpointing. These controls remain unavailable in this version of the router.
- Inworld supports confidence, not the router's
eot_timeout_mscontrol. Per-language threshold objects are warned and omitted for both Inworld and Flux, rather than silently falling back to a numeric default. - Thresholds must be numbers from 0 to 1, or non-empty maps of such numbers. Turn durations must be unsigned 32-bit integers;
endpointingandeot_timeout_msmust be positive. Malformed values returninvalid_requestwithout changing the active configuration.
Mappings follow the provider interfaces: Qwen3, OpenAI VAD, xAI streaming STT, and ElevenLabs Scribe.
Deepgram Nova example
Set endpointing to control how much audio silence Deepgram Nova waits for before producing a speech_final result, which the Router uses to close the turn:
{ "type": "session.update", "session": { "model": "nova-3", "language": "en", "endpointing": 300 } }
- If omitted, the Router uses 300 ms. This is the Router's default, not Deepgram's native API default.
- A live update can change
endpointing; the Router replaces the upstream session using its planned-switch flow, normally at a turn boundary. The client WebSocket stays connected. - Values must be positive integers fitting an unsigned 32-bit value. Strings, zero, negative numbers, fractions, booleans, and
nullreturninvalid_request. - Nova's separate
utterance_end_msword-gap detector remains fixed at 1000 ms. Either detector can close a turn, soendpointingis not a guaranteed minimum turn duration. - Deepgram Flux uses
eou_thresholdandeot_timeout_ms, notendpointing. Unsupported models reportendpointingin awarningand omit it from the applied settings. Nova also ignores it with a warning whenturn_detectionis anything other thanprovider, because the Router turns Nova's own endpointing off.
STT Lab displays known defaults and sends those displayed values explicitly when connecting. This includes the adapter defaults in the table and VoiceRun ASR defaults supplied by the model catalog. These are labeled Lab defaults; they are not a promise that an independently configured upstream server uses the same defaults. Where no numeric default is available (including Scribe's commit silence), the Lab labels it provider-managed instead of guessing. Confidence and timeout support are displayed separately.
See Deepgram endpointing and utterance-end detection for the distinction between the two detectors.
input_audio_buffer.append#
Append a base64-encoded audio chunk. audio.append is accepted as an alias.
{"type":"input_audio_buffer.append","audio":"AAAA//8AAAEAAAD/////AAAB..."}
input_audio_buffer.commit#
Mark the end of the user's turn. Under turn_detection: "client" this ends the turn; under other strategies it only marks a turn boundary and applies a model switch that is waiting for the turn to end. audio.commit is accepted as an alias. See Client-controlled turns.
{"type":"input_audio_buffer.commit"}
turn.context#
Set the assistant's latest utterance for semantic turn detection, up to 8,000 bytes. endpoint.context is accepted as an alias. The Router answers turn.context.updated.
{"type":"turn.context","text":"What number should I call you back on?"}
session.close#
Gracefully finish the socket. Closing the WebSocket directly is also supported. The Router answers session.closed.
{"type":"session.close"}
Server Messages#
session.created#
{"type":"session.created","session":{"id":"c4b2d518-4bad-4ba5-b66a-006cbed8c233"}}
session.updated#
Confirms the accepted configuration. It echoes the settings you sent that the model applies, never provider_arguments values, and adds a configuration receipt. It does not mean the provider is connected; see session.ready.
{ "type": "session.updated", "model": "nova-3", "language": "en", "sample_rate": 16000, "input_audio_format": "pcm16", "configuration": { "revision": 1, "model": "nova-3", "turn_detection": "provider", "native_keys": [], "native_status": "submitted", "overridden_normalized_fields": [] } }
native_keys lists the forwarded provider_arguments keys, and overridden_normalized_fields lists Router settings that one of them replaced. When a live update needed a new provider connection on the same model, session.updated also carries reconnected: true, replayed_audio_ms, and turn_interrupted.
session.ready#
The active model's provider connection is up. It follows each model that becomes active, including after a switch or fallback. If the session's model does not connect within ten seconds, the session ends with provider_connect_timeout; a model you switch to that cannot connect produces session.update_failed instead.
{"type":"session.ready","model":"nova-3","readiness_level":"transport_connected","configuration_revision":1}
readiness_level is transport_connected for streaming models (the provider socket is open and configured) or local_allocated for models that connect per request. Neither guarantees the provider accepted every setting.
warning#
Reports settings that the selected model cannot honor. The session remains active.
{ "type": "warning", "code": "unsupported_for_model", "message": "some session fields are not supported by this model", "fields": ["eou_threshold"] }
A turn_detection_unavailable warning means the requested local strategy cannot run, and the session uses provider turn detection instead:
{ "type": "warning", "code": "turn_detection_unavailable", "message": "semantic turn detection is unavailable on this server; using provider turn detection" }
session.update_pending#
A model switch was accepted. applies_at is now when it starts at once, or turn_end when it waits for the current turn to end, with the effective stall_ms and max_wait_ms.
{"type":"session.update_pending","model":"nova-3","applies_at":"turn_end","stall_ms":2000,"max_wait_ms":10000}
session.update_failed#
A model switch could not be applied, or a newer session.update replaced it (code: "superseded"). The current model continues.
{"type":"session.update_failed","model":"nova-3","code":"provider_error","message":"the requested STT model did not connect in time; the current model is unchanged"}
session.model_changed#
The live session moved to another model: reason is provider_unavailable after an automatic fallback, or client_requested after a switch you asked for. replayed_audio_ms is the input since the last turn boundary that was replayed into the new model, up to eight seconds. turn_interrupted is true when a turn was in progress; the new model then produces that turn's turn.ended. The message also carries a configuration receipt, as in session.updated.
{ "type": "session.model_changed", "from_model": "voicerun-asr-realtime-v1", "to_model": "nova-3", "reason": "provider_unavailable", "replayed_audio_ms": 2400, "turn_interrupted": true }
Transcription events after this message belong to to_model. The Router discards queued hypotheses from the failed model before switching.
speech.started / speech.stopped#
Voice activity heard by the Router, under silero, smart_turn, and semantic. audio_ms is the position in your input audio.
{"type":"speech.started","audio_ms":1440}
transcription.delta#
The current full hypothesis. Replace the previous hypothesis rather than concatenating deltas. Under any turn_detection other than provider, it carries the whole turn so far.
{"type":"transcription.delta","text":"hello how are","language":"en"}
turn.ended#
Final transcript for a completed turn. reason says what ended it: provider, client, silero, smart_turn, semantic, silence_fallback (no completion score arrived in time), or timeout (max_silence_ms reached).
{ "type": "turn.ended", "text": "hello how are you doing today", "language": "en", "reason": "provider", "eou_prob": null }
Client-controlled turns add commit_id and flush_outcome. The local strategies add the completion score as eou_prob (or null) and the silence that ended the turn:
{ "type": "turn.ended", "text": "I need to reschedule because I have a meeting on Tuesday", "language": "en", "reason": "semantic", "eou_prob": 0.82, "silence_ms": 280 }
A turn's transcript is limited to 64 KiB, and a single provider result to 32 KiB; beyond that the session ends with transcript_limit.
turn.context.updated#
Confirms a turn.context message.
{"type":"turn.context.updated"}
session.closed#
The answer to session.close; the Router then closes the socket.
{"type":"session.closed"}
error#
{"type":"error","code":"insufficient_credits","message":"insufficient VoiceRun credits"}
Common codes include invalid_request, unsupported_model, unsupported_for_model, unauthorized, insufficient_credits, auth_unavailable, service_unavailable, provider_error, provider_unavailable, concurrency_limit, session_not_configured, and session_timeout. invalid_provider_credentials indicates a VoiceRun-managed provider integration problem; customers never send provider credentials through this API.
| Code | Meaning |
|---|---|
configuration_timeout | No valid session.update within ten seconds of session.created |
provider_connect_timeout | The model's provider did not connect within ten seconds |
operation_pending | A command arrived while the session was starting, recovering, or switching models; retry after the outcome. Audio sent meanwhile is held, up to eight seconds |
backpressure | More than eight seconds of audio was held, or the provider stopped accepting audio |
transcript_limit | A turn's transcript exceeded the size limit |
stream_expired | The session's billing stream ended |
transition_unconfirmed | A model switch could not be confirmed; reconnect before sending more audio |
Turn Detection#
turn_detection decides when a turn ends. Every strategy works with every model.
turn_detection | The turn ends when | Events |
|---|---|---|
provider (default; alias server) | the model's own endpointing produces a final | transcription.delta, turn.ended |
client | you send input_audio_buffer.commit | transcription.delta, turn.ended with commit_id and flush_outcome |
silero | the Router's voice-activity detector hears min_silence_ms of silence after speech | speech.started, speech.stopped, transcription.delta, turn.ended |
smart_turn | the Smart Turn model judges the pause complete (short wait) or incomplete (long wait), or gives no score in time (medium wait) | as for silero |
semantic (alias typesafe) | a semantic model judges the transcript a complete reply to the assistant's last utterance, with the same waits | as for silero |
Under any strategy other than provider, the model's finals become segments of a longer turn. Deepgram Nova turns its own endpointing off in these modes. Other models keep segmenting; the Router still decides where the turn ends, and the update is answered with an unsupported_for_model warning naming turn_detection.
silero, smart_turn, and semantic listen to the Router's own copy of the input, so they work at any sample_rate and with mulaw. Early in each pause the Router asks the model to finalize, and a provider final that arrives after its turn ended is never repeated in the next turn. If a local strategy is unavailable, the session uses provider and receives a turn_detection_unavailable warning.
Client-controlled turns#
With turn_detection: "client", send input_audio_buffer.commit when the user's turn ends. The Router asks the model to finalize the audio it has not reported yet, and sends turn.ended once that final arrives or after one second, so words spoken just before the commit stay in this turn. flush_outcome reports how the turn closed:
flush_outcome | Meaning |
|---|---|
flushed | The model finalized the pending audio |
unsupported | The model cannot finalize on demand; the turn closed with the text on hand |
timeout | No final within one second |
provider_error | The finalize request failed |
superseded | A second commit arrived first |
model_changed, mode_changed, provider_failed | A model switch, a turn_detection change, or a provider failure closed the turn |
Deepgram Nova supports finalizing on demand. With other models, a provider final for audio just before the commit can appear in the next turn.
Semantic turns#
For semantic, send the assistant's latest utterance with turn.context whenever it changes. The completion score compares the user's transcript with it, so a pause after "I need to reschedule because" need not end the turn. A score that does not arrive within request_timeout_ms falls back to uncertain_silence_ms.
turn_options#
Tune silero, smart_turn, and semantic. Unknown fields and out-of-range values return invalid_request.
{"type":"session.update","model":"nova-3","turn_detection":"smart_turn","turn_options":{"min_silence_ms":320,"max_silence_ms":3000}}
| Field | Default | Range | Meaning |
|---|---|---|---|
min_silence_ms | 256 | 64–3000 | Silence that ends a turn judged complete, and every silero turn |
uncertain_silence_ms | 900 | min_silence_ms–max_silence_ms | Silence that ends a turn with no usable score |
max_silence_ms | 2200 | up to 10000 | Silence that ends any turn |
complete_threshold | 0.7 | 0–1, above incomplete_threshold | semantic score at or above which the turn is complete. smart_turn uses 0.5 |
incomplete_threshold | 0.35 | 0–1 | Score below which the turn waits max_silence_ms |
speech_threshold | 0.5 | 0.01–0.99 | Voice-activity probability that counts as speech |
request_timeout_ms | 600 | 50–2000 | Time allowed for one completion score |
min_request_interval_ms | 100 | 100–2000 | Minimum gap between score requests |
transcript_settle_ms | 20 | 0–1000 | How long the transcript must be unchanged before it is scored |
flush_timeout_ms | 50 | 50–1000 | How long a turn end waits for the final of unfinished text |
Switching Models Mid-Session#
Send session.update with a different model on an active session. The Router starts the new model beside the current one and moves over only once it has connected, so a model that cannot connect never interrupts the session.
{"type":"session.update","model":"nova-3","apply":"turn_end","stall_ms":800,"max_wait_ms":30000}
apply: "turn_end"(default): if a turn is in progress, the switch waits for it to end and is acknowledged withsession.update_pending(applies_at: "turn_end"). It is forced earlier when no transcript arrives forstall_ms(default 2000) or aftermax_wait_ms(default 10000); both are capped at five minutes. Underclientturn detection,stall_msdefaults tomax_wait_ms, so a switch waits for your commit.apply: "now", or no turn in progress: the switch starts at once (session.update_pendingwithapplies_at: "now").
The switch completes with session.model_changed (reason: "client_requested") or fails with session.update_failed, and the current model continues. Audio since the last turn boundary, up to eight seconds, is replayed into the new model.
A new session.update while a switch is pending replaces it, and the replaced switch is reported with code: "superseded"; naming the current model withdraws it. The new model is billed at its own published rate from the moment of the switch. If it later fails, the Router returns to the previous model before trying fallback_models.
Provider Arguments#
provider_arguments passes provider-native settings the Router does not model. They are applied after the Router's own settings, so a native value wins on a conflict.
{"type":"session.update","model":"nova-3","provider_arguments":{"smart_format":false,"numerals":true}}
- Unknown keys are forwarded unchanged. Forwarding does not mean the provider accepted them.
- Credentials, URLs, callbacks, the model, the audio format, and each provider's turn-control settings (for example Deepgram
endpointingor Fluxeot_threshold) are rejected withinvalid_request. Use the Router fields for those. - Objects are limited to 16 KiB and 12 levels of nesting.
- Arguments belong to one model. Omitting the field keeps them,
{}clears them, and switching models without new arguments clears them. Fallback models never inherit them; give a fallback its own with{"model": "gpt-4o-transcribe", "provider_arguments": {…}}. - Values are never echoed back;
configuration.native_keyslists the forwarded keys. - Changing them reconnects the provider on the same model.
