Skip to main content
Voice mode runs the same assistant, the same questions, the same session as the text intake. A client can start a session in chat, continue it by voice, and switch back, and the conversation history follows. Everything on this page is the wire contract; the machine-readable source of truth is docs/asyncapi/voice.yaml in the API repo, and this page tracks it.

The flow at a glance

  1. Create or reuse an assistant session (REST, same as text).
  2. Mint a single-use ticket (REST, authenticated).
  3. Open the WebSocket with the ticket (no Bearer header on a raw new WebSocket(), which is exactly why the ticket exists).
  4. Say hello, get ready, then stream microphone audio and handle server frames.

Audio format

Send small frames continuously (20 ms frames work well). The server does the end-of-speech detection; you never decide when an utterance is finished.

Client to server messages

Server to client messages

The engine field and live mode

ready.engine is either "relay" or "live" (absent means "relay", older servers do not send it). The frame protocol is identical in both modes. A client written against this page works on both. Two timing rules matter, and both are safe to follow unconditionally:
  1. Render transcripts by turn_no, never by arrival order. In live mode transcript.user can arrive after the answer’s audio.begin, because transcription streams while the model is already speaking.
  2. Never string-match turn.message. In live mode it is the transcript of what the assistant actually said, phrased naturally, not a fixed template.

Barge-in, the part integrators get wrong

The user is allowed to interrupt the assistant mid-sentence. The full sequence:
  1. User starts talking while audio is playing.
  2. Server sends audio.cancel for that turn_no.
  3. You stop playback immediately, flush the buffer, and send playback.interrupted with the milliseconds actually played.
  4. The conversation continues; the transcript records only what was really heard.

Close codes

Billing

Voice turns meter into the same seconds bucket as everything else. Each turn frame carries billed_seconds, and metered frames surface the remaining balance so you can warn the user before the tank runs dry.

Sandbox

sk_test_ sessions run the full protocol, including endpointing and frames, but never call a vendor and never bill. Ideal for integration tests.