Skip to main content

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_urlhttp://host:8000 or https://voice.zeligate.com; do not include a trailing /v1.
  • api_key — sent as Authorization: Bearer … on every request. Required only when the box sets ZELI_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 an AudioStream — iterate it for audio bytes as 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 requested output_format.
  • output_format accepts the full menu — mp3_*, wav_*, raw pcm_*, 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_seconds is clamped to 30–600 (default 300). expires_at is 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 not voices.add/delete or tokens.create_ephemeral — those return 401 insufficient_scope.
  • Minting requires a full key, and 501 not_supported is 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.

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)

Source distribution for zeli-tts 0.3.1
File Size Uploaded
zeli_tts-0.3.1.tar.gz 18.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for zeli-tts 0.3.1
File Interpreter ABI Platform
zeli_tts-0.3.1-py3-none-any.whl Python 3 none any Details

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

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page