Skip to main content

Streaming

Every message you send and receive on /v1/stream, in the order a session uses them, and how a stream ends.

Open a session #

Connect to /v1/stream with the subprotocol dicon.v1 and send session.create as the first message:

json
{"type":"session.create","protocol_version":"1.0","mode":"mt_asr","expected_num_speakers":"auto"}
  • protocol_version — "1.0".
  • mode — mt_asr (multi-talker) or ts_asr (target speaker).
  • expected_num_speakers — "auto" or an integer, within the engine's limit below.

What a session may ask for depends on the worker's engine. HEAT4, the one served, accepts mt_asr only and 1 to 8 speakers: a session keeps up to eight speakers apart and transcribes at most four at once, and "auto" means up to eight. The dicon engine accepts mt_asr and ts_asr with 1 to 4 speakers. GET /v1/metadata reports the engine as limits.asr_engine and the counts it accepts as limits.expected_num_speakers.

The server replies with session.accepted, carrying the session and request IDs and the initial state. An older two-request form also exists; use /v1/stream for new clients.

Target-speaker enrollment #

In ts_asr (the dicon engine only), send the voice to follow before stream.start. audio_b64 is base64 little-endian signed 16-bit mono PCM; Limits gives its length.

json
{"type":"enrollment.submit","audio_b64":"... little-endian signed 16-bit mono PCM ..."}

Sending audio #

Send {"type":"stream.start"}, then the audio as binary messages of little-endian signed 16-bit mono PCM at 16 kHz. A chunk may end anywhere on a whole sample; empty or odd-byte payloads are rejected. A JSON audio.chunk message with a base64 payload also works, but binary is more compact.

Finish with {"type":"stream.end"}, or cancel with {"type":"session.cancel"}.

Lifecycle #

text
MT: created -> ready -> streaming -> flushing -> completed
TS: created -> enrolling -> ready -> streaming -> flushing -> completed
any nonterminal -> cancelled | failed

Server events #

Every server event carries the session and request IDs and a seq that increases through the session. A speaker's speaker.update comes before that speaker's transcript.

  • session.state
  • speaker.update — candidate, admitted, suppressed, target_locked, target_not_found
  • transcript.partial
  • transcript.final
  • session.completed — with reason: end_of_stream, cancelled, timeout or failed
  • error
  • drain.notice — the server you are on is being replaced
  • session.resumed — after a successful resume

After drain.notice the server delivers a terminal event and closes with 1001, reason server draining. It is planned maintenance, not a network failure: finish and reconnect on a new session.

Partial transcripts #

Partials are append-only deltas, not the transcript so far, so they stay small however long the session runs:

json
{"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 — how many committed words you should already hold for this speaker. Committed words never change.
  • appended — new committed words; add them to the end.
  • open_word — the one word still being recognised. It replaces the previous open_word; "" means there is none.
  • revision — increases with each partial for this speaker.

If your committed word count does not equal stable_prefix_words, you missed a partial because your client was reading too slowly. Show a gap and wait for transcript.final, which is never dropped and carries the speaker's complete text: replace what you show for that speaker with it. Final text never changes.

Close codes #

  • 1000 — normal completion, after session.completed.
  • 1001, reason server draining — the server is being replaced.
  • 4400, or 1013 if retrying may help — a protocol or validation failure.
  • 1006 — never sent by the server: your client sees it when the connection drops with no close frame, for example on a network failure or a lost server.

There is exactly one terminal event per session. A dropped session cannot be resumed unless the service enables it; start a new session. Resume lists the codes used when reattaching, where 4400 means resume is off.

Two-request form (legacy) #

Older clients create a session over HTTP, then connect to the WebSocket URL it returns. POST /v1/sessions takes:

json
{
  "protocol_version": "1.0",
  "mode": "mt_asr",
  "audio_format": {"encoding": "pcm_s16le", "sample_rate": 16000, "channels": 1},
  "expected_num_speakers": "auto",
  "client_request_id": "optional-id"
}

client_request_id is optional; Limits gives its format. The response carries session_id, request_id, the initial state and a websocket_url, or a retryable 503 when the service is at capacity.

The WebSocket must reach the same server that created the session, which needs worker affinity that a load-balanced service does not provide. Use /v1/stream instead.

A session_id is not a bearer token: only the key that created a session may attach to it. A refused attach closes 4409 with reason unknown or already connected session, whether the session does not exist, belongs to someone else or is already attached.