/v1/bookings
Read, create, cancel, reschedule, and patch bookings. This is
the write-heavy surface — every write needs an
Idempotency-Key, and PATCH needs an
If-Match
ETag.
A booking's UID is its UUID — every endpoint that takes
:uid rejects non-UUIDs with
404 booking_not_found.
GET /v1/bookings
List bookings in the authenticated account.
| Method | GET |
| URL | https://api.42min.us/v1/bookings |
| Scope | bookings:read |
| Auth | Required |
Query parameters
| Param | Type | Notes |
|---|---|---|
limit |
integer | Default 20, max 100. |
cursor |
string |
Opaque cursor from a prior
meta.next_cursor.
|
sort |
string |
start_at_desc (default),
start_at_asc, created_at_desc,
updated_at_asc,
updated_at_desc.
|
event_type_id |
UUID | Filter to one event type. |
host_user_id |
UUID | Filter to one host. |
attendee_email |
string | Exact-match filter (case-sensitive). |
status |
string |
Comma-separated. Typical values: confirmed,
canceled.
|
start_date |
ISO 8601 | Lower bound on start_at (inclusive). |
end_date |
ISO 8601 | Upper bound on start_at (inclusive). |
updated_since |
ISO 8601 |
Only bookings whose updated_at is at or
after this time. Pair with
sort=updated_at_asc for catch-up
reconciliation — see below.
|
include_cancelled |
boolean |
When 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) isnullon list responses — fetch the detail to read them. -
calendar_sync_statusissynced/pending/failed/not_applicable(no calendar accounts attached). -
rescheduled_from_uidreferences 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 — 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.
| Method | GET |
| URL |
https://api.42min.us/v1/bookings/{uid}
|
| Scope | bookings:read |
| Auth | Required |
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.
| Method | POST |
| URL | https://api.42min.us/v1/bookings |
| Scope | bookings:create |
| Auth | Required |
| Idempotency-Key | Required |
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:
| Field | Type | Notes |
|---|---|---|
event_type_id |
UUID | One of event_type_id or usernameevent_slug required. |
username / event_slug |
string | Alternative to event_type_id. |
start |
ISO 8601 |
Required. Booking length is
event_type.duration_minutes.
|
timezone |
IANA tz |
Optional. Falls back to attendee.timezone,
then UTC.
|
attendee.email |
string | Required. Max 254 chars, RFC-shaped. |
attendee.name |
string |
If omitted, built from first_name +
last_name, else falls back to email.
|
attendee.first_name /
last_name
|
string | Max 127 each. |
attendee.phone |
string | Max 64. |
attendee.sms_opt_in |
boolean | Defaults to false. |
guests[] |
array |
Strings (emails) or {email, name?} objects.
|
responses |
object | Booking-form answers, keyed by question id. |
metadata |
object | Free-form JSON. Stored alongside the booking; UTM params are merged in. |
utm_source / utm_medium /
utm_campaign
|
string | Max 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 missing_idempotency_key— header missing. 404 event_type_not_found— bad id/slug.-
409 event_type_inactive— event type hasstatus != on. -
409 slot_in_past—startis in the past. -
409 slot_unavailable— slot is taken or otherwise blocked. -
409 idempotency_key_conflict— sameIdempotency-Keywas 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.
| Method | POST |
| URL |
https://api.42min.us/v1/bookings/{uid}/cancel
|
| Scope | bookings:cancel |
| Auth | Required |
| Idempotency-Key | Required |
Body
{ "reason": "Schedule conflict" }
| Field | Type | Notes |
|---|---|---|
reason |
string | Optional. 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.
| Method | POST |
| URL |
https://api.42min.us/v1/bookings/{uid}/reschedule
|
| Scope | bookings:reschedule |
| Auth | Required |
| Idempotency-Key | Required |
Body
{
"start": "2026-05-22T15:00:00Z",
"timezone": "Europe/Berlin",
"reason": "Invitee requested a later time"
}
| Field | Type | Notes |
|---|---|---|
start |
ISO 8601 |
Required. Booking ends at
start + duration_minutes.
|
timezone |
IANA tz | Optional. Falls back to the booking's current timezone. |
reason |
string | Optional. 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—startmissing or unparseable. 404 booking_not_found.-
409 booking_already_cancelled— booking isn'tconfirmed. -
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.
| Method | PATCH |
| URL |
https://api.42min.us/v1/bookings/{uid}
|
| Scope | bookings:update |
| Auth | Required |
| Idempotency-Key | Required |
| If-Match |
Required (ETag from the most recent GET)
|
Body
{
"metadata": { "stage": "qualified", "owner": "ada" },
"responses": { "phone": "+49 …" },
"attendee_name": "Bob D. Builder"
}
| Field | Type | Notes |
|---|---|---|
metadata |
object |
Shallow-merged into the existing metadata. Pass
null for a key to clear it on your side and
re-send.
|
responses |
object | Replaces the booking-form answers wholesale. |
attendee_name |
string | Max 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/responsesmust be objects (not arrays/scalars);attendee_namemust be ≤ 255 chars. 404 booking_not_found.-
409 version_conflict—If-Matchdoesn't match the currentversion. Re-GET and retry. -
422 field_immutable— body included a field that can't be patched. 428 missing_if_match— header missing.
Last updated May 19, 2026.