Streaming text-to-speech · 24kHz mono 16-bit PCM · Kokoro on-device / Edge Microsoft cloud
The service has built-in API Key authentication so external programmatic clients (browser extensions, scripts, other backends) can call it directly. Rules:
TTS_API_KEY environment variable in docker-compose.yml. Change it to your own strong random value./index.html) can use eligible /api routes and WebSocket without a key. /api/auth and /api/logs never use this exemption; neither does /v1/* when a key is configured.X-API-Key: <key> (preferred, especially behind Caddy Basic Auth) or Authorization: Bearer <key>; WebSocket cannot set custom headers in a browser, so use query parameter /ws/tts?key=<key>.Honest boundary: same-origin exemption relies on the Origin/Referer header, which non-browser clients can forge. For real network isolation still put a reverse proxy (Caddy) in front; the key provides controlled access for external integrations.
# CRX plugin call example (external client, key required)
fetch("https://tts.example.com/api/tts", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "YOUR_SECRET_KEY"
},
body: JSON.stringify({ text: "Hello", engine: "kokoro", voice: "zf_xiaoxiao", speed: 1.0 })
});
Health check. Returns the engine and ffmpeg readiness status.
{ "status": "v0.12 engine running", "ready": true, "max_text_length": 100000 }
max_text_length mirrors MAX_TEXT_LENGTH (present on 200 and 503) so the UI can highlight overflow and speak only the in-limit portion.
{ "status": "starting", "ready": false, "max_text_length": 100000 }
{ "status": "ffmpeg missing", "ready": false, "max_text_length": 100000 }
Key-probe endpoint: verifies whether the supplied key is valid, so a client (e.g. a CRX extension) can check credentials before real calls. This endpoint does not honor same-origin exemption — even the bundled pages must send a correct key to get authorized: true.
{ "auth": "enabled", "authorized": true }
{ "auth": "disabled", "authorized": true } // when TTS_API_KEY is not configured
{ "auth": "enabled", "authorized": false, "detail": "缺少或错误的 API Key" }
Use the "Verify key" button in the Authentication section above to test this endpoint.
Diagnostic log endpoint for the in-memory ring buffer. It is not same-origin exempt; when TTS_API_KEY is configured, send a valid key with X-API-Key or Authorization: Bearer.
{ "limit": 100, "total_buffered": 287, "lines": ["2026-... INFO tts-api: ..."] }
List all available voices for both engines. Edge voices are fetched live from Microsoft and cached for the configured TTL.
{
"kokoro": [
{ "id": "zf_xiaoxiao", "name": "晓晓", "gender": "female", "language": "zh" },
{ "id": "am_michael", "name": "Michael", "gender": "male", "language": "en" }
],
"edge": [
{ "id": "zh-CN-XiaoxiaoNeural", "name": "...", "gender": "Female", "locale": "zh-CN" },
{ "id": "zh-CN-liaoning-XiaobeiNeural", "name": "...", "gender": "Female", "locale": "zh-CN-liaoning" },
{ "id": "zh-CN-shaanxi-XiaoniNeural", "name": "...", "gender": "Female", "locale": "zh-CN-shaanxi" },
{ "id": "zh-HK-HiuGaaiNeural", "name": "...", "gender": "Female", "locale": "zh-HK" },
{ "id": "zh-TW-HsiaoChenNeural", "name": "...", "gender": "Female", "locale": "zh-TW" },
{ "id": "en-US-AvaNeural", "name": "...", "gender": "Female", "locale": "en-US" }
]
}
The API returns Microsoft’s full Edge catalog. The Web UI filters Edge voices to zh-CN, zh-CN-liaoning, zh-CN-shaanxi, zh-HK, zh-TW, en-US, en-GB. Dialect locales appear in the Chinese dropdown with a dialect tag. ShortNames are owned by Microsoft; renamed or delisted voices disappear with no local fallback catalog.
Synthesize a short built-in voice preview. Query parameters: engine (kokoro or edge), voice, and speed (0.5–3.0). The response is audio/mpeg with Content-Disposition: inline.
Stream MP3 audio for the given text.
{
"text": "你好世界",
"engine": "kokoro",
"voice": "zf_xiaoxiao",
"speed": 1.0,
"ssml": false
}
| Field | Type | Default | Description |
|---|---|---|---|
text | string | — | Input text (required, non-empty; max length from MAX_TEXT_LENGTH, default 100000) |
engine | string | "kokoro" | "kokoro" (on-device) or "edge" (Microsoft cloud) |
voice | string | "zf_xiaoxiao" | Voice ID from /api/voices |
speed | number | 1.0 | Playback speed, range 0.5–3.0 |
ssml | boolean | false | Reserved flag. Raw SSML is currently rejected with 422. |
download | query | false | Set ?download=true to return Content-Disposition: attachment. |
| Status | Description |
|---|---|
200 | audio/mpeg streaming response |
400 | Empty text, empty after Markdown cleaning, or no speakable content for the selected voice |
401 | Missing or wrong API key |
429 | ffmpeg process limit reached |
422 | Invalid field (including text over MAX_TEXT_LENGTH) |
500 | Kokoro/local synthesis failed before any audio was produced |
502 | Edge/upstream synthesis failed before any audio was produced |
503 | Engine not ready |
504 | Synthesis timed out before audio was ready |
OpenAI-compatible binary speech endpoint for SDKs and agents such as Hermes and OpenClaw. Set the client base URL to http://<host>:8880/v1.
TTS_API_KEY is configured, send Bearer or X-API-Key; forged same-origin headers never bypass /v1.mp3, opus, aac, flac, wav, and raw 24 kHz mono s16le pcm. MIME and filename extension follow the requested format.coral. A Kokoro ID requires model:"kokoro". alloy/coral use adult Edge voice Ava; real Ana remains selectable by ID.422 before synthesis. Numbers, punctuation, whitespace, and combining marks are neutral.instructions, lang_code/lang/language, the guide-only format alias, and custom voice objects are not implemented and return 400/422.API.md and compatibility audit.curl http://localhost:8880/v1/audio/speech \
-H "Authorization: Bearer $TTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input":"Hello","model":"edge","voice":"coral","response_format":"mp3"}' \
--output speech.mp3
Interactive TTS session. Send a JSON request, receive a stream of binary PCM frames interleaved with JSON control messages. The connection stays open after a request completes, so you can send the next request.
{
"text": "第一句。\n第二句。",
"engine": "kokoro",
"voice": "zf_xiaoxiao",
"speed": 1.0
}
Separate sentences with \n — the server uses these as synthesis units and aligns segment boundaries 1:1 for gapless streaming. speed is clamped server-side to 0.5–3.0.
| Message | Type | Description |
|---|---|---|
{"type":"start"} | JSON | Begin processing a request |
{"type":"seg","text":"..."} | JSON | Sentence boundary marker; marks the start of one synthesis unit in the PCM stream |
| Binary frame | ArrayBuffer | 24kHz 16-bit mono PCM chunk (typically 2048 bytes) |
{"type":"end"} | JSON | All audio sent; connection stays open |
{"type":"error","message":"..."} | JSON | Request rejected or synthesis failed; connection stays open |
error covers: invalid JSON, missing/empty text, text over length, unknown engine, unknown Kokoro voice, and synthesis failure (e.g. an invalid Edge voice or engine fault). On synthesis failure the server sends error instead of end, so the client leaves the "synthesizing" state without a false success.
While the server is synthesizing, the client can send any message (or close the connection) to cancel the current request. The signal immediately wakes Edge I/O and queued Kokoro work; a queued Kokoro request does not wait to acquire synthesis-slot capacity. This does not make resource cleanup instantaneous: before listening for the next request, the server waits for the Edge decoder process to exit or for an already-running Kokoro inference to stop cooperatively at the next generated chunk, then completes cleanup.
"engine":"auto" is a client-side UI option only — it is never sent to the server. When Auto is selected, the browser splits by sentence and language boundary: Chinese (including Chinese punctuation) routes to the Chinese voice, and English (including single terms/all-caps abbreviations like DNS, OpenWrt) always routes to the English voice. Each Kokoro pipeline is monolingual — the Chinese pipeline cannot even pronounce isolated English abbreviations (official issues #95/#238), so English is never merged into a Chinese segment.
Before synthesis the server strips Markdown formatting (code blocks, inline code, images, links, headings, blockquotes, lists, bold/italic/strikethrough, horizontal rules) and removes quotation marks (straight/curly double quotes, CJK corner brackets, and Chinese book-title brackets), preventing markup from being read aloud or quotes producing phoneme artifacts in Kokoro.
Additionally Kokoro applies per-voice language filtering (a fallback safeguard): Chinese voices (zf_*/zm_*) strip all English letters before synthesis, English voices (af_*/am_*) strip all CJK characters. Each Kokoro pipeline is monolingual and mispronounces cross-language text, so stripping the other language is preferable to reading garbage. For mixed Chinese/English text, use Auto mode in the UI to route per sentence.
| Parameter | Value |
|---|---|
| Sample rate | 24000 Hz |
| Channels | mono (1) |
| Bit depth | 16-bit signed little-endian PCM (WebSocket) / MP3 (REST) |
| Path | Content |
|---|---|
| /index.html | Single-page browser UI |
/static/style.css | Stylesheet |
| /api | This docs page |
/favicon.ico | Site favicon (200) |