Overview
The streaming WebSocket API is designed for real-time, agent-driven applications where text arrives incrementally (for example, token-by-token from an LLM). Unlike the single-requesttext-to-speech protocol, this endpoint keeps a persistent session open: you configure the voice once, then push text as it becomes available and receive audio chunks back as they are generated.
The server buffers and batches incoming text intelligently before dispatching it to TTS workers, so you can send anything from a full sentence to a single token per message without worrying about segmentation.
This is a distinct endpoint (
/ws) from the single-shot WebSocket Streaming API (/open). Use this page for incremental / agent streaming, and the overview page for one-shot generations.Connection
Connect to the/ws endpoint with your API key:
Authentication is handled during the WebSocket handshake via the
x-api-key header or an x-api-key query parameter.
On a successful connection, the server sends a welcome message before you send anything:
Protocol flow
A typical session follows this sequence:1
Connect
Open the WebSocket with your
x-api-key and wait for the status / connected welcome message.2
Configure
Send a
stream-config message with at least a model. This must be sent before any text.3
Stream text
Send one or more
stream-text messages as text becomes available. Audio chunks stream back as JSON messages.4
End or cancel
Send
end-stream to flush and finish the current turn, or cancel to abort in-flight generation and clear buffers.Message format
Every message is a JSON object with anaction field. Request payloads can be placed in either a data or a config object — both are accepted interchangeably.
Client actions
stream-config
Sets the session-level TTS configuration. Must be sent before any stream-text message, otherwise the server responds with an error:
Example
Parameters
string
required
Model ID to use for generation (e.g.,
dd-etts-3.0). Only required field.string
UUID of the voice/emotion prompt that controls which voice the engine uses. Can be changed mid-stream via inline config tags.
string
Base64-encoded WAV to clone a voice inline, as an alternative to
voicePromptId. Expects mono 48 kHz audio, up to 1 MB.string
Language/locale code for the generated speech (e.g.,
en-US, he-IL, es-MX). Determines the language model and pronunciation rules. Can be changed mid-stream via inline config tags.string
Output audio format, e.g.,
s16le, wav, or mp3. Defaults to the worker’s default when omitted.integer
Output sample rate in Hz (e.g.,
16000, 24000, 48000).boolean
default:"true"
Prioritize low latency.
true gives the session real-time processing priority; false uses standard priority.boolean
default:"true"
Apply audio cleanup processing.
string
Target speaker gender,
male or female. Used for language-specific handling such as Hebrew diacritics.integer
default:"50"
Maximum time in milliseconds to wait for more text before flushing the first buffered segment to a worker. Lower values reduce time-to-first-audio.
boolean
default:"false"
Enable emoji tokens to be interpreted as inline locale/voice configuration hints.
stream-text
Sends text to be synthesized. The server buffers and batches text before dispatching it to workers, so messages can be as small as a single token.
text field is also accepted:
string
required
The text (or text fragment) to synthesize.
string
Optional opaque context blob forwarded to the worker with this flush. Useful for correlating audio with an upstream request.
Inline config changes
locale and voicePromptId can be changed mid-stream by embedding a <config /> XML tag in the text:
- Text before the tag is synthesized with the previous config.
- The new config applies to all text after the tag.
- Tags may be split across multiple
stream-textmessages — incomplete tags are buffered until the closing>arrives. - Closing tags (
</config>) are stripped and ignored.
end-stream
end-stream marks the end of a turn — a logical unit of text that should be spoken as one continuous utterance (for example, a single assistant reply).
Because the server buffers and batches incoming text to optimize prosody and latency, it does not know when you have finished sending text unless you tell it. end-stream does two things:
- Flushes the tail as the final segment. Any remaining buffered text is synthesized and marked as the last segment of the turn. This gives the tail clean sentence-final intonation instead of the “more is coming” prosody used for mid-stream segments.
- Closes the turn, so the final audio chunk is tagged with
"isFinal": true. This is the signal your client should wait for to know the whole turn is done.
Even without
end-stream, buffered text is eventually flushed and synthesized on its own — the tail is not lost. What you lose by omitting it is the explicit turn-end signal: no chunk is tagged "isFinal": true, so your client can’t tell a turn boundary from an ordinary segment boundary, and the tail is spoken as a continuation rather than a clean ending. Always send end-stream to close a turn cleanly.Multi-turn conversations
The connection is persistent and can be reused across many turns. After you receive theisFinal chunk for one turn, simply start sending stream-text again for the next turn — the session configuration from your initial stream-config is retained. Send end-stream again to close each subsequent turn.
This makes a single connection ideal for a back-and-forth conversation: keep it open for the whole session, configure once, and bracket each assistant reply with stream-text messages followed by an end-stream.
cancel and ping
action
Aborts in-flight generation and clears all buffers. Useful for barge-in — when the user interrupts, cancel the current turn so you stop generating audio the user will never hear. The server emits a synthetic finish message with
"isCancelled": true, after which you can start a new turn.action
Keepalive. The server replies with
{"action": "pong"}. Use it to hold a conversation connection open between turns.Server responses
Audio chunks
Audio arrives as JSON messages. Decode the base64data field and append the bytes in order.
string
Identifier of the generation this chunk belongs to. Use it to correlate chunks across concurrent generations.
integer
Sequential chunk index within a generation, starting from 0.
string
Base64-encoded audio data for this chunk.
boolean
true when this is the final chunk of a generation.boolean
true on the final chunk of the whole turn. Only produced after you send end-stream.boolean
true when the generation was aborted via cancel.end-stream):
Errors
Code example
The following raw Python client connects, configures a session, streams text token-by-token, and collects audio chunks until the turn is final.Streaming from an LLM
The streaming API is built for exactly this pattern: forward tokens from a streaming LLM response intostream-text as they arrive, then send end-stream once the LLM finishes, and play the audio chunks as they come back.
Because sending text and receiving audio happen concurrently, run a producer (LLM → stream-text) and a consumer (audio chunks → playback) at the same time. The key rule is: call end-stream as soon as the LLM has produced its last token, and treat the isFinal chunk as “the assistant has finished speaking.”
