Reference
Booking
18 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.
Who this page belongs to, and what can be booked with them.
/book/{handle}GET/book/{handle}
curl "$MORSE/book/…" \
-H "Authorization: Bearer $MORSE_TOKEN"{
"available": true,
"bio": "…",
"company": "…",
"name": "Priya Shah",
"note_required": true,
"title": "Pricing review",
"types": [
{
"description": "…",
"duration_minutes": 30,
"slug": "…",
"title": "Pricing review"
}
]
}The host's photo, honouring their own choice of it — uploaded, Google's, a preset, or the placeholder (D19).
/book/{handle}/avatarThis is the one place a face becomes public without a shared meeting, which narrows plan 009 5.4 for page owners only: having a page is the consent.
GET/book/{handle}/avatar
curl "$MORSE/book/…/avatar" \
-H "Authorization: Bearer $MORSE_TOKEN"Undo a booking, from the confirmation page, within ten minutes (D13).
/book/{handle}/undo410 once it cannot be undone, whatever the reason — expired, another page's token, or a meeting that has started. A booker who waited too long gets one answer and one instruction: reply to your email.
POST/book/{handle}/undo
curl -X POST "$MORSE/book/…/undo" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "x-timezone: Asia/Kolkata" \
-H "Content-Type: application/json" \
-d '{ "token": "mp_YOUR_MORSE_TOKEN" }'Book a slot. **Unauthenticated**, by design: this is the whole feature.
/book/{handle}/{slug}What stops it being an open relay is not authentication but D12 and D18 — the
way in travels only to the address itself, and a page can be made to send at
most PER_PAGE_DAILY emails a day. That bounds the damage rather than
preventing it; a challenge on the form is the named next step if junk arrives.
Send X-Timezone so the invitation and the .ics render in the booker's own
zone, and so the host sees their time beside their own.
POST/book/{handle}/{slug}
curl -X POST "$MORSE/book/…/…" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "x-timezone: Asia/Kolkata" \
-H "Content-Type: application/json" \
-d '{ "email": "priya@neuralarc.ai", "name": "Priya Shah", "start": "2026-10-02T10:30:00+05:30" }'{
"email": "priya@neuralarc.ai",
"ends_at": "2026-10-02T10:30:00+05:30",
"host_name": "Priya Shah",
"starts_at": "2026-10-02T10:30:00+05:30",
"type_title": "…",
"undo_expires_at": "2026-10-02T10:30:00+05:30",
"undo_token": "mp_YOUR_MORSE_TOKEN"
}Bookable instants between two times.
/book/{handle}/{slug}/slotsThe window is capped so one call cannot ask us to read the whole horizon, and clipped to the horizon so a caller cannot reach past it. Busy time is cached for a minute (D18): a visitor stepping through months should not cost a Google call each time. A booking never reads that cache.
GET/book/{handle}/{slug}/slots
curl "$MORSE/book/…/…/slots?start=2026-10-02T10%3A30%3A00%2B05%3A30&end=2026-10-02T10%3A30%3A00%2B05%3A30" \
-H "Authorization: Bearer $MORSE_TOKEN"{
"available": true,
"holidays": [
{
"date": "2026-10-02",
"name": "Priya Shah",
"open": true
}
],
"slots": [
"2026-10-02T10:30:00+05:30"
]
}Whether a handle is yours to take.
/booking/handleSo a collision is answered where the handle is typed, rather than as a 409
after everything else on the page has been filled in. The PUT still checks:
this is a courtesy, not the guard, and two people can always race it.
GET/booking/handle
curl "$MORSE/booking/handle?handle=%E2%80%A6" \
-H "Authorization: Bearer $MORSE_TOKEN"{
"available": true,
"handle": "…",
"reason": "…"
}Countries with a holiday calendar, as alpha-2 codes.
/booking/holidays/countriesThe client names them in the viewer's language.
GET/booking/holidays/countries
curl "$MORSE/booking/holidays/countries" \
-H "Authorization: Bearer $MORSE_TOKEN"[
"…"
]Whose holidays your page knows about, the whole list at once.
/booking/holidays/countriesIts own route rather than a field on PageIn, which is a full replace: a
script restating the page without it would quietly drop every holiday.
PUT/booking/holidays/countries
curl -X PUT "$MORSE/booking/holidays/countries" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "countries": [ 3 ] }'{
"bookings": 1,
"enabled": true,
"handle": "…",
"holiday_countries": [
3
],
"holidays": [
{
"chosen": true,
"date": "2026-10-02",
"names": [
{
"country": 3,
"name": "Priya Shah"
}
],
"opening": null,
"public": true
}
],
"hours": [
{
"end_minute": 1,
"start_minute": 1,
"weekday": 1
}
],
"note_required": true,
"remaining": [
"…"
],
"timezone": "Asia/Kolkata",
"types": [
{
"description": "…",
"duration_minutes": 30,
"enabled": true,
"id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
"slug": "…",
"title": "Pricing review"
}
]
}Close a holiday, or open it in one range, instead of its default.
/booking/holidays/days/{day}Holidays only: any other date is a date override, which the page does not have (D7).
PUT/booking/holidays/days/{day}
curl -X PUT "$MORSE/booking/holidays/days/2026-10-02" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "end_minute": 1, "start_minute": 1 }'{
"bookings": 1,
"enabled": true,
"handle": "…",
"holiday_countries": [
3
],
"holidays": [
{
"chosen": true,
"date": "2026-10-02",
"names": [
{
"country": 3,
"name": "Priya Shah"
}
],
"opening": null,
"public": true
}
],
"hours": [
{
"end_minute": 1,
"start_minute": 1,
"weekday": 1
}
],
"note_required": true,
"remaining": [
"…"
],
"timezone": "Asia/Kolkata",
"types": [
{
"description": "…",
"duration_minutes": 30,
"enabled": true,
"id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
"slug": "…",
"title": "Pricing review"
}
]
}Put a holiday back to its default.
/booking/holidays/days/{day}Not an error if it already is.
DELETE/booking/holidays/days/{day}
curl -X DELETE "$MORSE/booking/holidays/days/2026-10-02" \
-H "Authorization: Bearer $MORSE_TOKEN"{
"bookings": 1,
"enabled": true,
"handle": "…",
"holiday_countries": [
3
],
"holidays": [
{
"chosen": true,
"date": "2026-10-02",
"names": [
{
"country": 3,
"name": "Priya Shah"
}
],
"opening": null,
"public": true
}
],
"hours": [
{
"end_minute": 1,
"start_minute": 1,
"weekday": 1
}
],
"note_required": true,
"remaining": [
"…"
],
"timezone": "Asia/Kolkata",
"types": [
{
"description": "…",
"duration_minutes": 30,
"enabled": true,
"id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
"slug": "…",
"title": "Pricing review"
}
]
}Fetch your own booking link and say what happened.
/booking/linkSettings used to assert that a saved handle was a working link. It is not:
#147 shipped a card showing neuralarc.onmorse.com while the hostname did
not resolve, and its owner learned that from a browser error. This is the
card checking instead of claiming.
Nothing the caller sends reaches the request — the host is their own handle
under this deployment's own domain (D19), so this fetches one of exactly as
many addresses as there are booking pages.
GET/booking/link
curl "$MORSE/booking/link" \
-H "Authorization: Bearer $MORSE_TOKEN"{
"state": "…",
"url": "https://onmorse.com/priya"
}Your booking page: its handle, timezone, hours, types, and whether it is taking bookings. 404 before you have set one up.
/booking/pageGET/booking/page
curl "$MORSE/booking/page" \
-H "Authorization: Bearer $MORSE_TOKEN"{
"bookings": 1,
"enabled": true,
"handle": "…",
"holiday_countries": [
3
],
"holidays": [
{
"chosen": true,
"date": "2026-10-02",
"names": [
{
"country": 3,
"name": "Priya Shah"
}
],
"opening": null,
"public": true
}
],
"hours": [
{
"end_minute": 1,
"start_minute": 1,
"weekday": 1
}
],
"note_required": true,
"remaining": [
"…"
],
"timezone": "Asia/Kolkata",
"types": [
{
"description": "…",
"duration_minutes": 30,
"enabled": true,
"id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
"slug": "…",
"title": "Pricing review"
}
]
}Create or replace your page, hours and all.
/booking/pageA PUT, not a PATCH: the week is saved whole, because a range removed from a day is only expressible by sending the day as it should now be. Changing the handle keeps the old one: it stays this person's and redirects to whatever they are called now (0053). The reasoning it replaces — "a handle that used to be someone else's is not a link Morse should keep honouring" — was right about the page and wrong about the link, which had already been sent and went to whoever took the name next.
PUT/booking/page
curl -X PUT "$MORSE/booking/page" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "enabled": true, "handle": "…", "timezone": "Asia/Kolkata" }'{
"bookings": 1,
"enabled": true,
"handle": "…",
"holiday_countries": [
3
],
"holidays": [
{
"chosen": true,
"date": "2026-10-02",
"names": [
{
"country": 3,
"name": "Priya Shah"
}
],
"opening": null,
"public": true
}
],
"hours": [
{
"end_minute": 1,
"start_minute": 1,
"weekday": 1
}
],
"note_required": true,
"remaining": [
"…"
],
"timezone": "Asia/Kolkata",
"types": [
{
"description": "…",
"duration_minutes": 30,
"enabled": true,
"id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
"slug": "…",
"title": "Pricing review"
}
]
}The meeting types on your page, shortest first.
/booking/typesGET/booking/types
curl "$MORSE/booking/types" \
-H "Authorization: Bearer $MORSE_TOKEN"[
{
"description": "…",
"duration_minutes": 30,
"enabled": true,
"id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
"slug": "…",
"title": "Pricing review"
}
]Add a meeting type — a name, a length, and an optional line about it.
/booking/typesPOST/booking/types
curl -X POST "$MORSE/booking/types" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "duration_minutes": 30, "slug": "…", "title": "Pricing review" }'{
"description": "…",
"duration_minutes": 30,
"enabled": true,
"id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
"slug": "…",
"title": "Pricing review"
}Replace a meeting type.
/booking/types/{type_id}Meetings already booked through it are untouched: a booking is an ordinary meeting once it exists, and owes nothing to the type that made it (D9).
PATCH/booking/types/{type_id}
curl -X PATCH "$MORSE/booking/types/3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "duration_minutes": 30, "slug": "…", "title": "Pricing review" }'{
"description": "…",
"duration_minutes": 30,
"enabled": true,
"id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
"slug": "…",
"title": "Pricing review"
}Remove a meeting type.
/booking/types/{type_id}Deleting twice is not an error.
DELETE/booking/types/{type_id}
curl -X DELETE "$MORSE/booking/types/3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60" \
-H "Authorization: Bearer $MORSE_TOKEN"Take a type off your page, or put it back.
/booking/types/{type_id}/enabledIts own route rather than a field on TypeIn, which is a full replace: a
default there would silently switch a type back on whenever someone edited its
title, and a required one would make every edit form carry a flag it has no
business knowing about.
Meetings already booked through it are untouched — a booking is an ordinary
meeting once it exists.
PATCH/booking/types/{type_id}/enabled
curl -X PATCH "$MORSE/booking/types/3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60/enabled" \
-H "Authorization: Bearer $MORSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "enabled": true }'{
"description": "…",
"duration_minutes": 30,
"enabled": true,
"id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
"slug": "…",
"title": "Pricing review"
}