Zeligate VoiceDeveloper docs
v1.3.0Sign in

Get started / Authentication

Authentication

The API authenticates with an Authorization: Bearer token. Auth is configured on the box, so a public deployment requires a key while an embedded loopback box can run open.

API keys

Set one or more keys on the server and send it on every /v1 request:

  • Single keyZELI_API_KEY=sk-zeli-...
  • Multiple keysZELI_API_KEYS=sk-zeli-a,sk-zeli-b (comma-separated)
Unset means open

If neither variable is set, the API is open — correct for a loopback/embedded box, wrong for a public one. Always set a key before exposing the box to a network.

Sending the key

Send the key as an Authorization: Bearer header.

curl -X POST "https://voice.zeligate.com/v1/text-to-speech/zeli-voice-1" \
  -H "Authorization: Bearer sk-zeli-..." \
  -H "Content-Type: application/json" \
  -d '{"text":"Authenticated with a Bearer token."}' --output out.mp3

WebSocket authentication

The realtime stream-input WebSocket reads the key from either of two places:

  1. The BOS message field authorization (a Bearer sk-zeli-... token).
  2. A ?authorization=Bearer%20sk-zeli-... query parameter (for browser clients that can't set headers).
{ "text": " ", "authorization": "Bearer sk-zeli-...", "voice_settings": { "stability": 0.5 } }

See Realtime WebSocket for the full protocol.

Errors

Auth failures use the ZeliSpeech error envelope:

{ "detail": { "status": "missing_api_key", "message": "..." } }
Status codestatusWhen
401missing_api_keyAuth is required but no key was sent
401invalid_api_keyThe key sent isn't recognized (or has expired)
401insufficient_scopeAn ephemeral token was used on a management route

See Errors for the complete list.

Ephemeral tokens (browser audio)

Never ship a real zsk_live_ key to a browser. Instead, your backend mints a short-lived, synthesis-only token and hands it to the page; the browser then streams audio directly from the API until the token expires.

POST /v1/tokens/ephemeral            (requires a FULL API key)
{ "ttl_seconds": 300 }               // clamped to 30–600, default 300
 
→ { "token": "zsk_temp_…", "expires_at": 1770000000, "scope": "tts" }

Ephemeral tokens can call synthesis and read endpoints (/v1/text-to-speech/*, GET /v1/voices*, the realtime WebSocket) but not voice cloning/removal or token minting — those return 401 insufficient_scope. Expiry is enforced server-side on every request.

Backend (Node) → browser flow:

// backend route — holds the real key
import { ZeliSpeech } from "zelispeech";
const zeli = new ZeliSpeech({ baseUrl: BASE, apiKey: process.env.ZELISPEECH_API_KEY });
app.post("/api/tts-token", async (req, res) => res.json(await zeli.tokens.createEphemeral()));
 
// browser — uses only the temp token
const { token } = await (await fetch("/api/tts-token", { method: "POST" })).json();
const zeliClient = new ZeliSpeech({ baseUrl: BASE, apiKey: token });
const clip = await zeliClient.textToSpeech.convert("zeli-voice-1", "Hello!");
new Audio(URL.createObjectURL(new Blob([clip], { type: "audio/mpeg" }))).play();

Ephemeral tokens require the portal keys table on the box (ZELI_API_KEYS_TABLE); a static-keys-only box returns 501 not_supported.

Provisioning a public box

Checklist
  1. Set ZELI_API_KEY (or ZELI_API_KEYS) on the box.
  2. Terminate TLS in front — clients expect https://.
  3. Optionally pin the swap-out voice with ZELI_EL_DEFAULT_VOICE=zeli-voice-1.

Per-key concurrency limits, queueing, and 429 rate limiting are on the roadmap (the engine is batch-1, so concurrency control matters for public boxes).

Zeligate Voice API · self-hosted · secure data sovereignty · source