This page explains the credentials people use to join a live stream, and why a viewer can never broadcast.
Where this fits: this is the same token model as the rest of Livqeno. Your backend holds the API key, and your app only ever gets short-lived tokens (see Access tokens). The difference is that a live-stream credential bundles two tokens, one for the video and one for the chat, and which call your backend makes decides the person's role.
There are two kinds of credential:
- a host credential, which can broadcast, and
- a viewer credential, which can only watch.
Your backend makes them with the API key:
// Host/co-host — full publish permission, chat ADMIN/MODERATOR scope
const hostCredential = await livqeno.liveStreams.addHost(streamId, { identity: 'alice', role: 'HOST' });
// Viewer — subscribe-only, chat MEMBER scope
const viewerCredential = await livqeno.liveStreams.createViewerToken(streamId, 'carol');from livqeno import AddHostParams
host_credential = livqeno.live_streams.add_host(stream_id, AddHostParams(identity="alice", role="HOST"))
viewer_credential = livqeno.live_streams.create_viewer_token(stream_id, "carol")# Host/co-host — full publish permission, chat ADMIN/MODERATOR scope
curl -X POST "$LIVQENO_API_URL/v1/live-streams/$STREAM_ID/hosts" \
-H "Authorization: Bearer $LIVQENO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"identity": "alice", "role": "HOST"}'
# Viewer — subscribe-only, chat MEMBER scope
curl -X POST "$LIVQENO_API_URL/v1/live-streams/$STREAM_ID/viewer-tokens" \
-H "Authorization: Bearer $LIVQENO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"identity": "carol"}'What each role gets
| Role | Publish video/audio | Chat scope | Can add/remove hosts |
|---|---|---|---|
HOST | Yes | ADMIN | Yes |
CO_HOST | Yes | MODERATOR | No |
| Viewer | No | MEMBER | No |
Hosts and co-hosts can publish the same things. The difference is their chat scope. Adding and removing hosts is always done by your backend, so your app decides who may do it. See Hosts & co-hosts.
What's inside a credential
Both calls return the same shape:
| Field | What it is |
|---|---|
streamId | The stream to join. |
identity | The user this credential is for. |
role | HOST, CO_HOST, or VIEWER. For your UI only. The real permissions are signed into the tokens. |
rtc | A normal RTC token grant: token, endpoint, iceServers, and more. |
chat | A normal chat token for the stream's conversation. |
chatRootMessageId | The hidden message that reactions attach to. |
Send the whole object to the app, unchanged. LiveStream.join() reads all of it.
How long a credential lasts
The RTC token inside expires like any RTC token. You don't set the lifetime on these calls, so it uses the default (10 minutes in the stock configuration).
- It only matters for joining. A host who joined in time isn't kicked out when the token expires mid-stream.
- It matters again if they need to reconnect. See Network quality.
- To get fresh credentials, call
addHostorcreateViewerTokenagain for the same identity.
A viewer can't become a host
This is the key safety rule of Live Streaming. Even if someone edits your app or replays a request, a viewer can't broadcast:
- The viewer-token request has no
rolefield. There's nothing to change. - The RTC token always has
publish,publishAudio,publishVideoandpublishDataset tofalse. - The chat token always has
MEMBERscope. - Sending an unknown field like
roleis rejected, not ignored. - The permissions are signed into the token, so the app can't widen them.
The only way to let someone broadcast is for your backend to call addHost
for them.
Under the hood
These are normal RTC and Chat
tokens. There's no third token type. In @livqeno/live, LiveStream.join()
takes whatever your role's call returned.
Next steps
- Quickstart: see both credentials used end to end.
- Hosts & co-hosts and Viewers: when to make each one.