Stream text input
WSS wss://api.kittenml.com/v1/tts/realtime
Use input streaming when text is produced over time, such as an LLM response.
Keep one WebSocket open, send text fragments as they arrive, and play each
audio delta immediately. This route is a KittenML extension; the
OpenAI-compatible POST /v1/audio/speech endpoint receives complete input
text and streams only its output.
The API key needs the tts:generate permission. Send it in the WebSocket
upgrade header:
Authorization: Bearer sk_kitten_live_...
Connect
wss://api.kittenml.com/v1/tts/realtime?model=kitten-tts-mini-0.8&voice=Bella&speed=1&response_format=pcm
| Query parameter | Default | Accepted values |
|---|---|---|
model | kitten-tts-mini-0.8 | Nano, Micro, or Mini model ID |
voice | Bella | Any documented KittenTTS voice |
speed | 1 | 0.25 through 4.0 |
response_format | pcm | pcm |
Input streaming returns headerless 24 kHz, mono, signed little-endian PCM16. Read server events continuously while sending text; this prevents client-side buffers from delaying long streams.
Client events
Append text as it becomes available:
{"type":"input_text.append","text":"The first part of the response. "}
The server buffers incomplete phrases and applies backpressure. Send a commit to synthesize all currently buffered text while keeping the session open:
{"type":"input_text.commit"}
End the input after the final fragment:
{"type":"input_text.done"}
One session accepts at most 262,144 cumulative input characters. Each append
may contain at most 8,192 characters and each WebSocket frame may contain at
most 64 KiB. Send 50–500 characters at a time, preferably complete phrases,
and do not send more text after input_text.done.
Server events
The connection begins with session.created. Each accepted append produces
input_text.accepted; a commit produces input_text.committed.
Audio arrives in sequence-numbered Base64 deltas:
{
"type": "speech.audio.delta",
"event_id": "event_...",
"audio": "AACAPw...",
"sequence": 0
}
Decode and concatenate the audio values in sequence order. The terminal
event confirms the complete duration and character count:
{
"type": "speech.audio.done",
"event_id": "event_...",
"usage": {
"input_characters": 1280,
"audio_seconds": 92.375
}
}
If an error event arrives or the socket closes before speech.audio.done,
treat the generation as incomplete.
Node.js example
Install ws, then run this server-side example. Native browser WebSockets
cannot set an Authorization header; do not expose a long-lived API key in
browser code.
import {createWriteStream} from 'node:fs';
import WebSocket from 'ws';
const ws = new WebSocket(
'wss://api.kittenml.com/v1/tts/realtime' +
'?model=kitten-tts-mini-0.8&voice=Bella&response_format=pcm',
{headers: {Authorization: `Bearer ${process.env.KITTENML_API_KEY}`}},
);
const output = createWriteStream('speech.pcm');
ws.on('open', () => {
ws.send(JSON.stringify({type: 'input_text.append', text: 'Hello from '}));
ws.send(JSON.stringify({type: 'input_text.append', text: 'a live text stream.'}));
ws.send(JSON.stringify({type: 'input_text.done'}));
});
ws.on('message', (raw) => {
const event = JSON.parse(raw.toString());
if (event.type === 'speech.audio.delta') {
output.write(Buffer.from(event.audio, 'base64'));
} else if (event.type === 'speech.audio.done') {
output.end();
ws.close();
} else if (event.type === 'error') {
output.destroy();
throw new Error(event.error?.message || 'TTS stream failed');
}
});
Play the raw output with a player configured for signed 16-bit little-endian PCM, 24 kHz, mono. Runnable Python and JavaScript clients are available in the realtime TTS examples.
Limits and recovery
Input-streaming WebSockets share the same two active TTS slots per organization as HTTP generations. A slot lasts until completion or disconnect. Reconnect and resubmit only the text you still need after a failure; a new connection is a new generation. A session closes after five minutes without a client message or after two hours total, whichever happens first.