Skip to main content
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

WebSocket is also the cheaper choice against rate limits: one connection holds a single concurrency slot for a whole multi-turn session, where the same session over HTTP consumes one slot per utterance.

Authentication

The x-api-key header goes on the WebSocket handshake, not in a frame after connecting:
Authentication is enforced in two places: So a client that forgets the header fails immediately and visibly at connect time, rather than part-way through a session.
Browsers cannot set custom headers on the WebSocket constructor. Use Streaming HTTP for browser clients, or proxy the WebSocket through a backend that sets the header.

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