ZeliSpeech — Python SDK
A small, dependency-light client for ZeliSpeech —
self-hosted, low-latency streaming text-to-speech. Point it at your running
server and get audio back in a couple of lines. The SDK talks the same
authenticated /v1 API documented in the
API reference, so
the request/response shapes follow mainstream TTS-API conventions.
Install
pip install zeli-tts
Runtime dep: requests. Live playback (play/stream) shells out to ffplay
(ffmpeg), mpv, or afplay.
Quickstart
from zeli_tts import ZeliSpeech, play, save, stream
client = ZeliSpeech(
base_url="https://voice.zeligate.com",
api_key="sk-zeli-...", # optional if the box runs open
)
# 1) Stream — first audio after the first sentence, not the whole passage
audio = client.text_to_speech.stream(
voice_id="zeli-voice-1",
text="Hey there, this is Zeli.",
output_format="mp3_44100_128",
)
for chunk in audio:
... # audio bytes as they generate
# 2) Stream + play live (needs ffplay or mpv)
stream(client.text_to_speech.stream(voice_id="zeli-voice-1", text="Playing as I generate."))
# 3) One-shot — a finished clip
audio = client.text_to_speech.convert(voice_id="zeli-voice-1", text="Hello world.")
save(audio, "hello.mp3")
Client
ZeliSpeech(base_url, *, api_key=None, timeout=60.0)
base_url—http://host:8000orhttps://voice.zeligate.com; do not include a trailing/v1.api_key— sent asAuthorization: Bearer …on every request. Required only when the box setsZELI_API_KEY; a loopback/embedded box can run open.client.capabilities()/client.health()/client.is_ready()— engine info and readiness (handy while a box is warming up).
Synthesis — client.text_to_speech
stream(voice_id, text, *, model_id="zeli-turbo", voice_settings=None,
output_format="mp3_44100_128", timeout=None) -> AudioStream
convert(voice_id, text, *, ...same...) -> bytes
stream(...)returns anAudioStream— iterate it for audiobytesas they generate;.read()drains it to one blob. It exposes.output_format,.sample_rate,.voice(the resolved voice id), and.request_id.convert(...)returns the finished clip in the requestedoutput_format.output_formataccepts the full menu —mp3_*,wav_*, rawpcm_*,ulaw_8000,alaw_8000,opus_48000_*— see the output formats reference.voice_settings(stability/style/speed/…) maps onto Turbo delivery — see voice settings.
Voices — client.voices
client.voices.list() # -> [Voice(id, label, description, custom), ...]
client.voices.get("zeli-voice-1") # -> Voice (404s on unknown ids)
client.voices.add(name="My voice", file="me.wav", description="") # -> Voice
client.voices.delete("custom-abc123")
add clones a voice zero-shot from a short reference clip (~10–20 s of
clean single-speaker audio; file may be a path, a file object, or bytes).
Cloning and removal apply to the Turbo engine's custom voices, and require a
full API key — ephemeral tokens get 401 insufficient_scope here.
Ephemeral tokens — client.tokens
Never ship a real API key to a browser or mobile app: anyone can read it. The
intended flow is that your backend (which holds a full api_key) mints a
short-lived, synthesis-only token and hands it to the client, which then talks
to the API directly with that token.
tok = client.tokens.create_ephemeral(ttl_seconds=300)
# -> {"token": "zsk_temp_…", "expires_at": 1770000000, "scope": "tts"}
ttl_secondsis clamped to 30–600 (default 300).expires_atis the epoch second the token dies — enforced server-side on every request, so a leaked token is usable for minutes at most.- The returned token has scope
"tts": it can call synthesis and read endpoints (text_to_speech.stream/convert,voices.list/get) but notvoices.add/deleteortokens.create_ephemeral— those return401 insufficient_scope. - Minting requires a full key, and
501 not_supportedis returned on a server without the portal keys table configured.
Backend endpoint example (FastAPI):
from fastapi import FastAPI
from zeli_tts import ZeliSpeech
app = FastAPI()
zeli = ZeliSpeech(base_url="https://voice.zeligate.com", api_key=os.environ["ZELISPEECH_API_KEY"])
@app.post("/api/tts-token")
def tts_token():
return zeli.tokens.create_ephemeral(ttl_seconds=300) # browser uses this directly
More examples
# Delivery control (stability / style / speed map onto Turbo knobs)
clip = client.text_to_speech.convert(
"zeli-voice-1", "Same words, very different delivery!",
voice_settings={"stability": 0.2, "style": 0.9, "speed": 1.1},
)
# Raw PCM for your own audio pipeline (24 kHz mono int16, no container)
pcm = client.text_to_speech.convert("zeli-voice-1", "Raw samples.", output_format="pcm_24000")
# Telephony formats
ulaw = client.text_to_speech.convert("zeli-voice-1", "For the phone line.", output_format="ulaw_8000")
# Stream straight to a file as it generates
with open("out.mp3", "wb") as f:
for chunk in client.text_to_speech.stream("zeli-voice-1", "Written as it speaks."):
f.write(chunk)
# Clone a voice, use it, remove it
voice = client.voices.add(name="My narrator", file="reference.wav")
client.text_to_speech.convert(voice.id, "Cloned voice speaking.")
client.voices.delete(voice.id)
# Play inline in a Jupyter notebook
from IPython.display import Audio
Audio(data=bytes(client.text_to_speech.convert("zeli-voice-1", "Hello, notebook!")))
# Robust error handling
from zeli_tts import APIError, ConnectionError
try:
client.text_to_speech.convert("zeli-voice-1", "…")
except APIError as e:
if e.status == "invalid_api_key":
... # rotate/refresh the key
elif e.status_code == 503:
... # model still loading — retry shortly
except ConnectionError:
... # box unreachable / stopped
Helpers
from zeli_tts import play, save, stream, pcm_to_wav
play(audio) # play a finished clip (any output format)
stream(audio_stream) # play chunks live; returns the collected bytes
save(audio, "out.mp3") # write bytes to a file (pcm_* wrapped when .wav)
pcm_to_wav(pcm, sample_rate=24000)
Errors
All derive from ZeliSpeechError:
| Exception | When |
|---|---|
ConfigurationError |
bad base_url, or a missing dep/player |
ConnectionError |
server unreachable (refused / DNS / timeout) |
APIError |
non-2xx from an HTTP endpoint (.status_code, .status, .body) |
GenerationError |
the stream broke mid-synthesis |
Auth failures are APIErrors whose .status tells you why:
.status_code |
.status |
When |
|---|---|---|
| 401 | missing_api_key |
auth required but no key sent |
| 401 | invalid_api_key |
unknown, revoked, or expired key/token |
| 401 | insufficient_scope |
an ephemeral token hit a management route |
| 501 | not_supported |
minting on a server without the keys table |
Migrating from 0.1.x
0.2.0 moves the SDK onto the authenticated /v1 API and adopts the ZeliSpeech
naming. ZeliTTS / ZeliTTSError remain as aliases, but the synthesis calls
changed: stream(text, voice=...) → stream(voice_id, text), audio now
arrives in output_format (mp3 by default) instead of raw PCM, and the
engine-knob kwargs (exaggeration, cfg_weight, …) are replaced by
voice_settings.
Metadata
Release files for zeli-tts 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| zeli_tts-0.3.1.tar.gz | 18.2 kB | Without provenance | Details for zeli_tts-0.3.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| zeli_tts-0.3.1-py3-none-any.whl | Python 3 | none | any | Details for zeli_tts-0.3.1-py3-none-any.whl |
Total release size: 34.6 kB
Release files / zeli_tts-0.3.1.tar.gz
| Download URL | zeli_tts-0.3.1.tar.gz |
|---|---|
| Size | 18.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5cda34278bfaccefafe5bf88344ffd7e750683e0d38ebb0f1b39ee28c9821a9e
|
|
BLAKE2b-256 checksum How to use checksums |
d11c5f06c6e19531df93ee975375fd29e4fa43f8175654dd380766a9e0407de6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Release files / zeli_tts-0.3.1-py3-none-any.whl
| Download URL | zeli_tts-0.3.1-py3-none-any.whl |
|---|---|
| Size | 16.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8e7180e8133f3db3be7f5184109247e2c560bfd49e8200135c09e36a0c414329
|
|
BLAKE2b-256 checksum How to use checksums |
58b09c6a930577cf2ff157716112d53ffc9ceb7002b240c620d78785fb14f0dc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Security for zeli-tts 0.3.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.