Streaming
The production create-and-stream path and the stream protocol.
Create a session on the stream
Connect to /v1/stream with subprotocol dicon.v1, then make the first message:
{"type":"session.create","protocol_version":"1.0","mode":"mt_asr","expected_num_speakers":"auto"}The server replies with session.accepted, including stable session and request IDs and the initial lifecycle state. Creation and all mutable streaming state share one connection, so a multi-worker load balancer needs no affinity between an HTTP request and a later WebSocket handshake. This is the production and example-client path; the legacy per-session route /v1/sessions/{session_id}/stream also exists.
Message fields
protocol_version—"1.0".mode—mt_asr(multi-talker) orts_asr(target speaker).expected_num_speakers—"auto"or an integer 1 to 4.
Target-speaker enrollment
For TS-ASR, first send the enrollment audio:
{"type":"enrollment.submit","audio_b64":"... little-endian signed 16-bit mono PCM ..."}The audio_b64 payload is little-endian signed 16-bit mono PCM.
Sending audio
Send {"type":"stream.start"}, then send audio. The efficient form is binary messages of little-endian signed 16-bit mono PCM at 16 kHz; a JSON audio.chunk message with a base64 payload is available for compatibility. Arbitrary complete-sample chunk boundaries are accepted; empty or odd-byte payloads are rejected.
Finish with {"type":"stream.end"}, or cancel with {"type":"session.cancel"}.
Lifecycle
MT: created -> ready -> streaming -> flushing -> completed
TS: created -> enrolling -> ready -> streaming -> flushing -> completed
any nonterminal -> cancelled | failedServer events
Every server event carries the session and request IDs and a monotonically increasing session seq. Speaker discovery or update precedes that row's transcript. The events are:
session.statespeaker.update—candidate,admitted,suppressed,target_locked,target_not_foundtranscript.partialtranscript.finalsession.completederrordrain.notice— once, when an intentional drain startssession.resumed— after a successful same-instance resume replay
Partial transcripts
Partials are append-only deltas, not the transcript so far, so their size does not grow with session length:
{"type":"transcript.partial","speaker":"speaker_0","revision":7,
"stable_prefix_words":42,"appended":["quick","brown"],"open_word":"fo",
"audio_end_s":12.4}stable_prefix_words is how many committed words you should already hold; append appended to them. RNNT greedy output is monotonic, so a committed word never revises. open_word is the single in-flight word and replaces whatever open_word you were last given ("" means there is none). revision increases per speaker.
Missed partials, finals, disconnects
If your committed word count does not equal stable_prefix_words, you missed a partial under backpressure: render a gap and wait for transcript.final, which always carries that speaker's complete text and is never dropped. The server merges rather than discards partials wherever it can, so a gap means the connection was already flagged as a slow consumer.
Final text never revises, and there is exactly one terminal event. By default a disconnected session cannot be resumed; create a new session.