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.

FieldTypeRequiredDescription
modelstringYesSTT model selector. A different model on an active session switches models; see Switching models mid-session
fallback_modelsarrayNoOrdered fallback models; up to five, distinct from the primary model. Each entry is a model name or {"model": …, "provider_arguments": {…}}
languagestringNoLanguage code, or auto when supported
contextstringNoContext or keyword biasing, up to 8,000 bytes; support varies by model. prompt is an alias
input_audio_formatstringNopcm16 (default) or mulaw
sample_rateintegerNoInput sample rate, 8000–48000; default 16000
turn_detectionstringNoWho ends a turn: provider (default; alias server), client, silero, smart_turn, or semantic (alias typesafe). See Turn detection
turn_optionsobjectNoSilence windows and thresholds for silero, smart_turn, and semantic. See turn_options
provider_argumentsobjectNoProvider-native settings the Router does not model. See Provider arguments
applystringNoWhen a model switch takes effect: turn_end (default) or now
stall_ms, max_wait_msintegerNoBounds on a model switch that waits for the turn to end
endpointingintegerNoSilence-based endpointing in milliseconds. Supported by Nova, Qwen3, xAI, OpenAI transcription, and ElevenLabs Scribe. Defaults vary; see the mapping below.
eou_thresholdnumber or objectNoEnd-of-turn confidence, 0–1, for VoiceRun STT Model, Deepgram Flux, and Inworld. Language maps are supported only by VoiceRun.
eot_timeout_msintegerNoAdvanced end-of-turn timeout for VoiceRun STT Model and Deepgram Flux
vad_stop_msintegerNoVoiceRun STT Model silence before end-of-turn scoring
allowed_languagesstringNoVoiceRun 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 fieldModel/providerNative settingRouter behavior when omitted
endpointingDeepgram Novaendpointing300 ms
endpointingQwen3 ASRturn_detection.silence_duration_ms500 ms
endpointingxAIendpointing800 ms
endpointingOpenAI transcriptionturn_detection.silence_duration_ms500 ms
endpointingElevenLabs Scribevad_silence_threshold_secs (milliseconds divided by 1000)Provider-managed; not sent
eou_thresholdDeepgram Fluxeot_threshold0.8
eou_thresholdInworldendOfTurnConfidenceThreshold0.8
eou_thresholdVoiceRun ASReou_thresholdDeployment default
eot_timeout_msDeepgram Fluxeot_timeout_ms2500 ms
eot_timeout_msVoiceRun ASReot_timeout_msDeployment 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_threshold is 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; endpointing controls 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_ms control. 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; endpointing and eot_timeout_ms must be positive. Malformed values return invalid_request without 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 null return invalid_request.
  • Nova's separate utterance_end_ms word-gap detector remains fixed at 1000 ms. Either detector can close a turn, so endpointing is not a guaranteed minimum turn duration.
  • Deepgram Flux uses eou_threshold and eot_timeout_ms, not endpointing. Unsupported models report endpointing in a warning and omit it from the applied settings. Nova also ignores it with a warning when turn_detection is anything other than provider, 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.

CodeMeaning
configuration_timeoutNo valid session.update within ten seconds of session.created
provider_connect_timeoutThe model's provider did not connect within ten seconds
operation_pendingA command arrived while the session was starting, recovering, or switching models; retry after the outcome. Audio sent meanwhile is held, up to eight seconds
backpressureMore than eight seconds of audio was held, or the provider stopped accepting audio
transcript_limitA turn's transcript exceeded the size limit
stream_expiredThe session's billing stream ended
transition_unconfirmedA 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_detectionThe turn ends whenEvents
provider (default; alias server)the model's own endpointing produces a finaltranscription.delta, turn.ended
clientyou send input_audio_buffer.committranscription.delta, turn.ended with commit_id and flush_outcome
silerothe Router's voice-activity detector hears min_silence_ms of silence after speechspeech.started, speech.stopped, transcription.delta, turn.ended
smart_turnthe 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 waitsas 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_outcomeMeaning
flushedThe model finalized the pending audio
unsupportedThe model cannot finalize on demand; the turn closed with the text on hand
timeoutNo final within one second
provider_errorThe finalize request failed
supersededA second commit arrived first
model_changed, mode_changed, provider_failedA 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}}
FieldDefaultRangeMeaning
min_silence_ms25664–3000Silence that ends a turn judged complete, and every silero turn
uncertain_silence_ms900min_silence_ms–max_silence_msSilence that ends a turn with no usable score
max_silence_ms2200up to 10000Silence that ends any turn
complete_threshold0.70–1, above incomplete_thresholdsemantic score at or above which the turn is complete. smart_turn uses 0.5
incomplete_threshold0.350–1Score below which the turn waits max_silence_ms
speech_threshold0.50.01–0.99Voice-activity probability that counts as speech
request_timeout_ms60050–2000Time allowed for one completion score
min_request_interval_ms100100–2000Minimum gap between score requests
transcript_settle_ms200–1000How long the transcript must be unchanged before it is scored
flush_timeout_ms5050–1000How 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 with session.update_pending (applies_at: "turn_end"). It is forced earlier when no transcript arrives for stall_ms (default 2000) or after max_wait_ms (default 10000); both are capped at five minutes. Under client turn detection, stall_ms defaults to max_wait_ms, so a switch waits for your commit.
  • apply: "now", or no turn in progress: the switch starts at once (session.update_pending with applies_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 endpointing or Flux eot_threshold) are rejected with invalid_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_keys lists the forwarded keys.
  • Changing them reconnects the provider on the same model.
transcriptionwebsocketsttmessages