Skip to main content
For interactive, realtime use cases the Voice API exposes a WebSocket endpoint that bridges your connection to the synthesis engine. Authenticate by passing your API key as the api_key query parameter (browsers and most WS clients can’t set custom headers during the handshake).
A valid key upgrades the connection (101 Switching Protocols); an invalid key fails the handshake with 401. On the JSON control frames you send, the service replaces voice_id with your enrolled engine voice for that id — so you always reference your own Flam voice ids (the 24-char ids from List voices), never the engine’s. Everything else is forwarded verbatim in both directions.

Session model

One connection is one session; a session can run up to 4 concurrent contexts (independent synthesis streams), each keyed by a client-chosen context_id. You start a context, optionally keep appending text to it (ideal for LLM output arriving in chunks), and finalize it when the turn is done. Audio for each context streams back as it’s synthesized.
  • You send — JSON text frames. The type field dispatches the message (see below).
  • You receive — JSON text frames (status/control) interleaved with binary frames (raw audio).
  • Limits — text frames over 1 MB close the socket (1009); a session idle for 600 s is closed by the server (session_ended, reason idle); per-key connection caps close with 4429.

Messages you send

task_start — begin a context

task_continue — append text to an open context

Send as many as you like; set finalize: true on the last one to flush and finish the context.

task_cancel — abort a context

session_end — graceful close

The server replies with session_ended and closes the socket.

Messages you receive

text frame
{context_id, sample_rate, audio_format} — the effective rate and format for the context. Use these (not what you requested) to configure playback.
text frame + binary frame
{context_id, sentence_index, chunk_index, audio_duration_s, byte_length} — immediately followed by one binary frame of raw PCM (or opus) bytes. Pair them in order: each audio_chunk announces the binary frame that follows it.
text frame
{context_id, sentence_index, text, audio_duration_s} — one sentence fully synthesized.
text frame
{context_id, num_sentences, total_audio_duration_s, profile, trace_id} — the context is complete. profile carries timing detail (ttfb_ms, generate_ms, overall_rtf, …).
text frame
{context_id, sentences_completed, sentences_dropped, trace_id} — reply to task_cancel.
text frame
{context_id, sentences_completed, code, trace_id} — terminal failure for one context.
text frame
{context_id?, code, message, fatal} — validation or billing errors. When fatal is true the session is closing; otherwise only the offending message was rejected.
text frame
{reason} — idle or client_request. The socket closes after this frame.

Audio formats

Binary frames carry raw PCM (no container) in the context’s output_format: Audio is mono at the effective sample_rate from task_started (native 24000 Hz). Wrap or play it client-side; nothing on this path returns WAV.

Billing

WebSocket synthesis is metered exactly like /speak and SSE streaming: you’re billed per character of text on every task_start / task_continue frame, against your plan’s monthly character allowance.
  • The handshake checks your plan before upgrading: streaming not included in your plan → 403; character quota exhausted → 402 (when billing enforcement is active). The handshake response carries X-Billing-Remaining with your remaining characters.
  • If your workspace runs out of credits mid-session (strict enforcement), the text frame that exceeded the balance is not forwarded — you receive a fatal error frame and the session closes:
Usage appears in your dashboard analytics and on the monthly invoice like any other synthesis.

Close codes

End-to-end example

The first connection after a long idle period may be slow while the synthesis engine warms up. For latency-sensitive apps, open the socket ahead of the first turn and keep it alive.
Need a one-way, fully self-described stream (e.g. for server-side playback)? Stream speech (SSE) uses a fixed start / chunk / done / error event shape. Use the WebSocket when you need a bidirectional, turn-based session.

Prompt

Copy this into your coding agent (Claude Code, Cursor, Codex…) to implement this endpoint.