Connection
/ws/tts and /ws are equivalent (same handler, two paths).
The handshake also accepts an X-API-Key: sk_... header — preferred outside the browser, since it keeps the key out of the URL (see Authentication).
Request
Send a JSON message after connecting. You can send multiple requests over the same connection — each is a fresh synthesis job.Response
- Binary messages: Raw Int16 PCM audio chunks (44.1 kHz, mono). Large chunks are split into ≤512KB WebSocket frames.
- Text message
"EOS": Synthesis complete for this request — the connection stays open for further requests. - JSON visemes: A bare JSON array of
{ v, c, t, id }objects (not wrapped in a{"type":...}envelope) — sent only ifvisemes: trueand only when the model actually produced viseme data for that chunk. - JSON timing (optional, streaming mode only):
{ "type": "timing", "timing": { ... RTF, IsFirst, ... }, "ttfb_ms"?: number }— internal performance telemetry sent alongside some chunks;ttfb_msis only present on the first chunk. Treat this as informational, not part of the audio contract. - JSON error:
{ "type": "error", "data": { "code": "...", "message": "...", "detail": "...", "retryable": true|false } }— same structured shape as every other Lokutor error; see the Error Catalog. This is not a bare{"type":"error","message":"..."}— the payload is underdata, and it’s the full structured object.
Best practices
- Keep the WebSocket open for multiple requests to avoid handshake latency
- The stream runs at
6denoising steps, tuned for live conversation; for offline audio at up to8, use RESTPOST /tts/synthesize - Validate inputs against
GET /configbefore sending - Always pass
voiceandlanguageexplicitly — neither has a server-side default on this endpoint