mod_meet) has no create API: it
materialises a room the first time a participant joins with a token that
carries that name, and it reaps the room when the last participant leaves.
Everything the console shows under Rooms is therefore live engine state,
read back from the engine on each poll — not a database table you can pre-seed,
rename, or archive.
When a room is created and when it is reaped
- You pick a name and mint a token for it.
- A client joins with that token. The engine creates the room at that moment.
This is the only way a room comes into existence — there is no
rooms.createprocedure and no “create room” action in the console. - The room is listed by
meet.rooms.listwith a participant count and acreatedAttimestamp (the console renderscreatedAtas the room’s “up” time). - When the last participant leaves, the engine reaps the room. Its name disappears from the listing, along with its participant and track state.
Room name grammar
A room name is carried in a token claim, validated by the control plane’sroomName schema, and then used as an element of the MoQT namespace for the
room’s tracks. That last point is the constraint that matters: the name has to
survive being placed in a namespace path, so it is restricted to unreserved
characters only — no percent-encoding, no slashes, no spaces, nothing that
would need escaping to sit in a namespace element.
Practical consequences:
- A name is an opaque identifier, not a title. Put the human-readable subject of the meeting in your own application data, not in the room name.
- Two different names are two different rooms, even if they differ only in case or punctuation. Nothing normalises them for you.
- A name that the schema rejects fails at the control plane before the engine is ever contacted, so the failure surfaces as a rejected mutation rather than as a transport-level join error.
Names the console mints
New meeting does not ask you for a name; it generates one and navigates straight into the join flow. The generated shape is:x positions are drawn with crypto.getRandomValues from a 32-character
alphabet — abcdefghijkmnpqrstuvwxyz23456789 — which is the lowercase
alphanumerics with the visually ambiguous characters (l, o, 0, 1)
removed. The names are random rather than sequential, so holding one room’s
name tells you nothing about any other room’s name.
You are not required to use this shape. It is simply a name that is guaranteed
to satisfy the schema, safe to read aloud, and not guessable from a sibling.
How the name becomes a namespace element
The room name is the element that separates one room’s media from another’s in the MoQT namespace. Tokens are scoped to namespaces, so the room name in the token is what gates publish and subscribe for that room: a token minted for one room name cannot carry media for a different one. Two things follow from this:- Treat the name as a capability-adjacent value. Anyone who holds a valid token for a name can join that room. Do not derive names from sequential identifiers, and do not reuse a name across unrelated meetings if the tokens for the earlier meeting have not expired.
- A name change is a new room. There is no rename. Moving a meeting to a different name means minting new tokens and having clients join the new name; the old room reaps itself once its last participant leaves.
Empty rooms: why one can still be listed
Opening Participants on a room can show “Nobody is in this room”. That is not a bug and not an inconsistent read. It means the engine still holds the room, but no participant is currently in it — the room has not been reaped yet. You can also see a room that looks emptier than its own header suggests:- The room header reports
numParticipantsand, when it differs, the number of participants that are hidden (participants whosehiddenpermission is set). Hidden participants are counted in the room, and the difference between the total and the visible count is shown explicitly in the room subtitle. - A participant that has just joined can appear with no tracks at all. The engine registers a track when it first carries media, so an empty track list means nothing has arrived from that participant yet — it is not a statement that they are refusing to publish.
Closing a room versus waiting for it to reap
The Close action on a room card callsmeet.rooms.remove. It does not
delete a stored record, because there is no stored record. It ejects every
participant currently in the room, which is what causes the engine to drop
the room.
Removing a single participant (
meet.participants.remove) is the narrower
version of the same idea: that person is disconnected immediately, and they can
rejoin with a valid token. Neither action revokes anything.
If a close is rejected, the console surfaces the error on the room card rather
than optimistically dropping the row — a room that is still listed after a
failed close is still open.
Rejoining a name after a close
Because a close only ejects participants, and because tokens are unaffected by it, a join that arrives after the close re-creates the room under the same name. This includes joins that were already in flight when you clicked Close: they are racing the eject, and whichever lands last wins. A client that reconnects automatically will therefore reappear in a room you just closed. Closing a room is consequently a way to clear a room, not a way to end a meeting permanently. To make a name unjoinable you have to stop honouring it at token-mint time — stop minting tokens for that name, and let the tokens already out there expire. See Authentication for how token expiry and signing-key rotation interact. The reverse case is the ordinary one: a room that reaped itself is recreated by the next join with a valid token for that name, with fresh participant, track, and permission state. Nothing carries over from the previous occupancy.Joining requires a reachable engine
Rooms are read from the engine, so the console checksmeet.status before it
lets you into a room. The check distinguishes three states, and they mean
different things for room lifecycle:
New meeting is disabled unless the engine is reachable. This is deliberate:
joining a room on an unreachable engine fails at the transport with a much less
legible error than a disabled button.
Configuration is per organization — each org has its own engine address and its
own rotatable signing key — so a room name is only ever meaningful within the
org whose engine holds it.
Related
- Authentication — minting the token that carries the room name, and namespace scoping
- Telemetry — the operational streams the gateway emits