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