{ "openapi": "3.1.0", "info": { "title": "WAI room service", "version": "0.1.0", "license": { "name": "Apache-2.0", "identifier": "Apache-2.0" }, "description": "The HTTP API of the room service (`joule-moq-room`), which keeps WAI live rooms (`wai/extensions/live-rooms.md`) over a relay: it issues grants, authors each room's lifecycle on its clock's events track, and serves the signed room. A configuration turns on the modes a room keeps: end-to-end encryption (`[room] e2ee`), admission by age (`[age]`), co-performance (`[co]`), a watch room (`[room] watch_source`), a remote desktop room (`[remote]`), a conversation (`[messaging]`), calls (`[call]`) or ads (`[ads]`). Each mode's paths answer 404 in a service that does not keep it.\n\nEvery answer but a watch room's source is JSON with `Cache-Control: no-store`. Each operation's `operationId` is its name in the WAI toolchain's catalogue, `noun.verb` (`room.grant`), the name every other door derives its own from: the MCP tool `room_grant`, the command `wai room grant`. A path answers 405 to a method it does not take (`/msg/*` and `/call/*` paths answer 404). The statuses listed for each operation are those its handler can return; `tests/openapi_contract.rs` holds this description to the service, route by route and status by status.\n\nRequests that a principal authorises carry its grant as `Authorization: Bearer ` and a proof of possession of the grant's key in `WAI-Room-Proof`, made over a challenge the service issued in `WAI-Room-Challenge` (live-rooms §10.5.3); a request that proves only a key carries the proof alone, naming no grant. The service listens on loopback behind a front that serves it under `public_prefix`; a proof names the path the client addressed." }, "servers": [ { "url": "http://127.0.0.1:9682", "description": "the example configuration's address (`room.example.toml`)" }, { "url": "http://127.0.0.1:8080/room-api", "description": "the service behind `wai dev`'s front, on its default port" } ], "tags": [ { "name": "room", "description": "every room" }, { "name": "age", "description": "admission by age (age-assurance §5.6)" }, { "name": "e2ee", "description": "end-to-end encrypted rooms (confidentiality)" }, { "name": "co-performance", "description": "co-performance" }, { "name": "watch", "description": "watch rooms (co-watch)" }, { "name": "remote", "description": "remote desktop rooms (remote-desktop)" }, { "name": "messaging", "description": "a conversation (live-messaging)" }, { "name": "calls", "description": "calls (live-messaging §10)" }, { "name": "ads", "description": "A room's ads (`[ads]`): the session's key and region, its device binding and claims, the asset lists and creatives, and the room's records (live-ads §14.8, ad-presentation §14)." } ], "paths": { "/": { "get": { "operationId": "service.show", "tags": [ "room" ], "summary": "The service's health", "description": "As `GET /health`.", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "relay": { "type": "boolean", "description": "the clock's session on the relay is open" }, "room": { "$ref": "#/components/schemas/Hex32" } }, "required": [ "ok", "relay", "room" ] } } } } } } }, "/health": { "get": { "operationId": "service.check", "tags": [ "room" ], "summary": "The service's health", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "relay": { "type": "boolean", "description": "the clock's session on the relay is open" }, "room": { "$ref": "#/components/schemas/Hex32" } }, "required": [ "ok", "relay", "room" ] } } } } } } }, "/room": { "get": { "operationId": "room.show", "tags": [ "room" ], "summary": "The current room", "description": "The room a page verifies before it enters (live-rooms §9): its id, `t0_us`, the signed descriptor and composition descriptor, the publishers' grants, how many publish and may, and the record of each mode the room keeps (end-to-end encryption, remote desktop, admission by age, co-performance, watch, shared world), with the service's clock in `now_us`.", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Room" } } } } } } }, "/grant": { "post": { "operationId": "room.grant", "tags": [ "room" ], "summary": "A grant for a key", "description": "A short-lived WRG1 grant for the key, which a relay honours only beside a proof of possession made with that key (live-rooms §10.5). The body is at most 1 KiB. In a room that admits by age the request carries `Authorization: PrivateToken token=\"…\"` (RFC 9577): without one the answer is 401 with a `WWW-Authenticate: PrivateToken` challenge for each key of the room's policy. A call room's grants come with its call (`/call/place`, `/call/answer`): there it is 403.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "principal": { "$ref": "#/components/schemas/Hex32" }, "role": { "type": "string", "enum": [ "publisher", "viewer", "host", "controller", "injector", "captioner" ], "description": "publisher or viewer; in a remote desktop room host, controller or viewer; in a room with ads (`[ads]`), injector, for a key `[ads] injectors` names: a grant permitting `cue` and `meta` and no media (WAI cue-injection §2), whose `join` the clock records once its events track appears; in a room that admits AI captioners (`[ai] captioners`), captioner, with `ai`: an AI participant's grant of the role that holds the ai power (live-rooms §3.3, §4.4), permitting `caption` and `meta` and no media, whose `join` the clock records once its events track appears" }, "ai": { "$ref": "#/components/schemas/Hex32", "description": "the pin of the model that acts for an AI participant (live-rooms §4.4, WAI SPEC.md §3.1): a captioner's, which it requires, or a publisher's in a room with co-performance that takes AI participants. Asked under a role without the ai power, in a room whose descriptor gives some role that power: `403` `ai_role_not_permitted` (§4.3 step 9)" }, "invitation": { "type": "string", "description": "a remote desktop room's host without a configured key: an invitation the room's authority signed" } }, "required": [ "principal", "role" ] } } } }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GrantAnswer" } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "a room that admits by age, and no token: `{\"error\": \"token_required\", \"age\"}` with its challenges", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "a token refused (age-assurance §11's reason), a remote desktop room's host or controller refused (`not_the_host`, `invitation_required`, `invitation_used`, `invitation_expired`, `invitation_bad_signature`, `control_not_allowed`), or a call room; or the role injector for a key `[ads] injectors` does not name (`not_an_injector`); or `ai` under a role without the ai power, in a room whose descriptor gives some role that power (`ai_role_not_permitted`), or a captioner's model `[ai] models` does not list (`ai_model_not_admitted`)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "`full` (the room has its publishers, or its captioners every captioner's place), `busy` (it has taken its grants), or `hosted` (a remote room's host holds its place)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "description": "past the client's rate: retry after retry_after_s", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "`relay`: the clock's session on the relay is not open; or, admitting by age, the window's nonces are at their bound", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/age/receipt": { "get": { "operationId": "admission.show", "tags": [ "age" ], "summary": "The room's admission receipt so far", "description": "In a room that admits by age (`[age]`); 404 in any other.", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "receipt": { "type": "object", "description": "the wai-age-admissions receipt (age-assurance §7), signed by the room's clock, every count under 50 suppressed" }, "policy": { "type": "string" } } } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "description": "past the client's rate: retry after retry_after_s", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/mls": { "get": { "operationId": "mls.tail", "tags": [ "e2ee" ], "summary": "The room's log of MLS messages", "description": "An end-to-end encrypted room's log (confidentiality §5.1, §15.2) from entry `from`: the clock's lifecycle records (`event`) and every GroupInfo, commit and Welcome carried (`mls`), in the order the clock took them; 404 in a room that is not end-to-end encrypted.", "parameters": [ { "name": "room", "in": "query", "required": true, "schema": { "$ref": "#/components/schemas/Hex32" }, "description": "the room's id" }, { "name": "from", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0 }, "description": "the first entry wanted (default 0)" }, { "name": "wait_ms", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0, "maximum": 8000 }, "description": "how long to hold the request while the log has nothing past `from` (default and most 8000)" } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "room": { "$ref": "#/components/schemas/Hex32" }, "entries": { "type": "array", "items": { "type": "object", "properties": { "seq": { "type": "integer" }, "event": { "$ref": "#/components/schemas/Hex" }, "mls": { "$ref": "#/components/schemas/Hex" } } } }, "next": { "type": "integer" }, "end": { "type": [ "integer", "boolean" ] } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "410": { "description": "a room since replaced", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/mls/key-package": { "post": { "operationId": "mls.import", "tags": [ "e2ee" ], "summary": "A KeyPackage for a granted principal's Add", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "room": { "$ref": "#/components/schemas/Hex32" }, "key_package": { "$ref": "#/components/schemas/Hex" } }, "required": [ "room", "key_package" ] } } } }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "principal": { "$ref": "#/components/schemas/Hex32" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "`not granted`: the KeyPackage's principal holds no grant of the room", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "410": { "description": "a room since replaced", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/mls/post": { "post": { "operationId": "mls.update", "tags": [ "e2ee" ], "summary": "A GroupInfo, or a commit and its Welcome", "description": "Checked by the room's clock (confidentiality §5.5), then carried in the log. The body is at most 128 KiB.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "room": { "$ref": "#/components/schemas/Hex32" }, "frames": { "type": "array", "items": { "$ref": "#/components/schemas/Hex" }, "minItems": 1 } }, "required": [ "room", "frames" ] } } } }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "seq": { "type": "integer" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "the clock refuses it (its reason); it is not carried", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "410": { "description": "a room since replaced", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/co/objects": { "get": { "operationId": "performance.list", "tags": [ "co-performance" ], "summary": "The clock's log of objects", "description": "Co-performance §9.1, from entry `from`, at most 5000 an answer; 404 in a room without co-performance.", "parameters": [ { "name": "from", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0 }, "description": "the first entry wanted (default 0)" } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "room": { "$ref": "#/components/schemas/Hex32" }, "objects": { "type": "array", "items": { "type": "object" } }, "next": { "type": "integer" }, "end": { "type": "boolean" }, "unlogged": { "type": "integer" } } } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/co/state": { "get": { "operationId": "performance.show", "tags": [ "co-performance" ], "summary": "The room's co-performance as the clock holds it", "description": "Its windows, the marks it signed and those it owes, its log's length and its own records, at room-clock `now`.", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/co/schedule": { "post": { "operationId": "schedule.create", "tags": [ "co-performance" ], "summary": "A turn schedule the clock signs", "description": "Co-performance §6.1, for a grant with the direct power; `from_us` at least `lead_ms` ahead. Authorised by a grant of the room (`Authorization: Bearer` and the grant's record in base64url) and its proof of possession (`WAI-Room-Proof`) over a challenge the service issued in `WAI-Room-Challenge`, on a 401 and on every answer it accepts (live-rooms §10.5.3); the proof's context is the method and the path the client addressed. The body is at most 4 KiB.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "mode": { "type": "string", "enum": [ "mark", "refuse" ] }, "from_us": { "type": "integer" }, "beat_us": { "type": "integer" }, "beats_per_bar": { "type": "integer" }, "segments": { "type": "array", "items": { "type": "object", "properties": { "start_us": { "type": "integer" }, "length": { "type": "integer" }, "turns": { "type": "integer" }, "pairs": { "type": "boolean" }, "holders": { "type": "array", "items": { "$ref": "#/components/schemas/Hex32" } } } } } }, "required": [ "mode", "segments" ] } } } }, "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "event": { "$ref": "#/components/schemas/Hex32" }, "t": { "type": "integer" }, "statement": { "$ref": "#/components/schemas/Hex" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "the grant's powers do not allow it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the clock could not sign it now", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/co/handover": { "post": { "operationId": "handover.create", "tags": [ "co-performance" ], "summary": "A handover the clock signs", "description": "Co-performance §6.3, for the holder of the turn at `at_us` (now and `lead_ms` when left out) or a grant with the direct power. Authorised by a grant of the room (`Authorization: Bearer` and the grant's record in base64url) and its proof of possession (`WAI-Room-Proof`) over a challenge the service issued in `WAI-Room-Challenge`, on a 401 and on every answer it accepts (live-rooms §10.5.3); the proof's context is the method and the path the client addressed. The body is at most 4 KiB.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "to": { "$ref": "#/components/schemas/Hex32" }, "at_us": { "type": "integer" } }, "required": [ "to" ] } } } }, "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "event": { "$ref": "#/components/schemas/Hex32" }, "t": { "type": "integer" }, "statement": { "$ref": "#/components/schemas/Hex" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "the grant's powers do not allow it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the clock could not sign it now", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/co/window": { "post": { "operationId": "window.create", "tags": [ "co-performance" ], "summary": "A selection window the clock signs", "description": "Co-performance §7.2, for a grant with the direct power; a draw's seed is drawn by the clock and kept until its selection. Authorised by a grant of the room (`Authorization: Bearer` and the grant's record in base64url) and its proof of possession (`WAI-Room-Proof`) over a challenge the service issued in `WAI-Room-Challenge`, on a 401 and on every answer it accepts (live-rooms §10.5.3); the proof's context is the method and the path the client addressed. The body is at most 4 KiB.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "rule": { "type": "string", "enum": [ "plurality", "reactions", "draw" ] }, "types": { "type": "array", "items": { "type": "string", "enum": [ "prompt", "sketch", "vote", "movement" ] } }, "options": { "type": "integer" }, "k": { "type": "integer" }, "close_us": { "type": "integer" }, "seconds": { "type": "integer" }, "question": { "type": "string" } }, "required": [ "rule" ] } } } }, "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "event": { "$ref": "#/components/schemas/Hex32" }, "t": { "type": "integer" }, "statement": { "$ref": "#/components/schemas/Hex" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "the grant's powers do not allow it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the clock could not sign it now", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/co/fidelity": { "post": { "operationId": "fidelity.create", "tags": [ "co-performance" ], "summary": "A fidelity statement now", "description": "Co-performance §5.6, for a grant with the direct power; `{\"ok\": false, \"reason\"}` while no intent is covered yet. Authorised by a grant of the room (`Authorization: Bearer` and the grant's record in base64url) and its proof of possession (`WAI-Room-Proof`) over a challenge the service issued in `WAI-Room-Challenge`, on a 401 and on every answer it accepts (live-rooms §10.5.3); the proof's context is the method and the path the client addressed. The body is at most 4 KiB.", "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "oneOf": [ { "type": "object", "properties": { "ok": { "type": "boolean" }, "event": { "$ref": "#/components/schemas/Hex32" }, "t": { "type": "integer" }, "statement": { "$ref": "#/components/schemas/Hex" } } }, { "type": "object", "properties": { "ok": { "const": false }, "reason": { "type": "string" } } } ] } } } }, "400": { "description": "a body that is not JSON", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "the grant's powers do not allow it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "the service could not read its own record", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the clock could not sign it now", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/offer": { "post": { "operationId": "offer.create", "tags": [ "room" ], "summary": "An offer the clock presents", "description": "WAI shoppable-events, for a grant with the direct power: the clock signs the item's signed description as a `meta` event (`WOI1`) and the offer as a `meta` event (`WOF1`) anchored to the object the programme shows it at, after reading both as a verifier reads them; a request that does not read is refused with the extension's reason and nothing is signed. The service sells nothing: an intent goes from a viewer to the merchant's own endpoint. Authorised by a grant of the room (`Authorization: Bearer` and the grant's record in base64url) and its proof of possession (`WAI-Room-Proof`) over a challenge the service issued in `WAI-Room-Challenge`, on a 401 and on every answer it accepts (live-rooms §10.5.3); the proof's context is the method and the path the client addressed. The body is at most 8 KiB.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "offer": { "type": "object", "description": "the offer's document (shoppable-events)" }, "item": { "type": "object", "description": "the item's description, signed by its merchant" }, "anchor": { "type": "object", "properties": { "track": { "$ref": "#/components/schemas/Hex32" }, "group": { "type": "integer" }, "object": { "type": "integer" } }, "required": [ "track", "group", "object" ] } }, "required": [ "offer", "item", "anchor" ] } } } }, "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "offer": { "$ref": "#/components/schemas/Hex32" }, "t": { "type": "integer" }, "record": { "type": "string", "description": "the offer's event, base64url" }, "item_record": { "type": "string", "description": "the description's event, base64url" } } } } } }, "400": { "description": "a body that is not JSON, or an offer that does not read: `reason` is the extension's", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "the grant's powers do not allow it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the clock could not sign it now", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/watch/time": { "get": { "operationId": "time.show", "tags": [ "watch" ], "summary": "The service's clock", "description": "For a page's estimate of the room clock (co-watch).", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "now_us": { "type": "integer" }, "t0_us": { "type": "integer" } } } } } } } } }, "/watch/source": { "get": { "operationId": "source.fetch", "tags": [ "watch" ], "summary": "The watch room's source, whole", "description": "The source's bytes, of its media type; a page checks them by their SHA-256, the content hash its policy names (co-watch §3.5). 404 in a room that watches nothing.", "responses": { "200": { "description": "the source's bytes", "content": { "*/*": { "schema": { "type": "string", "format": "binary" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/watch/challenge": { "get": { "operationId": "watch.setup", "tags": [ "watch" ], "summary": "A challenge for a join's proof", "description": "Single use, good for a minute (live-rooms §10.5.3).", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "challenge": { "$ref": "#/components/schemas/Hex32" } } } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/watch": { "get": { "operationId": "watch.tail", "tags": [ "watch" ], "summary": "The watch room's log", "parameters": [ { "name": "room", "in": "query", "required": true, "schema": { "$ref": "#/components/schemas/Hex32" }, "description": "the room's id" }, { "name": "from", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0 }, "description": "the first entry wanted (default 0)" }, { "name": "wait_ms", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0, "maximum": 8000 }, "description": "how long to hold the request while the log has nothing past `from` (default and most 8000)" } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "room": { "$ref": "#/components/schemas/Hex32" }, "entries": { "type": "array", "items": { "type": "object", "properties": { "seq": { "type": "integer" }, "event": { "$ref": "#/components/schemas/Hex" }, "mls": { "$ref": "#/components/schemas/Hex" } } } }, "next": { "type": "integer" }, "end": { "type": [ "integer", "boolean" ] } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "410": { "description": "a room since replaced", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/watch/room": { "get": { "operationId": "watch.show", "tags": [ "watch" ], "summary": "The room as a co-watch verifier is given it", "description": "Co-watch §11.1, with the latest skew statements read.", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/watch/join": { "post": { "operationId": "watch.join", "tags": [ "watch" ], "summary": "A viewer enters by a proof of its key", "description": "The proof over a challenge from `GET /watch/challenge` for `POST ///watch/join`; the clock signs the viewer's `join`. The body is at most 4 KiB.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "room": { "$ref": "#/components/schemas/Hex32" }, "grant": { "$ref": "#/components/schemas/Hex" }, "proof": { "$ref": "#/components/schemas/Hex" } }, "required": [ "room", "grant", "proof" ] } } } }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "t": { "type": "integer" }, "again": { "type": "boolean" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "a proof that fails (its reason)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "410": { "description": "a room since replaced", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the clock could not sign the join now", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/watch/post": { "post": { "operationId": "watch.update", "tags": [ "watch" ], "summary": "A viewer's events, checked and carried in order", "description": "Each event checked as co-watch §10 has it. The body is at most 16 KiB.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "room": { "$ref": "#/components/schemas/Hex32" }, "events": { "type": "array", "items": { "$ref": "#/components/schemas/Hex" }, "minItems": 1, "maxItems": 8 } }, "required": [ "room", "events" ] } } } }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "carried": { "type": "integer" }, "log": { "type": "integer" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "the first event refused (its reason, and `carried`: how many before it were carried)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "410": { "description": "a room since replaced", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/remote/control": { "post": { "operationId": "control.grant", "tags": [ "remote" ], "summary": "The host hands control over", "description": "A grant exchange (remote-desktop §7.1), the host's grant and proof presented as a co-performance request presents them; it takes effect at the first tick at least `lead_ms` ahead.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "to": { "$ref": "#/components/schemas/Hex32" }, "control": { "type": "integer", "minimum": 0, "maximum": 15, "description": "classes: 1 pointer, 2 keys, 4 touch, 8 clipboard" } }, "required": [ "to", "control" ] } } } }, "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "grant": { "$ref": "#/components/schemas/Hex" }, "digest": { "$ref": "#/components/schemas/Hex32" }, "effective": { "type": "integer" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "a grant that is not the room's host's", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "the exchange is refused (its reason, `not_present` among them)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "410": { "description": "a room since replaced", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the exchange could not be signed at its tick", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/remote/room": { "get": { "operationId": "remote.show", "tags": [ "remote" ], "summary": "The session as the clock holds it", "description": "What a verifier is given (remote-desktop §11.1). To a grant this room issued and its proof, presented as a co-performance request presents them; bounded per client.", "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "room": { "$ref": "#/components/schemas/Hex32" }, "descriptor": { "$ref": "#/components/schemas/Hex" }, "grants": { "type": "array", "items": { "$ref": "#/components/schemas/Hex" } }, "key_log": { "type": "null" }, "chains": { "type": "array", "items": { "type": "array", "items": { "$ref": "#/components/schemas/Hex" } } } } } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "a grant of another room", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "410": { "description": "a room since replaced", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "description": "past the client's rate: retry after retry_after_s", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/remote/state": { "get": { "operationId": "remote.verify", "tags": [ "remote" ], "summary": "That session verified", "description": "Remote-desktop §11.3: the outcome, the floor's changes and the floor statements refused, each input's fate, the latencies and each host's totals, with the clock's own floor and the principals it cut. To a grant this room issued and its proof, presented as a co-performance request presents them; bounded per client.", "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "a grant of another room", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "410": { "description": "a room since replaced", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "description": "past the client's rate: retry after retry_after_s", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/msg": { "get": { "operationId": "conversation.show", "tags": [ "messaging" ], "summary": "The conversation", "description": "Its descriptor, records and members (live-messaging); 404 when the service keeps no conversation.", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/msg/challenge": { "get": { "operationId": "conversation.setup", "tags": [ "messaging" ], "summary": "A challenge for a proof", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "challenge": { "$ref": "#/components/schemas/Hex32" } } } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/msg/join": { "post": { "operationId": "conversation.join", "tags": [ "messaging" ], "summary": "A key that proves itself is admitted", "description": "A proof of possession naming no grant (live-rooms §10.5.3); the conversation's first member owns it. The clock signs its `join` (live-messaging §3.4).", "security": [ { "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "grant": { "$ref": "#/components/schemas/Hex" }, "digest": { "$ref": "#/components/schemas/Hex32" }, "exp": { "type": "integer" }, "role": { "type": "string", "enum": [ "owner", "member" ] }, "conversation": { "type": "object" } } } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "`not_listed` or `removed`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "`full`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "description": "past the client's rate: retry after retry_after_s", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "the service could not read its own record", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the history store could not take it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/msg/log": { "get": { "operationId": "conversation.tail", "tags": [ "messaging" ], "summary": "The log as it is owed to the caller", "description": "Live-messaging §9.3, from position `from`, at most 256 entries, held up to 8 s while there is nothing new.", "parameters": [ { "name": "from", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0 }, "description": "the first entry wanted (default 0)" }, { "name": "wait_ms", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0, "maximum": 8000 }, "description": "how long to hold the request while the log has nothing past `from` (default and most 8000)" } ], "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "room": { "$ref": "#/components/schemas/Hex32" }, "entries": { "type": "array", "items": { "type": "object" } }, "withheld": { "type": "array", "items": { "type": "integer" } }, "next": { "type": "integer" }, "end": { "type": "integer" } } } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "`not_a_member`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/msg/key-package": { "post": { "operationId": "conversation.import", "tags": [ "messaging" ], "summary": "A member's KeyPackage", "description": "Kept for the member's Add. A member's request: its grant (`Authorization: Bearer`, the record in base64url) and its proof (`WAI-Room-Proof`) over a challenge the service issued; every answer carries a fresh one in `WAI-Room-Challenge`. The body is at most 128 KiB.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "key_package": { "$ref": "#/components/schemas/Hex" } }, "required": [ "key_package" ] } } } }, "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "seq": { "type": "integer" }, "end": { "type": "integer" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "`not_a_member`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the history store could not take it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/msg/mls": { "post": { "operationId": "conversation.update", "tags": [ "messaging" ], "summary": "A GroupInfo, or a commit and its Welcome", "description": "Checked by the clock and carried; it then signs an epoch statement (live-messaging §7.3). A member's request: its grant (`Authorization: Bearer`, the record in base64url) and its proof (`WAI-Room-Proof`) over a challenge the service issued; every answer carries a fresh one in `WAI-Room-Challenge`. The body is at most 128 KiB.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "frames": { "type": "array", "items": { "$ref": "#/components/schemas/Hex" }, "minItems": 1 } }, "required": [ "frames" ] } } } }, "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "seq": { "type": "integer" }, "end": { "type": "integer" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "`not_a_member`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "the clock refuses it (its reason)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the history store could not take it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/msg/post": { "post": { "operationId": "message.create", "tags": [ "messaging" ], "summary": "A message", "description": "Its sealed object and receipts, taken after the checks of live-messaging §9.2, once on stable storage. A member's request: its grant (`Authorization: Bearer`, the record in base64url) and its proof (`WAI-Room-Proof`) over a challenge the service issued; every answer carries a fresh one in `WAI-Room-Challenge`. The body is at most 128 KiB.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "group": { "type": "integer" }, "properties": { "$ref": "#/components/schemas/Hex" }, "payload": { "$ref": "#/components/schemas/Hex" }, "receipts": { "$ref": "#/components/schemas/Hex" } }, "required": [ "group", "properties", "payload", "receipts" ] } } } }, "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "seq": { "type": "integer" }, "end": { "type": "integer" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "`not_a_member`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "refused by §9.2 (its reason)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the history store could not take it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/msg/ack": { "post": { "operationId": "acknowledgement.create", "tags": [ "messaging" ], "summary": "An acknowledgement", "description": "The caller's own (live-messaging §9.1). A member's request: its grant (`Authorization: Bearer`, the record in base64url) and its proof (`WAI-Room-Proof`) over a challenge the service issued; every answer carries a fresh one in `WAI-Room-Challenge`. The body is at most 128 KiB.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "ack": { "$ref": "#/components/schemas/Hex" } }, "required": [ "ack" ] } } } }, "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "seq": { "type": "integer" }, "end": { "type": "integer" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "`not_a_member`, or another member's acknowledgement", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "refused (its reason)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the history store could not take it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/msg/leave": { "post": { "operationId": "conversation.leave", "tags": [ "messaging" ], "summary": "The caller leaves", "description": "The clock signs its `leave` (reason 1). A member's request: its grant (`Authorization: Bearer`, the record in base64url) and its proof (`WAI-Room-Proof`) over a challenge the service issued; every answer carries a fresh one in `WAI-Room-Challenge`. The body is at most 128 KiB.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": {} } } } }, "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "seq": { "type": "integer" }, "end": { "type": "integer" } } } } } }, "400": { "description": "a body that is not JSON", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "`not_a_member`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "the service could not read its own record", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the history store could not take it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/msg/remove": { "post": { "operationId": "member.delete", "tags": [ "messaging" ], "summary": "The owner removes a member", "description": "The clock signs its `leave` (reason 2). A member's request: its grant (`Authorization: Bearer`, the record in base64url) and its proof (`WAI-Room-Proof`) over a challenge the service issued; every answer carries a fresh one in `WAI-Room-Challenge`. The body is at most 128 KiB.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "principal": { "$ref": "#/components/schemas/Hex32" } }, "required": [ "principal" ] } } } }, "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "seq": { "type": "integer" }, "end": { "type": "integer" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "`not_a_member`, or `power_not_granted`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "`not_present`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "the service could not read its own record", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the history store could not take it", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/call/challenge": { "get": { "operationId": "call.setup", "tags": [ "calls" ], "summary": "A challenge for a proof", "description": "In a call room (`[call]`); 404 in any other.", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "challenge": { "$ref": "#/components/schemas/Hex32" } } } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/call/place": { "post": { "operationId": "call.create", "tags": [ "calls" ], "summary": "Place a call", "description": "A key proves itself (a proof naming no grant) and names its invitees; the authority signs the call descriptor (WCT1) for the room, and the caller's publisher grant (live-messaging §10). In a call room (`[call]`); 404 in any other.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "invitees": { "type": "array", "items": { "$ref": "#/components/schemas/Hex32" }, "minItems": 1 } }, "required": [ "invitees" ] } } } }, "security": [ { "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object" } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "`busy` (a call is in progress) or `full`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/call/ring": { "post": { "operationId": "call.invite", "tags": [ "calls" ], "summary": "Ring invitees", "description": "The caller's invitations (WCI1), with its grant and proof; each is checked (§10.2) and held in its callee's inbox. In a call room (`[call]`); 404 in any other.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "invitations": { "type": "array", "items": { "$ref": "#/components/schemas/Hex" }, "minItems": 1 } }, "required": [ "invitations" ] } } } }, "security": [ { "roomGrant": [], "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "rung": { "type": "integer" } } } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "not the caller", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "an invitation refused (its reason and `at`)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "410": { "description": "no call in this room", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/inbox": { "get": { "operationId": "inbox.show", "tags": [ "calls" ], "summary": "The caller's inbox", "description": "On a proof of the key (§10.4). In a call room (`[call]`); 404 in any other.", "security": [ { "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "principal": { "$ref": "#/components/schemas/Hex32" }, "invitations": { "type": "array", "items": { "type": "object" } }, "now_us": { "type": "integer" }, "t0_us": { "type": "integer" } } } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/call/answer": { "post": { "operationId": "answer.create", "tags": [ "calls" ], "summary": "Accept, decline or cancel", "description": "A callee's accept or decline, or the caller's cancel (WCA1, §10.3); on an accept the callee's publisher grant. In a call room (`[call]`); 404 in any other.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "answer": { "$ref": "#/components/schemas/Hex" } }, "required": [ "answer" ] } } } }, "security": [ { "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object" } } } }, "400": { "description": "the request does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "an answer of another key, or a missed call (the clock's)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service keeps no such room, or nothing at that path in this service's mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "the answer refused (its reason)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "410": { "description": "no call in this room", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/call": { "get": { "operationId": "call.show", "tags": [ "calls" ], "summary": "The current call", "description": "To a party to it, on a proof of its key: the call's records, which a party verifies offline (§10). In a call room (`[call]`); 404 in any other.", "security": [ { "roomProof": [] } ], "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object" } } } }, "401": { "description": "no proof of possession that passes; a fresh challenge is in WAI-Room-Challenge", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "403": { "description": "not a party to the call", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "no call, or not a call room", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/ads": { "get": { "operationId": "ad.show", "tags": [ "ads" ], "summary": "The room's ads", "description": "In a room whose service carries ads (`[ads]`, live-ads §14.8): the ad policy and the traffic policy (hexadecimal), the traffic policy's digest, the decider's grant, the stitched namespace `[room, stitcher, video, stitched, D]`, the programme, the regions the rights now list, the breaks of the schedule (each its cue's digest, `t`, `duration_ms` and `avail_ms`), the records the stitcher refused with why, and how many fills, receipts, claims and bindings it holds.", "responses": { "200": { "description": "the room's ads", "content": { "application/json": { "schema": { "type": "object" } } } }, "404": { "description": "this service carries no ads (`[ads]`), or no room carries them yet", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/ads/records": { "get": { "operationId": "ad.export", "tags": [ "ads" ], "summary": "The room's ad records, for a verifier", "description": "Everything a verifier of the room's ads is given but the session origins, which each edge keeps (live-ads §14.8): the descriptor, grants and chains, both policies and the lists, the receipts with their pods, the creatives, each session's creative records and composed listing, the claims and bindings, and each audience decision with its document. `?seal=all` seals every fill first. The operator's, as `GET /metrics` is: a request that came through a front is answered 404, since the claims and bindings link the sessions of one device key.", "parameters": [ { "name": "seal", "in": "query", "required": false, "description": "`all`: seal every fill before answering", "schema": { "type": "string", "enum": [ "all" ] } } ], "responses": { "200": { "description": "the room's records", "content": { "application/json": { "schema": { "type": "object" } } } }, "404": { "description": "this service carries no ads (`[ads]`), or no room carries them yet; or the request came through a front", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/ads/session": { "post": { "operationId": "viewer.join", "tags": [ "ads" ], "summary": "A session's key and the region it aggregated", "description": "A player tells the room of its playback session: its session key (ad-presentation §3) and the region it aggregated (audience-decision §4.3), or `null` (unresolved: every blackout applies). Nothing finer than the region is taken.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "player": { "$ref": "#/components/schemas/Hex32" }, "region": { "type": [ "string", "null" ], "description": "a region code, or null" } }, "required": [ "player" ] } } } }, "responses": { "200": { "description": "the session the key names", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "session": { "type": "string", "pattern": "^[0-9a-f]{32}$" } } } } } }, "400": { "description": "the request does not read: a session key, and a region code or null", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service carries no ads (`[ads]`), or no room carries them yet", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes (64 KiB)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the room holds as many as it keeps", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/ads/binding": { "post": { "operationId": "binding.create", "tags": [ "ads" ], "summary": "A session's device binding", "description": "A device binding (`WDB1`, ad-presentation §14.4), signed by the device key and by the session key, held to the room's traffic policy and both signatures, and kept as it came for the verifier.", "requestBody": { "required": true, "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary", "description": "the binding's bytes" } }, "application/json": { "schema": { "type": "object", "properties": { "record": { "$ref": "#/components/schemas/Hex", "description": "the binding's bytes, in hexadecimal" } }, "required": [ "record" ] } } } }, "responses": { "200": { "description": "kept: its digest", "content": { "application/json": { "schema": { "type": "object" } } } }, "400": { "description": "the binding does not read, is under another traffic policy (`binding_unbound`) or a signature fails (`bad_key`, `bad_signature`)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service carries no ads (`[ads]`), or no room carries them yet", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes (64 KiB)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the room holds as many as it keeps", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/ads/claim": { "post": { "operationId": "claim.create", "tags": [ "ads" ], "summary": "A presentation claim of an impression", "description": "A presentation claim (`WPC1`, ad-presentation §5), signed by the session key, held to its signature and kept as it came, a second claim of one impression among them: what it counts for is the verifier's to decide (§9.2, §14.6).", "requestBody": { "required": true, "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary", "description": "the claim's bytes" } }, "application/json": { "schema": { "type": "object", "properties": { "record": { "$ref": "#/components/schemas/Hex", "description": "the claim's bytes, in hexadecimal" } }, "required": [ "record" ] } } } }, "responses": { "200": { "description": "kept: its digest", "content": { "application/json": { "schema": { "type": "object" } } } }, "400": { "description": "the claim does not read (`claim_malformed`), is of another room (`wrong_room`) or its signature fails", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service carries no ads (`[ads]`), or no room carries them yet", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "the body is larger than the request takes (64 KiB)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "503": { "description": "the room holds as many as it keeps", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/ads/list/{file}": { "get": { "operationId": "asset.list", "tags": [ "ads" ], "summary": "A break's asset list for a session", "description": "The asset list of a break (live-ads §7.2 for `.json`, §7.3 for `.mpd`), filled for the session key the request presents as `player`, `_HLS_start_offset` seconds into the break (§7.4). An edge in front of the service (the HTTP egress gateway) writes the session's origin and passes the request on.", "parameters": [ { "name": "file", "in": "path", "required": true, "description": "`wai-ad-.json` or `.mpd`", "schema": { "type": "string", "pattern": "^wai-ad-[0-9a-f]{64}\\.(json|mpd)$" } }, { "name": "player", "in": "query", "required": true, "description": "the session key", "schema": { "$ref": "#/components/schemas/Hex32" } }, { "name": "_HLS_start_offset", "in": "query", "required": false, "description": "seconds into the break the session joins it", "schema": { "type": "number", "minimum": 0 } } ], "responses": { "200": { "description": "the asset list", "content": { "application/json": { "schema": { "type": "object" } }, "application/dash+xml": { "schema": { "type": "string" } } } }, "204": { "description": "a list MPD for a break filled by nothing" }, "400": { "description": "no session key presented", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "this service carries no ads (`[ads]`), or no room carries them yet; or no such break (`creative_break_unknown`), or the break has ended for this session", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/ads/creative/{hash}.m3u8": { "get": { "operationId": "creative.show", "tags": [ "ads" ], "summary": "A creative as an HLS media playlist", "description": "A fixture creative by its creative digest (ad-presentation §4), one segment an Object, so the bytes a player presents are the Objects the digest names.", "parameters": [ { "name": "hash", "in": "path", "required": true, "description": "the creative digest", "schema": { "type": "string", "pattern": "^[0-9a-f]{64}$" } } ], "responses": { "200": { "description": "the playlist", "content": { "application/vnd.apple.mpegurl": { "schema": { "type": "string" } } } }, "404": { "description": "this service carries no ads (`[ads]`), or no room carries them yet; or no such creative", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/ads/creative/{hash}/{file}": { "get": { "operationId": "creative.fetch", "tags": [ "ads" ], "summary": "A creative's header or one of its Objects", "description": "`init.mp4`, the creative's header, or `.m4s`, its Object `n` in presentation order.", "parameters": [ { "name": "hash", "in": "path", "required": true, "description": "the creative digest", "schema": { "type": "string", "pattern": "^[0-9a-f]{64}$" } }, { "name": "file", "in": "path", "required": true, "description": "`init.mp4` or `.m4s`", "schema": { "type": "string", "pattern": "^(init\\.mp4|(0|[1-9][0-9]*)\\.m4s)$" } } ], "responses": { "200": { "description": "the bytes", "content": { "video/mp4": { "schema": { "type": "string", "format": "binary" } }, "video/iso.segment": { "schema": { "type": "string", "format": "binary" } } } }, "404": { "description": "this service carries no ads (`[ads]`), or no room carries them yet; or no such creative or Object", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "securitySchemes": { "roomGrant": { "type": "http", "scheme": "bearer", "description": "a grant of the room: its WRG1 record in base64url" }, "roomProof": { "type": "apiKey", "in": "header", "name": "WAI-Room-Proof", "description": "a proof of possession over the service's challenge (live-rooms §10.5.3), in base64url" } }, "schemas": { "Hex": { "type": "string", "pattern": "^([0-9a-fA-F]{2})*$", "description": "bytes in hexadecimal" }, "Hex32": { "type": "string", "pattern": "^[0-9a-f]{64}$", "description": "32 bytes, 64 lowercase hexadecimal digits" }, "Error": { "type": "object", "properties": { "error": { "type": "string", "description": "a reason code or a short statement of what is wrong" }, "reason": { "type": "string" }, "retry_after_s": { "type": "number" } }, "required": [ "error" ] }, "Room": { "type": "object", "properties": { "room": { "$ref": "#/components/schemas/Hex32" }, "generation": { "type": "integer" }, "t0_us": { "type": "integer" }, "now_us": { "type": "integer" }, "descriptor": { "$ref": "#/components/schemas/Hex", "description": "the signed WRD1 descriptor" }, "compose": { "$ref": "#/components/schemas/Hex", "description": "the signed WCD1 composition descriptor" }, "grants": { "type": "array", "items": { "$ref": "#/components/schemas/Hex" }, "description": "the publishers' WRG1 grants" }, "present": { "type": "integer" }, "publishers": { "type": "integer" }, "max_publishers": { "type": "integer" }, "e2ee": { "type": [ "object", "null" ] }, "remote": { "type": [ "object", "null" ] }, "age": { "type": [ "object", "null" ] }, "co": { "type": [ "object", "null" ] }, "watch": { "type": [ "object", "null" ] }, "world": { "type": [ "object", "null" ] } }, "required": [ "room", "t0_us", "descriptor", "grants" ] }, "GrantAnswer": { "type": "object", "properties": { "grant": { "$ref": "#/components/schemas/Hex", "description": "the WRG1 record" }, "digest": { "$ref": "#/components/schemas/Hex32" }, "exp": { "type": "integer" }, "role": { "type": "string" }, "room": { "$ref": "#/components/schemas/Room" } }, "required": [ "grant", "digest", "exp", "role", "room" ] } } } }