Zeli AvatarDeveloper docs
v0.1.0

Get started / Authentication

Authentication

The avatar server authenticates with an API key presented as X-Api-Key. Auth is configured on the box, so a public deployment requires a key while an embedded loopback box can run open. The wire schema mirrors HeyGen (X-Api-Key, streaming.create_token) so migrating an existing integration is familiar.

When auth is on

Server auth is off unless you configure keys. Set one of these on the box:

  • ZELI_API_KEYS — a comma-separated list of full API keys.
  • ZELI_API_KEYS_TABLE — a DynamoDB table of keys (adds session-token minting).
Unset means open

If neither variable is set, the server is open — correct for a loopback/embedded box (the on-instance avatar for the AI pipeline), wrong for a public one. A standalone / public box must set one of the two before it is reachable on a network.

Presenting a key

Send your key on every request as X-Api-Key (an Authorization: Bearer <key> header is also accepted). The SDK sends it for you when you pass ZeliClient(api_key=...).

from zeli import ZeliClient, AvatarConfig, ClientOptions
 
# The SDK forwards the key on signalling + the control WebSocket.
client = ZeliClient(
    api_key="zeli_...",
    avatar_config=AvatarConfig(avatar_id="presenter-male-1080"),
    options=ClientOptions(server_url="https://avatar.zeligate.ai"),
)

Full keys vs. session tokens

There are two credential kinds, with different scope:

CredentialScopeMinted byCan mutate?
Full keyEverythingConfigured on the boxYes
Session token (zsk_temp_…)Stream-only (tts)A full keyNo

A full key calls everything. 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.

POST/v1/streaming.create_token
ttl_secondsintOptionaldefault: 300

Lifetime of the token in seconds. Clamped to the range 30–600.

// Request  (full-key gated — X-Api-Key: zeli_...)
{ "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_...
Never ship a full key to a browser

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": "..." }
StatuscodeWhen
401missing_api_keyAuth is required but no key was sent
401invalid_api_keyThe key sent isn't recognized (or has expired)
401insufficient_scopeA session token was used on a mutating / management route

In the SDK, a rejected key or token surfaces as AuthenticationError.

Provisioning a public box

Checklist
  1. Set ZELI_API_KEYS (or ZELI_API_KEYS_TABLE) on the box.
  2. Terminate TLS in front — clients expect https:// / wss://.
  3. For browser streaming, mint short-lived session tokens with streaming.create_token from your backend; never ship a full key to the page.
Zeli Avatar · real-time avatars over WebRTC · self-hostable · AU data residency · source