Open Morse

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.

GET/book/{handle}

GET/book/{handle}

curl "$MORSE/book/…" \
  -H "Authorization: Bearer $MORSE_TOKEN"
200
{
  "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).

GET/book/{handle}/avatar

This 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).

POST/book/{handle}/undo

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

POST/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" }'
201
{
  "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.

GET/book/{handle}/{slug}/slots

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

GET/booking/handle

So 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"
200
{
  "available": true,
  "handle": "…",
  "reason": "…"
}

Countries with a holiday calendar, as alpha-2 codes.

GET/booking/holidays/countries

The client names them in the viewer's language.

GET/booking/holidays/countries

curl "$MORSE/booking/holidays/countries" \
  -H "Authorization: Bearer $MORSE_TOKEN"
200
[
  "…"
]

Whose holidays your page knows about, the whole list at once.

PUT/booking/holidays/countries

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

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

DELETE/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"
200
{
  "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"
    }
  ]
}

Your booking page: its handle, timezone, hours, types, and whether it is taking bookings. 404 before you have set one up.

GET/booking/page

GET/booking/page

curl "$MORSE/booking/page" \
  -H "Authorization: Bearer $MORSE_TOKEN"
200
{
  "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.

PUT/booking/page

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

GET/booking/types

GET/booking/types

curl "$MORSE/booking/types" \
  -H "Authorization: Bearer $MORSE_TOKEN"
200
[
  {
    "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.

POST/booking/types

POST/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" }'
201
{
  "description": "…",
  "duration_minutes": 30,
  "enabled": true,
  "id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
  "slug": "…",
  "title": "Pricing review"
}

Replace a meeting type.

PATCH/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" }'
200
{
  "description": "…",
  "duration_minutes": 30,
  "enabled": true,
  "id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
  "slug": "…",
  "title": "Pricing review"
}

Remove a meeting type.

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

PATCH/booking/types/{type_id}/enabled

Its 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 }'
200
{
  "description": "…",
  "duration_minutes": 30,
  "enabled": true,
  "id": "3f9c1a24-5e6f-4b31-9a77-1b2c3d4e5f60",
  "slug": "…",
  "title": "Pricing review"
}