Skip to content
AIZACDeveloper Platform

Low-latency streaming transcription over a single WebSocket connection: connect, send a start frame, stream raw audio, send an end frame to flush, and receive result messages (plus lifecycle events) as each utterance is transcribed. After the stream ends, retrieve the stored session result with the REST endpoint documented below.

WebSocket connection

FieldValue
Endpointwss://api.denthub.ai/stt/realtime/stream
Query?key=<key>
AudioPCM16LE Β· 16000 Hz Β· mono Β· binary frames
Max conns/pod200 (HTTP 503 when exceeded)

start↑ first frame (text)

FieldTypeRequiredValue
typestringyes"start"
vadobjectnosegmentation tuning β€” see below
Audio is fixed server-side (16Β kHz mono PCM16LE) and is no longer declared by the client β€” sending sample_rate/codec/channels is unnecessary (accepted only if they match what the server expects, kept around for legacy clients). lang/backend are also fixed server-side (English STT, densper3) β€” any client-sent values are ignored. num_speakers/resume_* are reserved fields: accepted, but currently a no-op.

vad options

FieldDefaultRangeMeaning
min_silence_ms640100–3000silence that ends an utterance (lower = fast cuts, higher = merge sentences)
speech_pad_ms2000–1000padding kept around each clip (protects onset/offset phonemes)
threshold0.60.05–0.95speech-start sensitivity (lower = more sensitive, higher = conservative)
min_speech_ms19232–2000shorter blips are treated as noise
Each field must fall within its range, and threshold must be β‰₯ 0.4, and 2 Γ— speech_pad_ms must be ≀ min_silence_ms. vad is only valid in the first start frame, before any audio β€” it cannot change mid-stream. Invalid values are rejected outright (error frame + connection close): the sent value is always the applied value, there is no silent correction.

Playground presets

Presetvad
Fast cuts{"min_silence_ms":200,"speech_pad_ms":100,"threshold":0.6,"min_speech_ms":192}
Default{"min_silence_ms":300,"speech_pad_ms":100,"threshold":0.6,"min_speech_ms":192}
The Playground always sends the selected values as an explicit vad snapshot. Its Default preset is an editable starting point and does not replace the server defaults listed above.
Minimal
{ "type": "start" }
With vad tuning
{
  "type": "start",
  "vad": { "min_silence_ms": 400, "speech_pad_ms": 150 }
}

audio↑ binary frames

Send raw PCM16LE bytes as binary WebSocket frames β€” no WAV header, no base64, no JSON wrapping. Any frame size works; send continuously, without gating on silence yourself.

The gateway re-frames your stream internally into 32Β ms VAD windows and detects utterance boundaries server-side, so you never need to run your own voice-activity detection.

end↑ graceful close (text)

Send this text frame to flush any pending utterance and close cleanly. After sending it, you may close the WebSocket yourself, or wait for the server to close it once flushing finishes.

{ "type": "end" }

result↓ one per utterance

{
  "type": "result",
  "utt_index": 0,
  "session_id": "b1f7…",
  "abs_start_utc": "2026-07-10T05:12:01.80Z",
  "abs_end_utc":   "2026-07-10T05:12:05.20Z",
  "duration_ms": 3400,
  "text": "Patient presents with sensitivity on the upper right quadrant.",
  "segments": [
    { "start_ms": 800, "end_ms": 4200,
      "text": "Patient presents with sensitivity…", "language": "en" }
  ],
  "words": [ { "word": "Patient", "start_ms": 800, "end_ms": 1180 }, … ],
  "status": "ok",
  "forced_cut": false,
  "continues": false
}
FieldTypeDescription
utt_indexint0-based, monotonic per connection
session_iduuidConnection session ID
abs_start_utc / abs_end_utcstring (ISO 8601)Absolute utterance start/end (UTC)
duration_msintUtterance duration (sample-derived)
textstringFull transcription text
segments[]arraystart_ms / end_ms / text / language, recording-relative
words[]arrayword / start_ms / end_ms, recording-relative
statusstring"ok" Β· "empty" Β· "failed"
forced_cutbooltrue if the utterance was force-split at max length
continuesbooltrue if the next utt_index continues this utterance
Every timestamp here β€” abs_*_utc, and each start_ms/end_ms inside segments[] and words[] β€” is recording-relative, derived from a sample-count clock, not message-arrival time. Use these to line results up against the audio you sent, even under network jitter.

↓ session Β· warning Β· error

{"type":"session","event":"started","session_id":"…"}   // started Β· ended
{"type":"warning","code":"SLOW_CONSUMER"}                  // backpressure
{"type":"utterance_dropped","utt_index":8,"code":"STT_OVERLOAD"}
{"type":"error","utt_index":9,"code":"STT_TIMEOUT","message":"…","retryable":true}
  • session β€” connection lifecycle: started and ended.
  • warning β€” backpressure signal (SLOW_CONSUMER): you're sending audio faster than the gateway can process; slow down or check your network.
  • utterance_dropped β€” the STT backend was overloaded and this utterance was skipped; the gap in utt_index tells you which one.
  • error β€” a per-utterance failure; check retryable to decide whether to wait or move on.

Error codes

The error event carries a code and a retryable flag. Non-retryable errors close the connection; retryable ones are per-utterance.

CodeMeaningRetryable
INVALID_FRAMEA start value broke a rule β€” a vad range/cross-rule violation, or an audio-format field that doesn't match the fixed contractNo β€” fix and reconnect
STT_TIMEOUTThe STT backend timed out on this utteranceYes β€” wait; the backend retries
STT_BACKEND_5XXThe STT backend returned a 5xx for this utteranceYes
STT_PARSE_ERRORThe backend response could not be parsedNo β€” log and continue
INTERNALUnexpected server-side errorNo β€” log and reconnect

warning events are non-fatal backpressure signals β€” the socket stays open:

CodeMeaning
SLOW_CONSUMERYou're sending audio faster than the gateway can process β€” slow down or check your network
RING_BUFFER_NEAR_FULLThe ~3 s audio buffer is nearly full and audio may start dropping β€” slow down
The server never silently corrects a bad start β€” it sends an error frame and closes; what you send is what applies.

Close codes

CodeMeaning
1000Normal close, after you sent end
1001Going away β€” server is draining; reconnect to a new session
1011Internal error β€” log it and reconnect
An invalid or missing ?key= causes the WebSocket handshake itself to be rejected with HTTP 401 β€” the socket never opens. This is distinct from the 1000 / 1001 / 1011 close codes, which apply only to an already-open connection.

Retrieve session result

After the WebSocket stream ends, fetch the stored session record β€” this is how you retrieve the finalized recording and the billed usage once streaming is done. The response shape is below.

GETapi.denthub.ai/stt/realtime/{sessionId}

Auth: this REST call uses the X-License-Key header (unlike the WS handshake, which uses the ?key= query param β€” browsers can't set WS headers). Returns 404 if the session does not exist.

Path parameters

FieldTypeNotes
sessionIdstring (uuid)required
Request
curl https://api.denthub.ai/stt/realtime/<sessionId> \
  -H "X-License-Key: <license-key>"
Response Β· 200
{
  "sessionId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "COMPLETED",
  "quota": 42.5,
  "unit": "seconds",
  "recordingUrl": "recordings/2026/07/13/<session>.wav",
  "segments": [
    { "startMs": 0, "endMs": 3200, "result": {}, "isFinal": true }
  ]
}

Response fields

FieldTypeNotes
sessionIdstringthe realtime session id
statusenumPENDING Β· PROCESSING Β· COMPLETED Β· FAILED Β· ERROR Β· PARTIAL
quotanumberbillable usage (max segment end time)
unitstringquota unit (e.g. seconds)
recordingUrlstringobject-storage key of the finalized recording
segments[]object[]stored segments β€” { startMs, endMs, result, isFinal }
This stored shape differs from the live WS result message: segments here use camelCase fields (startMs/endMs) as integer milliseconds, not the WS message's snake_case start_ms/end_ms.