Connection
/ws/tts and /ws are the same endpoint. Authenticate the handshake with an Authorization: Bearer or X-API-Key header, or with ?api_key= where headers cannot be set (the browser WebSocket). In a browser, pass a short-lived wst_ ticket rather than your key (Authentication).
The socket stays open: send as many requests as you like, one after another, and each is a separate synthesis.
Request
The older
lang name is accepted as an alias for language.
The text may carry expression tags ([breath], [laugh], [sigh], and the emotions [happy], [sad], [angry], [surprised], [fearful], [disgusted], [excited], [anxious]) that the voice performs; each sound adds time to the audio.
Default voice. When voice is omitted, the server uses M5 (Noah) for English, F3 (Emma) for Spanish, and otherwise the language’s first voice. Pass a voice explicitly for a stable result. The SDKs’ TTSClient always sends one (F1 unless you choose another).
Response
No other JSON messages are sent: there are no timing or progress frames.
Errors and billing
- A message that fails validation (empty or too-long
text, an unknownvoiceorlanguage,speedorstepsout of range) gets an error frame and is not billed. - A message whose synthesis fails on Lokutor’s side gets an error frame and is not billed either.
- Successful messages are billed per character (rate card:
tts_per_million_chars). - A balance below the minimum refuses the handshake with
402(Billing errors).
Concurrency
Each open/ws/tts socket counts toward your account’s concurrent sessions (3 before the first top-up, 10 after; Limits). Past that limit the socket opens, sends an auth.rate_limited error frame, and closes. This differs from /ws/agent and /ws/stt, which refuse the handshake with 429. Close sockets you are not using.
Example (Node.js 22+)
Best practices
- Reuse one socket for several requests to avoid a new handshake each time.
- Start playback on the first binary frame; do not wait for
EOS. - Pass
voiceandlanguageon every request. - All nine languages are available.