VoxCPM2 (Vietnamese) · XTTS-v2 (English) · all requests are multipart/form-data.
https://tts.openleah.com # PRODUCTION (HTTPS) — use this
https://tts.openleah.com/ # DEMO UI (upload/manage voices, test languages)
http://127.0.0.1:7123 # on-host dev (native MPS, serve-gpu.sh)
Set once for examples: BASE=https://tts.openleah.com
{ "ok": true, "languages": ["en","vi"], "formats": ["wav","mp3"], "device": "mps",
"ref_limits": { "max_bytes": 3145728, "max_seconds": 15.0 },
"model_loaded": true, "idle_unload_seconds": 600 }
Release the loaded model now to free GPU/RAM → {ok, unloaded, model_loaded}.
It reloads automatically on the next /tts (cold start). The server also auto-unloads
after idle_unload_seconds of inactivity (env TTS_IDLE_UNLOAD_SECONDS,
default 600; 0 = always warm).
| field | type | default | notes |
|---|---|---|---|
text | string | required | For vi, normalized unless raw=true. |
lang | string | required | vi or en. |
ref | file (wav/mp3) | — | One-off reference voice to clone. ≤ 3 MB (else 400); audio over 15 s is auto-trimmed. Any format; transcoded server-side. |
voice_id | string | — | Reuse a saved voice from the library (see /voices). Ignored if ref is also sent. Omit both → default voice. |
format | wav | mp3 | wav (single) / mp3 (segmented) | Output codec. |
b64 | bool | false | Single-file mode: true → JSON {audio_base64}. |
raw | bool | false | Skip vi normalization + splitting (verbatim, one shot). |
segmented | bool | false | true → JSON array of per-sentence audio + offsets. |
One segment per sentence, in order. Each carries start/end = the slice of the original input text it came from, so a player can highlight the sentence being spoken.
{
"ok": true, "lang": "vi", "format": "mp3",
"normalized": "Phó Giáo Sư A ở Thành phố Hồ Chí Minh. Xin chào.",
"count": 2,
"segments": [
{ "index":0, "start":0, "end":16, "text":"PGS. A ở TP.HCM.",
"normalized":"Phó Giáo Sư A ở Thành phố Hồ Chí Minh.", "audio_base64":"<...>" },
{ "index":1, "start":17, "end":26, "text":"Xin chào.",
"normalized":"Xin chào.", "audio_base64":"<...>" }
]
}
start/end — code-point offsets into the original text; text[start:end] === segments[].text (= JS UTF-16 indices for Vietnamese).text original slice · normalized what was spoken · audio_base64 that sentence's audio.audio_base64 and play; while playing, highlight original.slice(start,end). Segments tile the input in order.Single-file modes: default (segmented=false) → binary audio/wav|mpeg; with b64=true → {audio_base64}.
ref (a short sample) to a /tts call.POST /voices,
then pass voice_id on any /tts call.Both cap uploads at ≤ 3 MB (else 400); audio longer than
15 s is auto-trimmed to 15 s. Any format (wav/mp3/…) is transcoded to wav
server-side. Omit both → bundled default voice.
# Segmented mp3 (progressive playback + offsets)
curl -X POST $BASE/tts -F "text=PGS. A ở TP.HCM. Xin chào." -F "lang=vi" -F "segmented=true"
# Clone from an uploaded voice (≤3MB, ≤15s)
curl -X POST $BASE/tts -F "text=Xin chào." -F "lang=vi" -F "ref=@my_voice.wav" -F "segmented=true"
# Single WAV file
curl -X POST $BASE/tts -F "text=Xin chào" -F "lang=vi" -o out.wav
Saved reference voices, reusable via voice_id on /tts.
| endpoint | what |
|---|---|
GET /voices | {ok, voices:[{id,name,seconds,bytes,created}]} |
POST /voices | form: name (required) + audio (file, ≤3 MB; >15 s auto-trimmed) → {ok, voice:{id,name,…}} |
GET /voices/{id}/audio | the stored wav (audio/wav) — for preview |
DEL /voices/{id} | {ok, deleted:id} · 404 if missing |
# add a voice, then use it
VID=$(curl -s -X POST $BASE/voices -F "name=Anh Minh" -F "audio=@voice.wav" | jq -r .voice.id)
curl -X POST $BASE/tts -F "text=Xin chào" -F "lang=vi" -F "voice_id=$VID" -F "segmented=true"
| input | spoken as |
|---|---|
PGS.TS | Phó Giáo Sư Tiến Sĩ |
TP.HCM | Thành phố Hồ Chí Minh |
1.250.000.000đ | một tỷ hai trăm năm mươi triệu đồng |
12,5% | mười hai phẩy năm phần trăm |
01/02/2026 · 14:30 | ngày một tháng hai năm … · mười bốn giờ ba mươi phút |
v.v. | vân vân |
| URLs / emails / name initials | left unchanged |
| status | when |
|---|---|
400 | bad lang/format; ref > 3 MB or unreadable (over-15 s is trimmed, not rejected) |
422 | missing text/lang |
500 | synthesis failure |
segmented to start playback early.mp3 re-encodes.TTS_ENGINE_VI, TTS_ENGINE_EN, VOXCPM_MODEL.Source of truth: docs/API.md · product/api.py · demo product/demo.html.