Roles and permissions

Every token carries exactly one role. The role decides whether the participant may send media and whether they may administer the channel. There are three roles, and the differences between them are small enough to fit in one table.

Role versus capability#

Capabilityhostcohostaudience
Publish microphone, camera and screenYesYesNo
Subscribe to other participantsYesYesYes
Send and receive data messagesYesYesYes
Administer the channelYesNoNo
Create the channel by joining itYesNoNo

host

The full set. A host can publish media, subscribe to everyone, send data messages, administer the channel and bring the channel into existence by joining it. Channel administration covers acting on the channel and its participants from within the session — for example removing a participant or muting a published track.

Use host for the person running the call, the broadcaster in a stream, or every participant in a small peer-to-peer meeting.

cohost

A speaker without administrative power. A cohost publishes and subscribes exactly like a host, but cannot administer the channel and cannot create it. If a cohost is the first to arrive at a channel that does not exist yet, the join fails — a host must open it first.

Use cohost for guests, invited speakers, and promoted audience members.

audience

Receive-only for media. An audience member subscribes to everyone else's tracks but publishes nothing — no microphone, no camera, no screen. Data messages still work, so audience members can use chat, reactions and any custom signalling you build.

Use audience for viewers of a live stream and for listeners in an audio room.

The default role is host

If your token request omits role, the token is issued as host. Pass the role explicitly on every call, or a listener will silently receive publishing and administrative rights.

Choosing a role#

The role is decided on your server, when you issue the token. Derive it from your own records — never from the request body.

On your token endpoint
function roleFor(user, channel) {
if (channel.ownerId === user.id) return 'host'; // runs the room
if (channel.speakers.includes(user.id)) return 'cohost'; // invited to speak
return 'audience'; // everyone else
}
const { token, wsUrl } = await applooma.createToken({
roomName: channel.name,
identity: user.id,
role: roleFor(user, channel),
metadata: { name: user.displayName },
});
ScenarioWho gets which role
Two-person video callBoth participants are host.
Team meeting with a moderatorModerator is host, everyone else is cohost.
Live streamBroadcaster is host, viewers are audience.
Audio roomRoom owner is host, speakers are cohost, listeners are audience.
Webinar with invited guestsPresenter is host, guests are cohost, attendees are audience.

Promoting a listener to a speaker#

There are two ways to change someone's role. The first changes it in place, while they stay connected. The second issues a new token and has the client rejoin. Use the first one; the second is still useful when you want the new role written into the token itself.

Change the role in place (recommended)

POST /v1/rooms/:roomName/participants/:identity/role— API key auth, so call it from your server. The media server updates the participant's publish permission and their metadata on the spot. Their session continues: no leave, no join, no gap in audio and no minutes lost to a round trip.

FieldTypeRules
rolestringRequired. host, cohost or audience.
change-role.sh
curl -X POST https://api.applooma.dev/v1/rooms/party-9/participants/rina/role \
-H "x-applooma-key: ak_your_key" \
-H "x-applooma-secret: as_your_secret" \
-H "Content-Type: application/json" \
-d '{ "role": "cohost" }'
# → 200 { "roomName": "party-9", "identity": "rina", "role": "cohost" }
# → 400 role must be host, cohost or audience
# → 404 Participant not found in that room
  • host and cohost may publish; audience may not. Every role keeps subscribe and data-message rights.
  • Only the role field of the participant's metadata changes. Anything else you put there, such as name, is kept.
  • Moving someone to audience withdraws their publish permission, so their microphone, camera and screen share stop reaching the room.
  • The change lasts for this session. If the client later rejoins, the role in its token applies again, so record the promotion in your own data and issue the matching role next time.

On the device: the Android SDK reads the new role, turns the microphone on or off to match and calls AppEventHandler.onRoleChanged(role); engine.currentRole returns the new value. On the other SDKs, tell the client it was promoted (a data message is the easy way) and have it call enableMicrophone() once it is on stage.

server.js
app.post('/api/promote', async (req, res) => {
const actor = await authenticate(req);
const channel = await channels.find(req.body.channel);
if (channel.ownerId !== actor?.id) return res.status(403).end();
await channel.addSpeaker(req.body.userId); // your own record
await fetch(`https://api.applooma.dev/v1/rooms/${channel.name}/participants/${req.body.userId}/role`, {
method: 'POST',
headers: {
'x-applooma-key': process.env.APPLOOMA_API_KEY,
'x-applooma-secret': process.env.APPLOOMA_API_SECRET,
'Content-Type': 'application/json',
},
body: JSON.stringify({ role: 'cohost' }),
});
res.json({ ok: true });
});

Rejoin with a new token

The role in a token does not change by itself. If you prefer, issue a new token with the new role and have that client rejoin the channel. Rejoining is quick, and because the identity is the same, other participants see the same person return with publishing rights.

Keep the identity the same

Reuse the participant's existing identity when you reissue the token. A new connection with an existing identity replaces the old one, so the promotion is a clean handover rather than a duplicate participant.

Rejoin: on your server

Add an endpoint that records the promotion in your own data and returns a fresh token. Authorise it: only a host should be able to promote someone.

promote.js
app.post('/api/promote', async (req, res) => {
const actor = await authenticate(req);
if (!actor) return res.status(401).json({ error: 'Sign in first' });
const channel = await channels.find(req.body.channel);
if (channel.ownerId !== actor.id) {
return res.status(403).json({ error: 'Only the host can promote' });
}
await channel.addSpeaker(req.body.userId); // your own record
// Tell that client to rejoin — for example over a data message,
// your websocket, or a push notification.
res.json({ ok: true });
});
// The promoted client calls your normal token endpoint again.
// roleFor() now returns 'cohost' for them.

Rejoin: on the client

When the client learns it has been promoted, it fetches a new token and rejoins the same channel.

promote.js
async function rejoinAs(role) {
await engine.leaveChannel();
const res = await fetch('/api/rtc-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ channel: 'lobby' }),
});
const { token, wsUrl } = await res.json();
await engine.joinChannel(token, wsUrl, { role, microphone: role !== 'audience' });
}
// Promoted to speaker
await rejoinAs('cohost');
// Sent back to the audience
await rejoinAs('audience');

Things worth knowing#

  • A role is per token, not per user. The same person can be a host in one channel and audience in another at the same time.
  • An audience token cannot be made to publish by any client-side option. The restriction is enforced by the token, not by the SDK.
  • An unknown role value is rejected with 400. The only accepted values are host, cohost and audience.
  • Audience members still count as participants for billing while they are connected. See Usage and quotas.

Where to go next#