The room service and the relay
A live room runs on two services, both reference implementations in the open-standards
repository: the relay (wai-relay, the crate joule-moq-relay), which carries every participant’s tracks and
their receipts, and the room service (wai-room, the crate joule-moq-room), which holds a room’s keys, issues
grants and authors the room’s lifecycle. wai dev runs both on your machine, the room
service under /room-api/ and the relay’s WebSocket binding at /moq on the page’s own
origin, as a production front serves them. This page describes them as the code serves them.
The same API is described in OpenAPI 3.1 in the repository, beside the room service
(joule-code/joule-code-rs/crates/joule-moq-room/openapi.json), and a test holds it to the
code.
The room service
Every answer is JSON, with Cache-Control: no-store. An error is {"error": "<reason>"} with
the status that fits it. Requests past a client’s allowance are answered 429 with
retry_after_s.
The room
| Route | What it does |
|---|---|
GET /health | {"ok": true, "relay": <bool>, "room": "<id>"}: relay is whether the room’s clock holds its session on the relay |
GET /room | The room as a page verifies it (live-rooms §9): room (its id), descriptor (the signed descriptor, hexadecimal), compose (the composition descriptor), grants (every grant issued in it), present, publishers, max_publishers, t0_us, the service’s clock as now_us, and the room’s extension records where it has them (e2ee, remote, age, co, watch) |
POST /grant | A grant for a key the page made. Body: {"principal": "<Ed25519 public key, 64 hexadecimal digits>", "role": "publisher" | "viewer"}. Answer: {"grant": "<WRG1 record, hexadecimal>", "digest", "exp", "role", "room": {…as GET /room}}. A full room is 409 full with max_publishers; a service whose clock is not on the relay is 503 relay |
A grant is public: every participant holds every grant of a room, to verify it. It is useless without its key, since the relay honours it only beside a proof of possession over the relay’s own challenge (live-rooms §10.5).
What a room may also hold
Each is the room service’s when its configuration turns that kind of room on; otherwise its
routes answer 404.
| Routes | The room | Extension |
|---|---|---|
GET /watch/room, GET /watch/source, GET /watch/time, GET /watch/challenge, POST /watch/join, POST /watch/post, GET /watch | A watch room: its policy and source, the room clock, joining with a proof of the key, the co-watch log | co-watch |
GET /co/state, GET /co/objects, POST /co/schedule, /co/handover, /co/window, /co/fidelity | Co-performance: turns, hand-overs, the audience’s selection windows, fidelity | co-performance |
GET /mls, POST /mls/key-package, POST /mls/post | An end-to-end encrypted room’s MLS log | confidentiality |
GET /age/receipt | A room that admits by age: its admission receipt; POST /grant then carries a Privacy Pass token (RFC 9577) | age-assurance |
POST /remote/control, GET /remote/room, GET /remote/state | A remote desktop room: a grant exchange that hands control over, the session’s records, the floor | remote-desktop |
GET /msg, GET /msg/challenge, POST /msg/join, GET /msg/log, POST /msg/key-package, /msg/mls, /msg/post, /msg/ack, /msg/leave, /msg/remove | A conversation: joining with a proof of the key, its log, sealed messages, acknowledgements | live-messaging |
GET /call/challenge, POST /call/place, POST /call/ring, GET /inbox, POST /call/answer, GET /call | A call: placed with a proof of the caller’s key, rung in each invitee’s inbox, answered; its grants come with it | live-messaging §10 |
A request that changes something in one of these rooms proves the key of the grant it
presents over a challenge the service gives (live-rooms §10.5.3): the grant as
Authorization: Bearer, the proof in WAI-Room-Proof, the challenge from WAI-Room-Challenge
on an earlier answer.
The relay
The relay speaks MOQT, draft-ietf-moq-transport-22, three ways on one configuration:
| Path | Where | Selected by |
|---|---|---|
| Native QUIC | a UDP port | ALPN moqt-22 |
| WebTransport over HTTP/3 | the same UDP port | ALPN h3, an extended CONNECT to its path |
| WebSocket | a TCP port, behind the front that holds the TLS | an upgrade at its path with the subprotocol moqt-22 |
A browser behind a front that carries TCP alone uses the WebSocket binding, as wai dev and
the public room do: new WebSocket("wss://<origin>/moq", "moqt-22"), each message one capsule.
Joining a room. The relay’s SETUP carries a challenge drawn for the session. The client
answers with its own SETUP carrying the grant as its authorization token and a proof by the
grant’s key over that challenge and the address it connected to; the relay checks both before
it serves any request, and closes a session whose proof is missing or made by another key.
The room’s authority is named in the relay’s configuration ([[auth.authorities]];
wai-room relay-lines <keys.toml> prints the lines).
Tracks. Each participant publishes under the namespace [room, participant, kind] (audio,
video, screen, events), each track beside its receipts track, its name followed by
_receipts. Every object’s receipt is forwarded whether the object is delivered or dropped,
and a relay configured to check the room’s tracks at its door holds each object to its
receipt before it forwards it. A late joiner fetches the groups before it joined.
The relay’s own README, in the repository beside its source, covers its configuration, its caches and delivery timeouts, cascading one room across relays, and the per-hop session record it signs.