Help API

    /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) is null on list responses — 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 — 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 has status != on.
    • 409 slot_in_paststart 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.

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

    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_errormetadata/responses must be objects (not arrays/scalars); attendee_name must be ≤ 255 chars.
    • 404 booking_not_found.
    • 409 version_conflictIf-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 May 19, 2026.