Skip to main content

Generate speech

POST https://api.kittenml.com/v1/audio/speech

Three sizes. Eight voices. One simple request. Generate speech with Nano, Micro, or Mini; all three hosted KittenTTS 0.8 models are free.

A valid TTS-enabled API key is still required. The key needs the tts:generate permission so the generation can appear in your usage history at a cost of $0.

Audio streams as it is generated. The default response is chunked binary audio; choose SSE when your client needs explicit, Base64-encoded audio events.

Request body

FieldTypeRequiredDefaultAccepted values
modelstringNokitten-tts-mini-0.8kitten-tts-nano-0.8, kitten-tts-micro-0.8, or kitten-tts-mini-0.8
inputstringYesNon-empty text, at most 4,096 characters
voicestringNoBellaOne of the eight voices below
response_formatstringNomp3mp3, wav, flac, aac, opus, or pcm
stream_formatstringNoaudioaudio for binary chunks or sse for Server-Sent Events
speednumberNo1.00.25 through 4.0

The JSON body may be at most 64 KiB. Unknown fields are not part of the public contract.

Voices

Meet the cast. Voice names are case-sensitive:

VoiceVoiceVoiceVoice
BellaJasperLunaBruno
RosieHugoKikiLeo

Output formats

FormatContent typeFile extensionNotes
mp3audio/mpeg.mp3Compact default
wavaudio/wav.wav24 kHz mono PCM in a WAV container
flacaudio/flac.flacLossless compressed audio
aacaudio/aac.aacAAC audio
opusaudio/ogg.opus or .oggOpus in an Ogg container
pcmaudio/L16; rate=24000; channels=1.pcmHeaderless 24 kHz mono, little-endian PCM16

The pcm bytes are little-endian. Some media libraries interpret the standard audio/L16 name as big-endian; use wav for portable PCM interchange, or explicitly configure your raw-audio reader for signed 16-bit little-endian data.

Stream binary audio with cURL

curl --fail-with-body -sS \
https://api.kittenml.com/v1/audio/speech \
-H "Authorization: Bearer $KITTENML_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"model": "kitten-tts-mini-0.8",
"input": "Hello! This audio was generated by KittenTTS.",
"voice": "Bella",
"response_format": "mp3",
"stream_format": "audio",
"speed": 1.0
}' \
--output kitten-output.mp3

The success body is binary audio. cURL writes each received chunk to the file; it does not need to hold the complete response in memory. Do not pipe binary audio to jq or print it in a terminal.

Stream SSE events

Set stream_format to sse when you need event boundaries:

curl --fail-with-body -sS -N \
https://api.kittenml.com/v1/audio/speech \
-H "Authorization: Bearer $KITTENML_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"model": "kitten-tts-mini-0.8",
"input": "The first sentence can play while the next sentence is generated. This keeps longer responses responsive.",
"voice": "Bella",
"response_format": "mp3",
"stream_format": "sse"
}'

Each speech.audio.delta contains a Base64-encoded piece of the selected audio format. Concatenate the decoded bytes in order. A final speech.audio.done marks a successful, complete stream:

data: {"type":"speech.audio.delta","audio":"SUQzBAAAAA..."}

data: {"type":"speech.audio.delta","audio":"qqqqqqqqqq..."}

data: {"type":"speech.audio.done","usage":{"input_tokens":0,"output_tokens":0,"total_tokens":0}}

If the connection closes before speech.audio.done, treat the output as incomplete and retry the generation with a new request.

Python with the OpenAI SDK

import os
from openai import OpenAI

client = OpenAI(
api_key=os.environ["KITTENML_API_KEY"],
base_url="https://api.kittenml.com/v1",
)

with client.audio.speech.with_streaming_response.create(
model="kitten-tts-mini-0.8",
input="Hello from the KittenML API.",
voice="Bella",
response_format="mp3",
speed=1.0,
) as audio:
audio.stream_to_file("kitten-output.mp3")

JavaScript with the OpenAI SDK

import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
import { createWriteStream } from "node:fs";
import OpenAI from "openai";

const client = new OpenAI({
apiKey: process.env.KITTENML_API_KEY,
baseURL: "https://api.kittenml.com/v1",
});

const audio = await client.audio.speech.create({
model: "kitten-tts-mini-0.8",
input: "Hello from the KittenML API.",
voice: "Bella",
response_format: "mp3",
speed: 1.0,
});

await pipeline(
Readable.fromWeb(audio.body),
createWriteStream("kitten-output.mp3"),
);

Test every model

for MODEL in \
kitten-tts-nano-0.8 \
kitten-tts-micro-0.8 \
kitten-tts-mini-0.8
do
curl --fail-with-body -sS \
https://api.kittenml.com/v1/audio/speech \
-H "Authorization: Bearer $KITTENML_API_KEY" \
-H "Content-Type: application/json" \
--data "{\"model\":\"$MODEL\",\"input\":\"Testing $MODEL.\",\"voice\":\"Bella\",\"response_format\":\"wav\"}" \
--output "$MODEL.wav"
done

Successful response

With stream_format: audio, the response body contains chunked audio bytes. Useful headers include:

HeaderMeaning
x-request-idIdentifier for logs and support
X-ModelModel that generated the response
X-Stream-Formataudio or sse
Content-DispositionSuggested output filename
Cache-Controlno-store

Header names are case-insensitive.

Validation errors

Authentication and model errors use the common KittenML error envelope. Parameter validation errors currently use a detail field:

{
"detail": "Unknown voice. Supported voices: Bella, Jasper, Luna, Bruno, Rosie, Hugo, Kiki, Leo"
}

See Errors and retries for client behavior.

Limits

An organization may run two TTS generations concurrently. A stream holds its slot until it completes or disconnects. A request above the limit returns 429 with Retry-After: 1. See Rate limits and timeouts.