Voice-Clone API — client reference

VoxCPM2 (Vietnamese) · XTTS-v2 (English) · all requests are multipart/form-data.

Base URL
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
Cold start (first request after a (re)start) loads the model and may take longer; the proxy allows up to 600 s.
Vietnamese text is normalized (abbreviations/numbers/dates → spoken) and split into per-sentence chunks by default; you get one merged file or an ordered list of per-sentence audio with source offsets.

GET  /health

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

POST  /unload

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

POST  /tts

Form fields

fieldtypedefaultnotes
textstringrequiredFor vi, normalized unless raw=true.
langstringrequiredvi or en.
reffile (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_idstringReuse a saved voice from the library (see /voices). Ignored if ref is also sent. Omit both → default voice.
formatwav | mp3wav (single) / mp3 (segmented)Output codec.
b64boolfalseSingle-file mode: true → JSON {audio_base64}.
rawboolfalseSkip vi normalization + splitting (verbatim, one shot).
segmentedboolfalsetrue → JSON array of per-sentence audio + offsets.

Response — segmented ← recommended for playback

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":"<...>" }
  ]
}
Player recipe: for each segment, decode 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}.

Voice cloning — two ways

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.

Examples

# 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

Voice library

Saved reference voices, reusable via voice_id on /tts.

endpointwhat
GET /voices{ok, voices:[{id,name,seconds,bytes,created}]}
POST /voicesform: name (required) + audio (file, ≤3 MB; >15 s auto-trimmed) → {ok, voice:{id,name,…}}
GET /voices/{id}/audiothe 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"

Vietnamese normalization

inputspoken as
PGS.TSPhó Giáo Sư Tiến Sĩ
TP.HCMThà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:30ngà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 initialsleft unchanged

Errors

statuswhen
400bad lang/format; ref > 3 MB or unreadable (over-15 s is trimmed, not rejected)
422missing text/lang
500synthesis failure

Notes

Source of truth: docs/API.md · product/api.py · demo product/demo.html.