- Control plane (control-plane API tRPC: voice control, streams CRUD, agents, webhooks, analytics) — long-lived API keys scoped to an org and a set of capabilities.
- Data plane (MoQT publish / subscribe, audio bridge, robotics topics, games rooms, data pub/sub) — short-lived relay tokens scoped to a set of namespaces, verified by the relay’s namespace_auth hook on every publish or subscribe.
Authenticate your server: API keys
Give your backend an API key so it can call the control plane. Create one in the dashboard or viastreams.apiKeys.create({ orgId, label, scopes }). The key string is
returned once, at creation; the control-plane API stores only the hash.
Authenticate a session: relay tokens
To let a client publish or subscribe on the data plane, mint it a relay token instead of sharing your API key. The relay never sees the API key. The control-plane API mints a short-lived relay token — a signed JWT carrying namespace claims — and the client presents that during the MoQT handshake. The relay’snamespace_auth hook verifies the JWT signature, extracts the
namespace scope, and gates every subsequent publish / subscribe against it.
A typical relay token looks like:
| Claim | Meaning |
|---|---|
kid | Signing-key id; the relay fetches the matching public key from Redis. |
iss | The control-plane API that minted the token. |
sub | Tenant id the relay stamps on the session. |
ns | Namespace patterns this token may publish or subscribe to. |
exp | Expiry (typically 1h for client tokens, longer for service-to-service). |
Authenticate an end user in the browser
In the browser, you cannot ship a long-lived API key. Instead, mint a short-lived relay token on your server for each browser session and pass it to the SPA:browserToken skips the API-key-on-client antipattern; the SDK presents
it directly to the relay.
Rotate keys without downtime
Every relay token is signed with a per-org signing key. Rotate keys without downtime by:streams.signingKeys.create({ orgId, label })— issues a new active key- wait for clients with old tokens to expire (1h by default)
streams.signingKeys.retire({ id })— old key stops minting new tokens
Legacy: service-account JWT
The original control plane (theTeleQuickClient root import — dial,
hangup, barge, push_audio) authenticated with an RSA service-account
JSON file pointed at by TELEQUICK_CREDENTIALS:
iss=telequick-sdk, sub=tenant_id)
and presented it on the QUIC handshake. This still works for backwards
compat — new code should use API keys + relay tokens via the Voice modality.
Related
- Modalities overview
- Architecture — namespace_auth fits into the relay
- Streams reference —
apiKeys,signingKeys