TTS-API Reference

Streaming text-to-speech · 24kHz mono 16-bit PCM · Kokoro on-device / Edge Microsoft cloud

Authentication Health check Key probe Logs Voices Voice preview REST synthesis OpenAI compatibility WebSocket synthesis Audio format Static files

Authentication

The service has built-in API Key authentication so external programmatic clients (browser extensions, scripts, other backends) can call it directly. Rules:

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 })
});

GET/

Health check. Returns the engine and ffmpeg readiness status.

Response 200 — engine ready

{ "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.

Response 503 — engine starting

{ "status": "starting", "ready": false, "max_text_length": 100000 }

Response 503 — ffmpeg missing

{ "status": "ffmpeg missing", "ready": false, "max_text_length": 100000 }

GET/api/auth

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.

Response 200 — authorized (or auth disabled)

{ "auth": "enabled", "authorized": true }
{ "auth": "disabled", "authorized": true }   // when TTS_API_KEY is not configured

Response 401 — key missing or wrong

{ "auth": "enabled", "authorized": false, "detail": "缺少或错误的 API Key" }

Use the "Verify key" button in the Authentication section above to test this endpoint.

GET/api/logs

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: ..."] }

GET/api/voices

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.

GET/api/voices/preview

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.

POST/api/tts

Stream MP3 audio for the given text.

Request body

{
  "text": "你好世界",
  "engine": "kokoro",
  "voice": "zf_xiaoxiao",
  "speed": 1.0,
  "ssml": false
}
FieldTypeDefaultDescription
textstring—Input text (required, non-empty; max length from MAX_TEXT_LENGTH, default 100000)
enginestring"kokoro""kokoro" (on-device) or "edge" (Microsoft cloud)
voicestring"zf_xiaoxiao"Voice ID from /api/voices
speednumber1.0Playback speed, range 0.5–3.0
ssmlbooleanfalseReserved flag. Raw SSML is currently rejected with 422.
downloadqueryfalseSet ?download=true to return Content-Disposition: attachment.

Response

StatusDescription
200audio/mpeg streaming response
400Empty text, empty after Markdown cleaning, or no speakable content for the selected voice
401Missing or wrong API key
429ffmpeg process limit reached
422Invalid field (including text over MAX_TEXT_LENGTH)
500Kokoro/local synthesis failed before any audio was produced
502Edge/upstream synthesis failed before any audio was produced
503Engine not ready
504Synthesis timed out before audio was ready

POST/v1/audio/speech

OpenAI-compatible binary speech endpoint for SDKs and agents such as Hermes and OpenClaw. Set the client base URL to http://<host>:8880/v1.

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

WS/ws/tts

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.

Client → Server

{
  "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.

Server → Client

MessageTypeDescription
{"type":"start"}JSONBegin processing a request
{"type":"seg","text":"..."}JSONSentence boundary marker; marks the start of one synthesis unit in the PCM stream
Binary frameArrayBuffer24kHz 16-bit mono PCM chunk (typically 2048 bytes)
{"type":"end"}JSONAll audio sent; connection stays open
{"type":"error","message":"..."}JSONRequest 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.

Cancel / Interrupt

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.

Auto engine

"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.

Text preprocessing

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.

Online test

Audio format

ParameterValue
Sample rate24000 Hz
Channelsmono (1)
Bit depth16-bit signed little-endian PCM (WebSocket) / MP3 (REST)

Static files

PathContent
/index.htmlSingle-page browser UI
/static/style.cssStylesheet
/apiThis docs page
/favicon.icoSite favicon (200)