Skip to main content
Beta. The multi-context WebSocket is in beta. The message protocol may change based on feedback before general availability. The WebSocket at /v1/tts/ws remains fully supported.
The multi-context WebSocket carries several independent streams of speech, called contexts, over a single connection. Each context has its own text buffer, voice, and lifecycle. You can open a new context for the next reply, or cancel the one that is speaking, without reconnecting. It is designed for voice agents: when a user interrupts, cancel the current reply and start the next one on the same socket.

When to use it

For help choosing between REST, HTTP streaming, and the WebSockets, see Choosing an API.

Endpoint

For the full message schemas, see the Multi-context WebSocket reference.

Authenticating

Send your API key in the chariotai-api-key header. Browser clients that cannot set handshake headers can pass it as a query parameter instead:
If authentication fails, the server accepts the socket, sends an error frame, and closes with code 1008.

Concepts

Contexts

A context is one stream of speech, usually one turn of a conversation. You name it with a context_id of your choice, and every message in both directions carries it.
  • The first input.text with a new context_id opens the context. There is no separate “create” message.
  • Each context has its own voice and speed, set on its first message or taken from the connection defaults.
  • Up to 5 contexts can be open on one connection at the same time.
  • A context ends with exactly one context.done message. Nothing is sent for that context after it.

Segments

Within a context, each input.flush turns the text buffered so far into one segment and generates it. Segments are numbered from 0 within each context and generated in order. All audio for a segment is tagged with its segment number, between an audio.start and an audio.done.

How a context works

1

Send text

Send input.text with a context_id. The first message opens the context. Text accumulates in that context’s buffer. Nothing is generated yet.
2

Flush to generate

Send input.flush for the context. Its buffered text is generated as the next segment and streamed back. The context stays open for more text.
3

Close or cancel

Send context.close when the reply is complete. The rest of the buffer is generated, all audio is delivered, and you receive context.done with reason completed. To stop immediately instead, send context.cancel.
Flushing is what triggers generation. Flush at natural boundaries, such as the end of each sentence, so audio starts early and sounds natural.

Interruptions (barge-in)

When your user starts speaking over the agent, cancel the context that is speaking and start the next reply in a new context:
context.cancel stops the context at once:
  • Generation in progress for the context is stopped.
  • Its queued segments and unflushed text are discarded.
  • The server replies with context.done and reason cancelled. No audio for that context is sent after it. Audio that arrives between your cancel and its context.done was already in flight, so discard it.
The server cannot recall audio it has already delivered. When you cancel, also stop playback and clear any audio for that context that is buffered in your player.
Use a new context_id for every turn, for example turn-1, turn-2. A finished context_id can be reused after its context.done, and it then starts a fresh context, but a new id per turn keeps late messages from an old turn easy to tell apart.

Connection parameters

Pass these as query parameters on the connection URL:
string
UUID of the default voice. Used by every context that does not name its own voice_id. Optional: without it, each context must set voice_id on its first message.
string
default:"chariot-v0"
Use chariot-v0 or chariot-multilingual-v1. Fixed for the connection. Every voice used must support this model. See Models.
number
default:"1.0"
Default speaking rate for contexts. Choose a value from the voice’s speeds list for the model. Chariot v0 supports only 1.0.
integer
default:"0"
An integer from 0 to 4. Higher values trade quality passes for lower latency. Applies to the whole connection.
number
default:"60"
Seconds the server waits for any client message before closing the connection as idle. Must be greater than 0 and at most 300.
boolean
default:"false"
Controls whether Chariot stores the generated audio. When true, the audio for each generated segment is stored and you can retrieve it from your TTS history.

Message protocol

Every message, including audio, is a JSON text frame.

Client to server

message
Append text to a context’s buffer. The first input.text for a context_id opens the context, and only that message may set voice_id and speed. An empty text sends nothing for generation and keeps the context alive.
message
Generate the context’s buffered text as the next segment. The context stays open.
message
No more text is coming for this context. The rest of its buffer is generated, all audio is delivered, and then context.done arrives with reason completed.
message
End the context now. See Interruptions. Cancelling a context that has already ended is ignored.
message
Finish every open context as if each received context.close, then close the connection.
message
Keep the connection alive. The server answers with pong.
context_id is 1 to 128 characters of letters, digits, _, -, ., and :. If you omit it, the message applies to a context named default.

Server to client

message
The connection is authenticated and ready.
message
A segment of a context is about to stream.
message
Audio for a segment: base64-encoded raw PCM, signed 16-bit little-endian, mono.
message
The segment has finished streaming. credits_utilized is what this segment cost.
message
The context has ended. This is always the last message for a context. Carries the reason, the number of completed segments, and the total credits_utilized. When reason is error, it also carries a code and message.
message
A message was rejected. Carries a code, a message, and the context_id it concerns, if any. The connection stays open unless the error is insufficient_credits or idle_timeout.
message
Sent after session.close once every context has finished. The server then closes the connection with code 1000.

Why a context ended

Error codes

Errors in an error message, or in a context.done with reason error. Only insufficient_credits and idle_timeout close the connection; every other error affects one message or one context.

Keeping contexts alive

Two timers apply:
  • Context idle timeout, 20 seconds. A context with nothing generating and no message for 20 seconds ends with reason expired, and its unflushed text is discarded. To keep a context open while your agent waits, for example on a slow tool call, send an empty input.text for it:
  • Connection idle timeout. If no message of any kind arrives within idle_timeout (default 60 seconds), the connection closes with code 1008. Send ping while your client is listening but not sending.

Audio format

Audio arrives in audio.chunk messages as base64-encoded raw PCM: signed 16-bit little-endian, mono, at the sample_rate given in audio.start (44100 Hz). The encoding field of session.ready names the format: pcm_s16le.

Close codes

Billing

Billing is per segment, at 1 credit per input character, exactly as on /v1/tts/ws. Each segment is charged when it finishes, and audio.done reports its cost. context.done reports the total for the context. When you cancel a context:
  • A segment that had started generating is charged in full.
  • Segments that were still queued, and text that was never flushed, are not charged.
The server checks your available credits before accepting each input.text, across all open contexts. If your credits cannot cover the pending text, you receive insufficient_credits and the connection closes with 4402.

Concurrency

Each segment that is generating uses one slot of your workspace’s concurrency limit, the same limit shared with the HTTP endpoints and /v1/tts/ws. Open contexts that are not generating use no slot. If a segment is refused a slot, only its context ends, with reason error and code concurrency_limit. The connection and your other contexts continue.

Limits

Example

A two-turn conversation where the user interrupts the first reply. Replace VOICE_ID with a voice UUID that supports chariot-multilingual-v1.
Python