/v2/tts/ws is designed for a streaming text source. An LLM can still be generating the end of a response while Bland is already synthesizing and returning audio for the beginning.
The connection and turn model
Keep one WebSocket open for one conversation. The voice and audio format are fixed byinit for the life of that connection.
A context_id identifies one turn of speech. Reuse the same ID for every text delta in that turn, then send end_of_turn when the LLM is finished.
The socket is not a queue of independent requests and does not multiplex simultaneous turns. Exactly one turn is active at a time.
Bland buffers incoming text
Send tokens as soon as you receive them. Bland accumulates the deltas, detects useful speech boundaries, and releases text to synthesis when it has enough context for natural output. You do not need to:- Wait for complete sentences before sending text.
- Build a character threshold or timer.
- Send a flush command after every fragment.
- Predict how much text the model needs for natural prosody.
end_of_turn tells Bland to synthesize that remaining tail. The buffering thresholds are tuned internally and are not part of the API contract.
Preemption and barge-in
Sendingspeak with a new context_id immediately preempts the active turn:
- Bland stops forwarding new audio for the old turn.
- In-flight synthesis for the old turn is aborted.
- Unsynthesized buffered text for the old turn is discarded.
- The old turn receives
utterance_endwith reasonpreempted. - If the new turn passes admission, it receives
utterance_startand begins buffering.
context_id receives an error without utterance_start or utterance_end.
context_id from your local playback queue.
Cancellation without replacement
Usecancel when a user interrupts but the replacement response is not ready:
cancelled. Late cancel or end_of_turn messages for a context that is no longer active are ignored.
When replacement tokens arrive, start them under a new ID. Use unique context IDs so application logs and playback state remain unambiguous.
Completing a turn and closing a session
These operations are different:end_of_turnflushes the active turn’s remaining text and lets its audio complete.closecancels any active turn, settles the session, sendsdone, and closes the socket.
- Send
end_of_turnafter the LLM finishes. - Keep reading audio.
- Wait for
utterance_endwith reasoncomplete. - Send
closewhen the conversation itself is finished.
close immediately after the final text can truncate the buffered tail.
Buffering audio for playback
Bland handles input text buffering. Your client still owns output playback buffering. The server sends raw audio frames as soon as they are generated, which may be faster than real-time playback. Enqueue them in order and let the audio device or media transport consume them at the negotiated sample rate. Associate incoming binary frames with the active context betweenutterance_start and utterance_end:
pcm_s16le, each second of mono audio is 96,000 bytes: 48,000 samples * 2 bytes. For 8 kHz mu-law, each second is 8,000 bytes.
Billing on interrupted turns
Billing follows delivered audio:- A synthesized text chunk becomes billable when the server accepts its first audio frame for delivery.
- The complete chunk is billed because Bland does not have frame-to-character attribution.
- Text still buffered, queued, or synthesized without delivering a frame is not billed.
- A preempted or cancelled turn can therefore have a partial set of its text billed.
- Each turn settles separately and is subject to the current $0.001 minimum charge when it delivers billable audio.
Concurrency and long-lived sockets
A successfully initialized WebSocket holds one organization-wide speech concurrency slot until it closes. The slot remains occupied between turns because the connection keeps a warm synthesis session reserved. Close sockets you no longer need. If the organization is at its concurrency cap, initialization returnsrate_limited. See Speech Limits for default limits and retry guidance.
Recommended client state
Track these values explicitly:
Treat
utterance_end as the terminal for a turn and done as the terminal for the session. Do not infer completion from a pause in binary frames.
Docs for agents: llms.txt