Open Morse

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.

POST/m/{code}/desk-view

POST/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.

GET/m/{code}/events

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

POST/m/{code}/guest

An 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" }'
200
{
  "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.

POST/m/{code}/hand

POST/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.

POST/m/{code}/launch

Not 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" }'
200
{
  "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).

GET/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"
200
{
  "id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
  "name": "Priya Shah",
  "title": "Pricing review"
}

A member's picture, for a guest in the same meeting.

GET/m/{code}/people/{user_id}/avatar

GET/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"

The guest half of hosting.ask_to_share. Same event on the same topic, so one set of host toasts answers both.

POST/m/{code}/share-request

Gated on being *in the room*, not merely holding a valid token: a guest still in the lobby has nothing to present onto, and without the check a stale tab on the doorstep could raise toasts at the host indefinitely.

POST/m/{code}/share-request

curl -X POST "$MORSE/m/4KJ9P2/share-request" \
  -H "Authorization: Bearer $MORSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "guest_id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60", "launch_token": "mp_YOUR_MORSE_TOKEN" }'

A guest's read of the saved board, for a late joiner.

GET/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"
200
{
  "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.

POST/m/{code}/wide-camera

POST/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.

POST/m/{code}/withdraw

Idempotent: 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" }'