Get started / Authentication
Authentication
Use an API key from your Zeli Avatar account for server-side integrations. For browser streaming, your backend exchanges that key for a short-lived session token.
Get an API key
- Sign in to the developer portal.
- Open API keys, create a named key, and copy it when shown.
- Store it in your backend's secret manager or environment as
ZELI_API_KEY. Do not commit it or include it in browser bundles.
Use this deployment's hosted base URL:
https://zeli-avatar.zelibot.xyzThe hosted service requires credentials for protected routes. Public visitor links use a separate consent and session flow; they do not expose your API key. See Share links.
Presenting a key
Send your key as X-Api-Key on routes that accept API keys. Streaming routes and token minting also accept Authorization: Bearer <key>; do not use that alternative for account-backed /auth/* API-key requests. The SDK sends the credential for you when you pass
ZeliClient(api_key=...).
import os
from zeli import ZeliClient, AvatarConfig, ClientOptions
# The SDK forwards the key on signalling + the control WebSocket.
client = ZeliClient(
api_key=os.environ["ZELI_API_KEY"],
avatar_config=AvatarConfig(avatar_id=os.environ["ZELI_AVATAR_ID"]), # Copy ID from your library
options=ClientOptions(server_url="https://zeli-avatar.zelibot.xyz"),
)curl -X POST "https://zeli-avatar.zelibot.xyz/v1/streaming.create_token" \
-H "X-Api-Key: zsk_live_..." \
-H "Content-Type: application/json" \
-d '{"ttl_seconds": 300}'Full keys vs. session tokens
There are two credential kinds, with different scope:
| Credential | Scope | Minted by | Can mutate? |
|---|---|---|---|
API key (zsk_live_…) | Authorized SDK and management routes | Your signed-in account in the portal | Yes, for your resources |
Session token (zsk_temp_…) | Stream-only (tts) | A full key | No |
A full key authorizes the SDK and management routes documented for API keys. It does not replace portal authentication on account-only routes. A session token is stream-only: it can drive an avatar stream but never mutate server state. This is what you hand to a browser, so a real key never leaves your backend.
Minting a session token
Your backend calls streaming.create_token with a full key and hands the
short-lived token to the browser.
Lifetime of the token in seconds. Clamped to the range 30 to 600.
// Request (full-key gated, X-Api-Key: zsk_live_...)
{ "ttl_seconds": 300 }
// Response
{
"data": {
"token": "zsk_temp_...",
"expires_at": 1770000000,
"scope": "tts"
}
}The browser then passes the token on the /connect signalling request and on the
control WebSocket as a ?session_token=... query parameter:
POST /connect?session_token=zsk_temp_...
WSS <control-gateway>?session_token=zsk_temp_...A leaked session token can drive a stream until it expires, but it can never
mutate. A mutation attempt returns 401 insufficient_scope. Expiry
is enforced server-side on every request.
Error envelope
Auth failures return 401 with a small JSON envelope:
{ "code": "missing_api_key", "message": "..." }| Status | code | When |
|---|---|---|
401 | missing_api_key | Auth is required but no key was sent |
401 | invalid_api_key | The key sent isn't recognized (or has expired) |
401 | insufficient_scope | A session token was used on a mutating / management route |
In the SDK, a rejected key or token surfaces as
AuthenticationError.
Account and portal routes
Account management, API-key creation, and Persona owner routes require a signed-in account. An SDK API key does not grant access to every /auth/* endpoint. Each reference page identifies its accepted credential. Use X-Api-Key for the account-backed API routes that explicitly accept API keys; Authorization on those routes is reserved for the signed-in identity token.
Self-hosting
Operating your own server has a separate authentication configuration. Follow the operator authentication guide before exposing a self-hosted service. Hosted users do not need server environment variables or a database setup.