Skip to main content

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 parameterDefaultAccepted values
modelkitten-tts-mini-0.8Nano, Micro, or Mini model ID
voiceBellaAny documented KittenTTS voice
speed10.25 through 4.0
response_formatpcmpcm

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.