Help API

/v1/bookings

Read, create, cancel, reschedule, and patch bookings. This is the write-heavy surface, and every write needs an Idempotency-Key, and PATCH needs an If-Match ETag.

A booking's UID is its UUID, and every endpoint that takes :uid rejects non-UUIDs with 404 booking_not_found.


GET /v1/bookings

List bookings in the authenticated account.

MethodGET
URLhttps://api.42min.us/v1/bookings
Scopebookings:read
AuthRequired

Query parameters

ParamTypeNotes
limitintegerDefault 20, max 100.
cursorstringOpaque cursor from a prior meta.next_cursor.
sortstringstart_at_desc (default), start_at_asc, created_at_desc, updated_at_asc, updated_at_desc.
event_type_idUUIDFilter to one event type.
host_user_idUUIDFilter to one host.
attendee_emailstringExact-match filter (case-sensitive).
statusstringComma-separated. Typical values: confirmed, canceled.
start_dateISO 8601Lower bound on start_at (inclusive).
end_dateISO 8601Upper bound on start_at (inclusive).
updated_sinceISO 8601Only bookings whose updated_at is at or after this time. Pair with sort=updated_at_asc for catch-up reconciliation; see below.
include_cancelledbooleanWhen status isn't given, set false to exclude canceled. Defaults to including them.

Response

{
  "data": [
    {
      "uid": "01H…",
      "version": 1,
      "event_type_id": "01HXXX…",
      "event_type_slug": "intro-call",
      "title": "Intro call",
      "status": "confirmed",
      "start_at": "2026-05-20T10:00:00.000Z",
      "end_at": "2026-05-20T10:30:00.000Z",
      "timezone": "Europe/London",
      "host": { "user_id": "01H…", "username": "ada", "email": "ada@example.com" },
      "attendees": [{ "email": "bob@example.com", "name": "Bob Builder", "timezone": "Europe/Berlin" }],
      "guests": [{ "email": "carol@example.com" }],
      "location": { "type": "google_meet", "url": "https://meet.google.com/…", "value": null },
      "metadata": { "crm_id": "C-7" },
      "responses": null,
      "calendar_sync_status": "synced",
      "calendar_event_id": "abc123…",
      "rescheduled_from_uid": null,
      "cancelled_at": null,
      "cancellation_reason": null,
      "no_show_at": null,
      "no_show_reason": null,
      "created_at": "2026-05-14T09:00:00.000Z",
      "updated_at": "2026-05-14T09:00:00.000Z"
    }
  ],
  "meta": { "request_id": "req_…", "next_cursor": null, "has_more": false }
}

Field notes:

  • responses (the booking-form answers) is null on list responses, so fetch the detail to read them.
  • calendar_sync_status is synced / pending / failed / not_applicable (no calendar accounts attached).
  • rescheduled_from_uid references the prior booking when this one is the result of a reschedule.

Catch-up reconciliation

If a webhook was paused (auto-paused after repeated delivery failures, or paused by you) you'll have missed booking.* events. To resync without guessing, sweep by modification time:

GET /v1/bookings?updated_since=<last-seen>&sort=updated_at_asc&limit=100

then follow meta.next_cursor to the end. updated_at_asc keeps the order stable while you page. One caveat: an internal calendar-sync retry also bumps updated_at, so a booking can resurface in this sweep with no user-facing change, so make your reconciliation idempotent and that's harmless.

curl

curl -H "Authorization: Bearer $TOKEN" \
  "https://api.42min.us/v1/bookings?status=confirmed&start_date=2026-05-01T00:00:00Z&limit=100"

# Catch-up after a webhook pause
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.42min.us/v1/bookings?updated_since=2026-05-18T00:00:00Z&sort=updated_at_asc&limit=100"

GET /v1/bookings/:uid

Return one booking with responses populated. Sets ETag for the version.

MethodGET
URLhttps://api.42min.us/v1/bookings/{uid}
Scopebookings:read
AuthRequired

Headers (response)

ETag: "3"

Response

Same shape as list items, with responses filled in:

{
  "data": {
    "uid": "01H…",
    "version": 3,
    "responses": { "phone": "+44 123 4567 8901", "company": "Acme" },
    "…": "…"
  },
  "meta": { "request_id": "req_…" }
}

curl

curl -i -H "Authorization: Bearer $TOKEN" \
  https://api.42min.us/v1/bookings/01H…

Common errors

  • 404 booking_not_found: unknown UID, or UID is not a UUID (collapsed into the same 404 to avoid leaking format details).

POST /v1/bookings

Create a booking.

MethodPOST
URLhttps://api.42min.us/v1/bookings
Scopebookings:create
AuthRequired
Idempotency-KeyRequired

Headers

Authorization: Bearer <token>
Content-Type: application/json
Idempotency-Key: <unique-per-operation>

Body

Either event_type_id or (username + event_slug) is required.

{
  "event_type_id": "01HXXX…",
  "username": "ada",
  "event_slug": "intro-call",

  "start": "2026-05-20T10:00:00Z",
  "timezone": "Europe/Berlin",

  "attendee": {
    "email": "bob@example.com",
    "name": "Bob Builder",
    "first_name": "Bob",
    "last_name": "Builder",
    "phone": "+49 30 12345678",
    "timezone": "Europe/Berlin",
    "sms_opt_in": false
  },

  "guests": [{ "email": "carol@example.com" }],
  "responses": { "phone": "+49 30 12345678" },
  "metadata": { "crm_id": "C-7" },
  "utm_source": "web",
  "utm_medium": "cta",
  "utm_campaign": "spring-launch"
}

Field reference:

FieldTypeNotes
event_type_idUUIDOne of event_type_id or usernameevent_slug required.
username / event_slugstringAlternative to event_type_id.
startISO 8601Required. Booking length is event_type.duration_minutes.
timezoneIANA tzOptional. Falls back to attendee.timezone, then UTC.
attendee.emailstringRequired. Max 254 chars, RFC-shaped.
attendee.namestringIf omitted, built from first_name + last_name, else falls back to email.
attendee.first_name / last_namestringMax 127 each.
attendee.phonestringMax 64.
attendee.sms_opt_inbooleanDefaults to false. The attendee agrees to get text messages about this booking; workflow SMS steps text only attendees who agreed. Needs attendee.phone as a valid number in international format, starting with +, which is then stored in E.164 form.
guests[]arrayStrings (emails) or {email, name?} objects.
responsesobjectBooking-form answers, keyed by question id.
metadataobjectFree-form JSON. Stored alongside the booking; UTM params are merged in.
utm_source / utm_medium / utm_campaignstringMax 255 each. Merged into metadata.

Response

201 Created with the full booking detail (same shape as GET /v1/bookings/:uid). Initial version is 1.

curl

curl -X POST https://api.42min.us/v1/bookings \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "event_type_id": "01HXXX…",
    "start": "2026-05-20T10:00:00Z",
    "timezone": "Europe/Berlin",
    "attendee": { "email": "bob@example.com", "name": "Bob Builder" }
  }'

Common errors

  • 400 validation_error: required field missing or malformed.
  • 400 attendee_email_invalid: bad email format / length > 254.
  • 400 attendee_phone_required: attendee.sms_opt_in is true but attendee.phone is missing.
  • 400 attendee_phone_invalid: attendee.sms_opt_in is true but attendee.phone isn't a valid number in international format.
  • 400 missing_idempotency_key: header missing.
  • 404 event_type_not_found: bad id/slug.
  • 409 event_type_inactive: event type has status != on.
  • 409 slot_in_past: start is in the past.
  • 409 slot_unavailable: slot is taken or otherwise blocked.
  • 409 idempotency_key_conflict: same Idempotency-Key was used with a different body in the last 24h.
  • 503 slot_lock_timeout: couldn't acquire the per-slot lock. Retry-After: 1.

POST /v1/bookings/:uid/cancel

Cancel a confirmed booking. Idempotent: calling it on an already-cancelled booking returns the current state, not an error.

MethodPOST
URLhttps://api.42min.us/v1/bookings/{uid}/cancel
Scopebookings:cancel
AuthRequired
Idempotency-KeyRequired

Body

{ "reason": "Schedule conflict" }
FieldTypeNotes
reasonstringOptional. Max 1024 chars. Surfaced in calendar removals and notifications.

Response

200 OK with the updated booking. Version is bumped.

curl

curl -X POST https://api.42min.us/v1/bookings/01H…/cancel \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"reason":"Schedule conflict"}'

Common errors

  • 404 booking_not_found: unknown UID.
  • 409 booking_in_past: booking already started or finished.

POST /v1/bookings/:uid/reschedule

Move a confirmed booking to a new start time. The duration is preserved.

MethodPOST
URLhttps://api.42min.us/v1/bookings/{uid}/reschedule
Scopebookings:reschedule
AuthRequired
Idempotency-KeyRequired

Body

{
  "start": "2026-05-22T15:00:00Z",
  "timezone": "Europe/Berlin",
  "reason": "Invitee requested a later time"
}
FieldTypeNotes
startISO 8601Required. Booking ends at start + duration_minutes.
timezoneIANA tzOptional. Falls back to the booking's current timezone.
reasonstringOptional. Max 1024.

Response

200 OK with the updated booking. Version is bumped.

curl

curl -X POST https://api.42min.us/v1/bookings/01H…/reschedule \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"start":"2026-05-22T15:00:00Z","timezone":"Europe/Berlin"}'

Common errors

  • 400 validation_error: start missing or unparseable.
  • 404 booking_not_found.
  • 409 booking_already_cancelled: booking isn't confirmed.
  • 409 booking_in_past: original start is in the past.
  • 409 slot_unavailable: new slot is taken.
  • 422 event_type_disallows_reschedule: the event type opts out of rescheduling.
  • 503 slot_lock_timeout: could not acquire the new-slot lock.

PATCH /v1/bookings/:uid

Patch a booking's mutable fields. Requires optimistic locking.

MethodPATCH
URLhttps://api.42min.us/v1/bookings/{uid}
Scopebookings:update
AuthRequired
Idempotency-KeyRequired
If-MatchRequired (ETag from the most recent GET)

Body

{
  "metadata": { "stage": "qualified", "owner": "ada" },
  "responses": { "phone": "+49 …" },
  "attendee_name": "Bob D. Builder"
}
FieldTypeNotes
metadataobjectShallow-merged into the existing metadata. Pass null for a key to clear it on your side and re-send.
responsesobjectReplaces the booking-form answers wholesale.
attendee_namestringMax 255 chars; stored as the primary attendee's name.

Any other field returns 422 field_immutable with details.fields naming the rejected keys. To change start use reschedule; to end the booking use cancel.

Response

200 OK with the updated booking detail. Response headers include ETag: "<new-version>".

Fires a webhook

A successful PATCH emits the booking.updated webhook (subscribe with booking_updated). The payload is the full booking plus a changed_fields array naming exactly which of metadata / responses / attendee_name this call changed. This is the only event that covers these silent edits; no booking.rescheduled or booking.canceled fires for a PATCH.

curl

curl -X PATCH https://api.42min.us/v1/bookings/01H… \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "If-Match: \"3\"" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"metadata":{"stage":"qualified"},"attendee_name":"Bob D. Builder"}'

Common errors

  • 400 validation_error: metadata/responses must be objects (not arrays/scalars); attendee_name must be ≤ 255 chars.
  • 404 booking_not_found.
  • 409 version_conflict: If-Match doesn't match the current version. Re-GET and retry.
  • 422 field_immutable: body included a field that can't be patched.
  • 428 missing_if_match: header missing.

Last updated September 18, 2026.