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#
| Capability | host | cohost | audience |
|---|---|---|---|
| Publish microphone, camera and screen | Yes | Yes | No |
| Subscribe to other participants | Yes | Yes | Yes |
| Send and receive data messages | Yes | Yes | Yes |
| Administer the channel | Yes | No | No |
| Create the channel by joining it | Yes | No | No |
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.
function roleFor(user, channel) {if (channel.ownerId === user.id) return 'host'; // runs the roomif (channel.speakers.includes(user.id)) return 'cohost'; // invited to speakreturn 'audience'; // everyone else}const { token, wsUrl } = await applooma.createToken({roomName: channel.name,identity: user.id,role: roleFor(user, channel),metadata: { name: user.displayName },});
| Scenario | Who gets which role |
|---|---|
| Two-person video call | Both participants are host. |
| Team meeting with a moderator | Moderator is host, everyone else is cohost. |
| Live stream | Broadcaster is host, viewers are audience. |
| Audio room | Room owner is host, speakers are cohost, listeners are audience. |
| Webinar with invited guests | Presenter 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.
| Field | Type | Rules |
|---|---|---|
role | string | Required. host, cohost or audience. |
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
hostandcohostmay publish;audiencemay not. Every role keeps subscribe and data-message rights.- Only the
rolefield of the participant's metadata changes. Anything else you put there, such asname, is kept. - Moving someone to
audiencewithdraws 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.
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 recordawait 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.
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.
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 speakerawait rejoinAs('cohost');// Sent back to the audienceawait 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
audiencetoken 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 arehost,cohostandaudience. - Audience members still count as participants for billing while they are connected. See Usage and quotas.