LevelChatDocs
Docs
Rooms

Rooms

Lifecycle, capabilities, events.

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

text
created  →  active  →  ended  →  archived
  • A room is created when you POST /v1/rooms.
  • It enters active the moment the first participant joins.
  • It enters ended when the last participant leaves or when you DELETE /v1/rooms/{id}.
  • It enters archived 24 hours after ended (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:

TypeScript
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.

TypeScript
const cap = ['publish:camera', 'publish:screen', 'subscribe:all', 'chat:send'];

Common combinations:

Use caseCapabilities
Standard meeting participantpublish:camera, publish:screen, subscribe:all, chat:send
Read-only viewersubscribe:all, chat:send, reactions:send
Stage participant in a broadcastpublish:camera, subscribe:all
Classroom studentpublish:camera, subscribe:all, chat:send, hand:raise
Moderatormoderate:participants, chat:send, subscribe:all

Listening for events

TypeScript
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

TypeScript
await lc.rooms.end(room.id); // or DELETE /v1/rooms/{id}

This kicks every participant with reason: "host_ended" and triggers room.ended webhook.