Reference
Guests
11 routes, generated from Morse’s own OpenAPI document. Every path below hangs off the base URL, and every one needs the bearer header.
The examples are built from each route’s schema, so the shapes and types are exactly what the API declares. The values are illustrative, and no one has run them.
A guest's desk view: the same server write as a member's
(hosting.my_desk_view), authenticated as guest_wide_camera is.
/m/{code}/desk-viewPOST/m/{code}/desk-view
curl -X POST "$MORSE/m/4KJ9P2/desk-view" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "guest_id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60", "launch_token": "mp_YOUR_MORSE_TOKEN", "on": true }'One stream for a guest's whole visit, before and after admission.
/m/{code}/eventsWhile they wait it tells them when the decision is made and who is in the
room. **After admission it keeps running**, carrying room state — which is
why the client must not unmount it on the way in (Phase 3d).
Phase 3c subscribed members to the room topic and deferred this half,
because the only room state then was locked, which decides who must knock
and which guests do regardless — plumbing with no payload. whiteboard_open
is the payload, and a guest who cannot see the board open is a guest staring
at a video grid while everyone else draws.
Verified before the stream opens — a leaked guest_id alone must reveal
nothing. The launch token is what the guest already holds; no guest write
path is introduced here, and none is needed.
**Not gated on the meeting being open.** A lobby reconnecting after the host
ended the meeting has missed ended; refusing with 410 left it on "Lost
the connection." instead. It opens, and lobby_catch_up says so at once.
GET/m/{code}/events
curl "$MORSE/m/4KJ9P2/events?guest_id=3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60&launch_token=mp_YOUR_MORSE_TOKEN" \
-H "Authorization: Bearer $MORSE_TOKEN"Two ways in.
/m/{code}/guestAn invite token means we already know the address and it came
from a trusted internal user, so we ask only for a name. Otherwise the guest
declares both, and the row is marked self_declared — the distinction the
Phase 5 recipient picker depends on.
POST/m/{code}/guest
curl -X POST "$MORSE/m/4KJ9P2/guest" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "Priya Shah" }'{
"email": "priya@neuralarc.ai",
"guest_id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
"launch_token": "mp_YOUR_MORSE_TOKEN",
"name": "Priya Shah"
}A guest raising or lowering their own hand: the same server write as a
member's (hosting.my_hand), authenticated by the launch token and by being
in the room, like the whiteboard read below.
/m/{code}/handPOST/m/{code}/hand
curl -X POST "$MORSE/m/4KJ9P2/hand" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "guest_id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60", "launch_token": "mp_YOUR_MORSE_TOKEN", "raised": true }'For a guest let in from the lobby: the LiveKit token to join with.
/m/{code}/launchNot for members.
POST/m/{code}/launch
curl -X POST "$MORSE/m/4KJ9P2/launch" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "guest_id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60", "launch_token": "mp_YOUR_MORSE_TOKEN" }'{
"anyone_can_share": true,
"display_name": "Priya Shah",
"livekit_url": "https://onmorse.com/priya",
"room_name": "Priya Shah",
"session_token": "mp_YOUR_MORSE_TOKEN",
"status": "…",
"token": "mp_YOUR_MORSE_TOKEN",
"whiteboard_open": true
}Who a member is, for a guest's tiles: the reason profiles exist is that a client sees "Aniket Tapre, CEO" rather than a name (plan 009 D11).
/m/{code}/people/{user_id}GET/m/{code}/people/{user_id}
curl "$MORSE/m/4KJ9P2/people/3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60?guest_id=3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60&launch_token=mp_YOUR_MORSE_TOKEN" \
-H "Authorization: Bearer $MORSE_TOKEN"{
"id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
"name": "Priya Shah",
"title": "Pricing review"
}A member's picture, for a guest in the same meeting.
/m/{code}/people/{user_id}/avatarGET/m/{code}/people/{user_id}/avatar
curl "$MORSE/m/4KJ9P2/people/3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60/avatar?guest_id=3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60&launch_token=mp_YOUR_MORSE_TOKEN" \
-H "Authorization: Bearer $MORSE_TOKEN"A guest's read of the saved board, for a late joiner.
/m/{code}/whiteboard**Read only, and deliberately so.** Guests draw on the whiteboard like
everyone else — that travels over the data channel and never touches the
API — but they never hold the snapshotter role and have no write path here
(ADR-003). This closes the mirror-image gap: without it a guest arriving
mid-meeting would see an empty canvas while everyone else saw a full one,
and would only start seeing strokes drawn after they walked in.
Authenticated by the launch token **and by admission**, which is the part
that matters. The launch token is minted at arrive(), before anyone has
knocked — so gating on the token alone would have let anyone holding the
meeting link read the live board without ever being let in, and would have
kept reading it after being denied. Invariant 11's principle is that nothing
is granted before admission; a read of meeting content is exactly that.
GET/m/{code}/whiteboard
curl "$MORSE/m/4KJ9P2/whiteboard?guest_id=3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60&launch_token=mp_YOUR_MORSE_TOKEN" \
-H "Authorization: Bearer $MORSE_TOKEN"{
"open": true,
"scene": {},
"seq": 1
}A guest's wide camera: the same server write as a member's
(hosting.my_wide_camera), authenticated as guest_hand is.
/m/{code}/wide-cameraPOST/m/{code}/wide-camera
curl -X POST "$MORSE/m/4KJ9P2/wide-camera" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "guest_id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60", "launch_token": "mp_YOUR_MORSE_TOKEN", "on": true }'Leaving the lobby. Without this the host keeps seeing a knock from someone who has closed the tab, and the waiting badge counts a ghost.
/m/{code}/withdrawIdempotent: a knock already admitted or denied is simply not waiting, and that is not a failure worth reporting to someone who is leaving anyway.
POST/m/{code}/withdraw
curl -X POST "$MORSE/m/4KJ9P2/withdraw" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "guest_id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60", "launch_token": "mp_YOUR_MORSE_TOKEN" }'