Building with an AI agent? Point it at
docs.kova.ai/for-agents.md — the complete API contract as one plain-text page.WS /v1/tts/ws is the recommended transport for interactive applications — voice agents, dialog systems, anything where text arrives incrementally over the lifetime of a session.
A single connection multiplexes multiple contexts. Each context is one continuous utterance with its own voice and format. Many contexts can be open at once; the server tags every frame with its context_id so you can route audio back to the right player.
When to use WebSocket vs streaming HTTP
Authentication
Thex-api-key header goes on the WebSocket handshake, not in a frame after connecting:
So a client that forgets the header fails immediately and visibly at connect time, rather than part-way through a session.
Connection lifecycle
1
Connect
Open a WebSocket to
wss://api.kova.ai/v1/tts/ws with x-api-key in the handshake.2
Start a context
Send
start_context with voice_id and model_id (both required), plus optional timestamps and response_format. The server replies with context_started carrying the fully resolved format.3
Send text
Send one or more
send_text frames. The server generates incrementally and emits audio_chunk frames, plus timestamps frames if enabled.4
Flush
Send
flush with your own flush_id to mark the end of the utterance. The server finishes generating and emits flush_completed echoing that id.5
Close the context
Send
close_context. The server emits a synthetic flush_completed (flush_id = "<context_id>:close") followed by context_closed. The WebSocket stays open.6
Reuse or disconnect
The connection is reusable — open new contexts on it, or close it when the session ends.
Audio format
audio_chunk values are base64-encoded headerless signed 16-bit little-endian PCM at 32 kHz mono by default. Override per context by passing response_format to start_context — same shape as the HTTP request body.
Read the actual format from the context_started frame, which echoes every field including the defaults you didn’t send.
As on streaming HTTP,
mp3 over WebSocket arrives as raw MPEG frames with no ID3 header.Two details to code against
Closing a context flushes it.close_context emits a flush_completed before context_closed, and any text you sent but never flushed still generates. Match flush_completed on your own flush_id rather than breaking on the first one you see.
Timestamps arrive incrementally. Each timestamps frame carries only the words finalized since the last one. Append them; don’t overwrite.
Both are covered in detail in the frame reference.
Next
- Frame reference — every client and server frame with shapes and defaults.
- Example: streaming a long document — concrete end-to-end walkthrough.
- For AI agents — the whole contract on one page.