A room is the unit of presence in LevelChat. Every call, every meeting, every broadcast is a room. Rooms are cheap to create (sub-100 ms p99) and idle rooms cost nothing — they auto-archive when the last participant leaves.
Lifecycle
created → active → ended → archived- A room is
createdwhen you POST/v1/rooms. - It enters
activethe moment the first participant joins. - It enters
endedwhen the last participant leaves or when youDELETE /v1/rooms/{id}. - It enters
archived24 hours afterended(rooms then become read-only — recordings remain).
Create a room
A standalone @levelchat/node SDK is on the roadmap; until it ships, the public REST API is
all you need:
const res = await fetch(`${process.env.LEVELCHAT_API_URL}/v1/rooms`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.LEVELCHAT_API_KEY}`, // lc_pk_xxx.yyy
},
body: JSON.stringify({
external_id: 'team-standup-2026-04-25',
type: 'meeting', // meeting | live | webinar | 1to1
config: {
max_participants: 50,
preferred_codec: 'av1',
record: { enabled: false },
e2ee: false,
region_hint: 'eu-fsn',
},
}),
});
const room = await res.json(); // { id, external_id, type, config, created_at }In most apps you don't pre-create rooms — call POST /v1/rooms/<id>/tokens directly with any
deterministic id your app picks (r_team-standup-2026-04-25) and the room is created on the
fly with sane defaults.
Pick the right type:
- meeting — full-mesh-ish, everybody can publish. ≤ 50 participants.
- broadcast — one (or few) publishers, many viewers. Cascade SFU + LL-HLS fallback at scale.
- hybrid — meeting that can promote to broadcast mid-session.
- classroom — meeting with raised-hand, breakouts, attendance.
- webinar — broadcast with Q&A queue + registration gating.
Token capabilities
Tokens are scoped, not roles. A publisher token with no subscribe:* cap will not see anyone.
A subscriber token with chat:send can talk in chat without ever appearing on camera.
const cap = ['publish:camera', 'publish:screen', 'subscribe:all', 'chat:send'];Common combinations:
| Use case | Capabilities |
|---|---|
| Standard meeting participant | publish:camera, publish:screen, subscribe:all, chat:send |
| Read-only viewer | subscribe:all, chat:send, reactions:send |
| Stage participant in a broadcast | publish:camera, subscribe:all |
| Classroom student | publish:camera, subscribe:all, chat:send, hand:raise |
| Moderator | moderate:participants, chat:send, subscribe:all |
Listening for events
import { LevelChat } from '@levelchat/web';
const lc = new LevelChat();
const live = await lc.joinLive({ token, signalingUrl: url, roomType: 'meeting' });
live.room.on('participant-joined', (p) => console.log(p.identity, 'joined'));
live.room.on('participant-left', (p) => console.log(p.identity, 'left'));
live.room.on('track-subscribed', (t) => console.log(t.participantId, 'published', t.kind));
live.room.on('connection-quality', (id, q) => console.log('rtt', q.rtt, 'loss', q.fractionLost));
live.room.on('error', (err) => console.error(err.code, err.message));Ending a room
await lc.rooms.end(room.id); // or DELETE /v1/rooms/{id}This kicks every participant with reason: "host_ended" and triggers room.ended webhook.