# Kaigi API reference > Kaigi is a scheduling platform. Publish event types, expose live availability, and let invitees book meetings — over a key-less public REST surface and an API-key-authenticated v1 surface. Base URL: `https://app.example.com` (placeholder — replace with your deployment). This document is also served as HTML at `/documentation`. ## Overview Kaigi is a scheduling platform. A host publishes one or more event types (meeting types), each backed by an availability schedule. Kaigi computes live bookable slots from the host's weekly hours, date overrides, Google Calendar busy times, and existing bookings, and lets an invitee confirm a slot. Booking creation writes a calendar event and sends confirmation emails as best-effort side effects. There are two REST surfaces. The public surface under `/api/public` needs no credentials and powers the hosted booking page — it is exactly what a third-party integrator would call to render availability and create bookings on behalf of an invitee. The authenticated surface under `/api/v1` is scoped to an API key and lets a host read their own event types and bookings. All examples use the placeholder base URL `https://app.example.com`. Replace it with your own deployment origin. Requests and responses are JSON; timestamps are ISO-8601 UTC strings unless noted. This page is also available as plain-text markdown at `/llms.txt` for LLM ingestion — the same content, generated from the same source. ## Core concepts ### Event types An event type is a bookable meeting definition owned by a host: a title, URL slug, duration, and scheduling policy (buffers, minimum notice, booking window, slot increment). Only active event types (`isActive: true`) are visible on the public surface. An event type's `schedulingType` is either `individual` (one host) or `round_robin` (a team rotates across eligible hosts). ### Availability: weekly rules and date overrides Availability lives in a schedule with an IANA `timezone`. Weekly rules define recurring working-hours windows (weekday 0=Sunday..6=Saturday, wall-clock times in the schedule timezone; multiple windows per day are allowed for split shifts). A date override replaces the weekly rules for one specific local date — either marking the whole day unavailable or supplying a custom window. The slot engine subtracts Google Calendar busy times and existing confirmed bookings, then applies buffers, minimum notice, and the booking window. ### Slots and days A slot is a bookable time range with ISO-8601 UTC `start` and `end`. The days endpoint returns the subset of local calendar dates (`YYYY-MM-DD`, in the requested `tz`) that contain at least one bookable slot — useful for rendering a date picker before fetching per-day slots. The `tz` query parameter only controls how results are grouped and displayed; it never changes which slots exist. ### Bookings and statuses A booking captures an invitee (name, email, notes, timezone) against a slot. Status is one of the values below. Creating a booking re-validates the slot against live availability and claims it atomically in the database, so a race for the same start returns a conflict. | status | meaning | | --- | --- | | confirmed | Active booking holding the slot. | | cancelled | Cancelled by the invitee or host; the slot is freed. | | error | Internal terminal state used by the booking lifecycle reconciler. | Every booking gets a high-entropy cancel token. Only its hash is stored; the raw token is returned once when the booking is created and is embedded in the invitee's cancel link. The public cancel endpoints authenticate with this raw token. ### Teams and round-robin An event type can belong to an organization (team). A `round_robin` team event type rotates bookings across its eligible hosts: at booking time Kaigi picks a host who is free for the requested slot, preferring the host with the fewest upcoming confirmed bookings (ties break by host creation order). Team-scoped reads on the v1 surface require the caller to be an admin or owner of the organization. ### Data objects The v1 surface returns two canonical object shapes. The public surface returns trimmed subsets of these (only the fields listed on each endpoint). Event type object (returned by the `/api/v1/event-types` endpoints): | field | type | notes | | --- | --- | --- | | id | string (uuid) | Event type id. | | slug | string | URL slug, unique per host (or per team). | | title | string | Display name. | | description | string | null | Optional description. | | durationMinutes | number | Meeting length in minutes. | | bufferBeforeMin | number | Buffer before the meeting. | | bufferAfterMin | number | Buffer after the meeting. | | minNoticeMinutes | number | Minimum lead time before a slot can be booked. | | bookingWindowDays | number | How far ahead slots are offered (days). | | slotIncrementMinutes | number | Granularity of generated start times. | | scheduleId | string | null | Availability schedule; null falls back to the host default. | | color | string | null | Optional display color. | | isActive | boolean | Whether the event type is public. | | createdAt | string (ISO-8601) | Creation timestamp. | | updatedAt | string (ISO-8601) | Last update timestamp. | Booking object (returned by the `/api/v1/bookings` endpoints): | field | type | notes | | --- | --- | --- | | id | string (uuid) | Booking id. | | eventTypeId | string (uuid) | Event type booked. | | status | string | confirmed | cancelled | error. | | start | string (ISO-8601) | Start instant (UTC). | | end | string (ISO-8601) | End instant (UTC). | | inviteeName | string | Invitee name. | | inviteeEmail | string | Invitee email. | | inviteeNotes | string | null | Optional invitee notes. | | timezone | string | Invitee IANA timezone. | | meetUrl | string | null | Google Meet URL, when created. | | cancelledAt | string | null | Cancellation timestamp. | | cancelledBy | string | null | invitee | host, when cancelled. | | cancelReason | string | null | Optional cancellation reason. | | createdAt | string (ISO-8601) | Creation timestamp. | ## Authentication The public surface (`/api/public`) requires no authentication. The authenticated surface (`/api/v1`) requires an API key on every request. ### Creating an API key Sign in and open the dashboard at `/dashboard/api-keys`. Click “Create key”, give it a name, and copy the key when it is shown — it is displayed once and only its hash is stored, so it cannot be retrieved later. Revoke a key from the same page. ### Sending the key Pass the key in the `x-api-key` request header. As an alternative, an `Authorization: Bearer ` header is also accepted. A missing key returns `401 UNAUTHORIZED` with message “Missing API key.”; an invalid key returns `401 UNAUTHORIZED` with “Invalid API key.”. Every `/api/v1` endpoint is scoped to the user who owns the key — you can only read your own event types and bookings (and team resources where you are an admin or owner). ```bash curl https://app.example.com/api/v1/me \ -H "x-api-key: $KAIGI_API_KEY" ``` ### CORS Both developer surfaces send permissive, credential-less CORS headers (`Access-Control-Allow-Origin: *`; allowed methods `GET, POST, DELETE, OPTIONS`; allowed headers `Content-Type, Authorization, x-api-key`). Preflight `OPTIONS` requests return `204`. ### Rate limiting Public endpoints are rate-limited per client IP to 30 requests per 60-second window (best-effort, per server instance). Exceeding the limit returns `429 RATE_LIMITED`. ## Errors and status codes Errors use a consistent JSON envelope. `details` is optional and `requestId` is a per-response UUID useful for support. ```json { "error": { "code": "SLOT_TAKEN", "message": "That time was just booked.", "details": null }, "requestId": "b3b6e0f2-8c2a-4e2a-9f1e-1c2d3e4f5a6b" } ``` | code | status | when | | --- | --- | --- | | BAD_REQUEST | 400 | Missing/invalid JSON body, required field, or query parameter. | | UNAUTHORIZED | 401 | Missing or invalid API key on an /api/v1 request. | | CALENDAR_RECONNECT_REQUIRED | 401 | The host must reconnect their Google Calendar. | | FORBIDDEN | 403 | Caller is not an admin/owner of the requested team. | | NOT_FOUND | 404 | Profile, event type, booking, or user not found (or not owned by the caller). | | EVENT_TYPE_NOT_FOUND | 404 | The event type is missing or inactive at booking time. | | BOOKING_NOT_FOUND | 404 | No booking matches the token or id. | | SLOT_TAKEN | 409 | The slot was claimed by a concurrent booking. | | SLOT_INVALID | 422 | The requested start is not a currently bookable slot. | | RATE_LIMITED | 429 | Too many public requests from this IP. | | INTERNAL | 500 | Unhandled server error. | ## Public API Key-less endpoints under `/api/public`, rooted at a host or team `handle`. These render availability and create/cancel bookings on behalf of an invitee. They are rate-limited per IP (30 requests / 60s) and require no credentials. ### GET /api/public/{handle} Fetch a public host or team profile with its active event types (trimmed fields). **Authentication:** None — public, rate-limited endpoint **Path parameters** - `handle` (string, required) — The host's or team's public handle. **Response** — 200 The host (name, handle) and a list of active event types with public fields only. ```json { "host": { "name": "Ada Lovelace", "handle": "ada" }, "eventTypes": [ { "id": "3f2c1e90-7a1b-4c3d-9e0f-1a2b3c4d5e6f", "slug": "intro-call", "title": "Intro call", "description": "A 30-minute introduction.", "durationMinutes": 30 } ] } ``` **Example** ```bash curl https://app.example.com/api/public/ada ``` **Notes** - Returns 404 NOT_FOUND if no profile matches the handle. ### GET /api/public/{handle}/{eventSlug} Fetch a single public event type by handle and slug. **Authentication:** None — public, rate-limited endpoint **Path parameters** - `handle` (string, required) — Host or team handle. - `eventSlug` (string, required) — Event type slug. **Response** — 200 The host (name, handle) and the event type with public fields only. ```json { "host": { "name": "Ada Lovelace", "handle": "ada" }, "eventType": { "id": "3f2c1e90-7a1b-4c3d-9e0f-1a2b3c4d5e6f", "slug": "intro-call", "title": "Intro call", "description": "A 30-minute introduction.", "durationMinutes": 30 } } ``` **Example** ```bash curl https://app.example.com/api/public/ada/intro-call ``` **Notes** - Only active event types are resolvable; otherwise 404 NOT_FOUND. ### GET /api/public/{handle}/{eventSlug}/days List local calendar dates (YYYY-MM-DD) that contain at least one bookable slot. **Authentication:** None — public, rate-limited endpoint **Path parameters** - `handle` (string, required) — Host or team handle. - `eventSlug` (string, required) — Event type slug. **Query parameters** - `from` (ISO-8601 datetime, required) — Start of the window (inclusive). - `to` (ISO-8601 datetime, required) — End of the window (inclusive). - `tz` (IANA timezone, optional) — Grouping timezone for the returned dates. Defaults to UTC. **Response** — 200 An array of YYYY-MM-DD strings (in tz) that have bookable slots. ```json { "data": ["2026-07-10", "2026-07-11", "2026-07-14"] } ``` **Example** ```bash curl "https://app.example.com/api/public/ada/intro-call/days?from=2026-07-10&to=2026-07-20&tz=America/New_York" ``` **Notes** - 400 BAD_REQUEST if from or to is missing or unparseable. - 404 NOT_FOUND if the event type does not resolve. ### GET /api/public/{handle}/{eventSlug}/slots List live bookable slots (ISO-8601 UTC start/end) over a window. **Authentication:** None — public, rate-limited endpoint **Path parameters** - `handle` (string, required) — Host or team handle. - `eventSlug` (string, required) — Event type slug. **Query parameters** - `from` (ISO-8601 datetime, required) — Start of the window (inclusive). - `to` (ISO-8601 datetime, required) — End of the window (inclusive). - `tz` (IANA timezone, optional) — Display/grouping timezone. Does not change which slots exist. Defaults to UTC. **Response** — 200 An array of { start, end } slots as ISO-8601 UTC. ```json { "data": [ { "start": "2026-07-10T15:00:00.000Z", "end": "2026-07-10T15:30:00.000Z" }, { "start": "2026-07-10T15:30:00.000Z", "end": "2026-07-10T16:00:00.000Z" } ] } ``` **Example** ```bash curl "https://app.example.com/api/public/ada/intro-call/slots?from=2026-07-10T00:00:00Z&to=2026-07-11T00:00:00Z&tz=America/New_York" ``` **Notes** - 400 BAD_REQUEST if from or to is missing or unparseable. - 404 NOT_FOUND if the event type does not resolve. ### POST /api/public/{handle}/{eventSlug}/bookings Create a booking on a public event type. Re-validates the slot and claims it atomically. **Authentication:** None — public, rate-limited endpoint **Path parameters** - `handle` (string, required) — Host or team handle. - `eventSlug` (string, required) — Event type slug. **Body parameters** - `start` (ISO-8601 datetime, required) — The exact slot start to book (must match a live slot). - `name` (string, required) — Invitee name. - `email` (string, required) — Invitee email. - `notes` (string, optional) — Optional message from the invitee. - `timezone` (IANA timezone, optional) — Invitee timezone. Defaults to UTC. **Response** — 201 The new booking id, the raw cancel token (shown only here), a ready-made cancel URL, the host name, and the confirmed start/end. ```json { "bookingId": "b7e0c2d1-4f3a-4b5c-8d6e-7f8a9b0c1d2e", "cancelToken": "u3Xr...redacted...9kQ", "cancelUrl": "https://app.example.com/booking/u3Xr...redacted...9kQ", "hostName": "Ada Lovelace", "start": "2026-07-10T15:00:00.000Z", "end": "2026-07-10T15:30:00.000Z" } ``` **Example** ```bash curl -X POST https://app.example.com/api/public/ada/intro-call/bookings \ -H "content-type: application/json" \ -d '{ "start": "2026-07-10T15:00:00.000Z", "name": "Grace Hopper", "email": "grace@example.com", "notes": "Excited to chat", "timezone": "America/New_York" }' ``` **Notes** - 400 BAD_REQUEST for invalid JSON, a missing start/name/email, or an unparseable start. - 404 EVENT_TYPE_NOT_FOUND if the event type is missing or inactive. - 409 SLOT_TAKEN if a concurrent request claimed the slot first. - 422 SLOT_INVALID if the start is not a currently bookable slot. ### GET /api/public/bookings/{token} Look up a booking by its raw cancel token (the token from the emailed cancel link). **Authentication:** Cancel token in the URL path (no API key) **Path parameters** - `token` (string, required) — The raw cancel token issued when the booking was created. **Response** — 200 A safe booking view: no host email or internal sync columns. ```json { "id": "b7e0c2d1-4f3a-4b5c-8d6e-7f8a9b0c1d2e", "status": "confirmed", "start": "2026-07-10T15:00:00.000Z", "end": "2026-07-10T15:30:00.000Z", "inviteeName": "Grace Hopper", "eventTitle": "Intro call", "meetUrl": "https://meet.google.com/abc-defg-hij", "timezone": "America/New_York" } ``` **Example** ```bash curl https://app.example.com/api/public/bookings/u3Xr...redacted...9kQ ``` **Notes** - 404 NOT_FOUND if no booking matches the token. ### POST /api/public/bookings/{token}/cancel Cancel a booking via its raw cancel token. Idempotent on an already-cancelled booking. **Authentication:** Cancel token in the URL path (no API key) **Path parameters** - `token` (string, required) — The raw cancel token. **Body parameters** - `reason` (string, optional) — Optional cancellation reason. **Response** — 200 Confirmation that the booking is cancelled. ```json { "status": "cancelled" } ``` **Example** ```bash curl -X POST https://app.example.com/api/public/bookings/u3Xr...redacted...9kQ/cancel \ -H "content-type: application/json" \ -d '{ "reason": "Something came up" }' ``` **Notes** - 404 BOOKING_NOT_FOUND if the token does not match a booking. - Cancelling frees the slot, deletes the calendar event, and emails both parties (best-effort). ## Authenticated API (v1) Endpoints under `/api/v1`, authenticated with an API key and scoped to the key's owner. Send the key in the `x-api-key` header (or as `Authorization: Bearer `). A missing or invalid key returns 401 UNAUTHORIZED. ### GET /api/v1/me Return the authenticated user's basic profile. **Authentication:** API key (x-api-key header or Bearer token) **Response** — 200 The user's id, name, email, and handle. ```json { "id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d", "name": "Ada Lovelace", "email": "ada@example.com", "handle": "ada" } ``` **Example** ```bash curl https://app.example.com/api/v1/me \ -H "x-api-key: $KAIGI_API_KEY" ``` **Notes** - 404 NOT_FOUND if the user record is missing. ### GET /api/v1/event-types List the caller's own event types plus event types of teams they administer. **Authentication:** API key (x-api-key header or Bearer token) **Response** — 200 A data array of full event type objects (own event types followed by team event types for orgs where the caller is admin or owner). ```json { "data": [ { "id": "3f2c1e90-7a1b-4c3d-9e0f-1a2b3c4d5e6f", "slug": "intro-call", "title": "Intro call", "description": "A 30-minute introduction.", "durationMinutes": 30, "bufferBeforeMin": 0, "bufferAfterMin": 0, "minNoticeMinutes": 120, "bookingWindowDays": 60, "slotIncrementMinutes": 15, "scheduleId": "8a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d", "color": "#f17463", "isActive": true, "createdAt": "2026-06-01T10:00:00.000Z", "updatedAt": "2026-06-01T10:00:00.000Z" } ] } ``` **Example** ```bash curl https://app.example.com/api/v1/event-types \ -H "x-api-key: $KAIGI_API_KEY" ``` ### GET /api/v1/event-types/{id} Fetch one of the caller's event types by id. **Authentication:** API key (x-api-key header or Bearer token) **Path parameters** - `id` (string (uuid), required) — Event type id. **Response** — 200 A single event type object. ```json { "id": "3f2c1e90-7a1b-4c3d-9e0f-1a2b3c4d5e6f", "slug": "intro-call", "title": "Intro call", "description": "A 30-minute introduction.", "durationMinutes": 30, "bufferBeforeMin": 0, "bufferAfterMin": 0, "minNoticeMinutes": 120, "bookingWindowDays": 60, "slotIncrementMinutes": 15, "scheduleId": "8a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d", "color": "#f17463", "isActive": true, "createdAt": "2026-06-01T10:00:00.000Z", "updatedAt": "2026-06-01T10:00:00.000Z" } ``` **Example** ```bash curl https://app.example.com/api/v1/event-types/3f2c1e90-7a1b-4c3d-9e0f-1a2b3c4d5e6f \ -H "x-api-key: $KAIGI_API_KEY" ``` **Notes** - 404 NOT_FOUND if the event type does not exist or is not owned by the caller. ### GET /api/v1/event-types/{id}/slots List live bookable slots for one of the caller's event types over a window. **Authentication:** API key (x-api-key header or Bearer token) **Path parameters** - `id` (string (uuid), required) — Event type id. **Query parameters** - `from` (ISO-8601 datetime, required) — Start of the window (inclusive). - `to` (ISO-8601 datetime, required) — End of the window (inclusive). - `tz` (IANA timezone, optional) — Display/grouping timezone. Defaults to UTC. **Response** — 200 An array of { start, end } slots as ISO-8601 UTC. ```json { "data": [ { "start": "2026-07-10T15:00:00.000Z", "end": "2026-07-10T15:30:00.000Z" }, { "start": "2026-07-10T15:30:00.000Z", "end": "2026-07-10T16:00:00.000Z" } ] } ``` **Example** ```bash curl "https://app.example.com/api/v1/event-types/3f2c1e90-7a1b-4c3d-9e0f-1a2b3c4d5e6f/slots?from=2026-07-10T00:00:00Z&to=2026-07-11T00:00:00Z" \ -H "x-api-key: $KAIGI_API_KEY" ``` **Notes** - 400 BAD_REQUEST if from or to is missing or unparseable. - 404 NOT_FOUND if the event type is not owned by the caller. ### GET /api/v1/bookings List bookings where the caller is the host. **Authentication:** API key (x-api-key header or Bearer token) **Response** — 200 A data array of booking objects. ```json { "data": [ { "id": "b7e0c2d1-4f3a-4b5c-8d6e-7f8a9b0c1d2e", "eventTypeId": "3f2c1e90-7a1b-4c3d-9e0f-1a2b3c4d5e6f", "status": "confirmed", "start": "2026-07-10T15:00:00.000Z", "end": "2026-07-10T15:30:00.000Z", "inviteeName": "Grace Hopper", "inviteeEmail": "grace@example.com", "inviteeNotes": "Looking forward to it.", "timezone": "America/New_York", "meetUrl": "https://meet.google.com/abc-defg-hij", "cancelledAt": null, "cancelledBy": null, "cancelReason": null, "createdAt": "2026-07-01T12:00:00.000Z" } ] } ``` **Example** ```bash curl https://app.example.com/api/v1/bookings \ -H "x-api-key: $KAIGI_API_KEY" ``` ### POST /api/v1/bookings Create a booking on one of the caller's own event types. Supports idempotency. **Authentication:** API key (x-api-key header or Bearer token) **Body parameters** - `eventTypeId` (string (uuid), required) — An event type owned by the caller. - `start` (ISO-8601 datetime, required) — The exact slot start to book. - `name` (string, required) — Invitee name. - `email` (string, required) — Invitee email. - `notes` (string, optional) — Optional invitee notes. - `timezone` (IANA timezone, optional) — Invitee timezone. Defaults to UTC. - `idempotencyKey` (string, optional) — If a booking already exists for this key, it is returned unchanged instead of creating a new one. **Response** — 201 The created booking object and a cancel URL. (The raw cancel token itself is not returned on this surface.) ```json { "booking": { "id": "b7e0c2d1-4f3a-4b5c-8d6e-7f8a9b0c1d2e", "eventTypeId": "3f2c1e90-7a1b-4c3d-9e0f-1a2b3c4d5e6f", "status": "confirmed", "start": "2026-07-10T15:00:00.000Z", "end": "2026-07-10T15:30:00.000Z", "inviteeName": "Grace Hopper", "inviteeEmail": "grace@example.com", "inviteeNotes": "Looking forward to it.", "timezone": "America/New_York", "meetUrl": "https://meet.google.com/abc-defg-hij", "cancelledAt": null, "cancelledBy": null, "cancelReason": null, "createdAt": "2026-07-01T12:00:00.000Z" }, "cancelUrl": "https://app.example.com/booking/u3Xr...redacted...9kQ" } ``` **Example** ```bash curl -X POST https://app.example.com/api/v1/bookings \ -H "x-api-key: $KAIGI_API_KEY" \ -H "content-type: application/json" \ -d '{ "eventTypeId": "3f2c1e90-7a1b-4c3d-9e0f-1a2b3c4d5e6f", "start": "2026-07-10T15:00:00.000Z", "name": "Grace Hopper", "email": "grace@example.com", "idempotencyKey": "order-1234" }' ``` **Notes** - 400 BAD_REQUEST if eventTypeId, start, name, or email is missing, or start is unparseable. - 404 NOT_FOUND if the event type is not owned by the caller. - 409 SLOT_TAKEN if the slot was just claimed; 422 SLOT_INVALID if the start is not bookable. ### GET /api/v1/bookings/{id} Fetch one of the caller's bookings by id. **Authentication:** API key (x-api-key header or Bearer token) **Path parameters** - `id` (string (uuid), required) — Booking id. **Response** — 200 A single booking object. ```json { "id": "b7e0c2d1-4f3a-4b5c-8d6e-7f8a9b0c1d2e", "eventTypeId": "3f2c1e90-7a1b-4c3d-9e0f-1a2b3c4d5e6f", "status": "confirmed", "start": "2026-07-10T15:00:00.000Z", "end": "2026-07-10T15:30:00.000Z", "inviteeName": "Grace Hopper", "inviteeEmail": "grace@example.com", "inviteeNotes": "Looking forward to it.", "timezone": "America/New_York", "meetUrl": "https://meet.google.com/abc-defg-hij", "cancelledAt": null, "cancelledBy": null, "cancelReason": null, "createdAt": "2026-07-01T12:00:00.000Z" } ``` **Example** ```bash curl https://app.example.com/api/v1/bookings/b7e0c2d1-4f3a-4b5c-8d6e-7f8a9b0c1d2e \ -H "x-api-key: $KAIGI_API_KEY" ``` **Notes** - 404 NOT_FOUND if the booking does not exist or the caller is not its host. ### POST /api/v1/bookings/{id}/cancel Cancel one of the caller's bookings by id. The caller must be the assigned host or an admin/owner of the event type's team. **Authentication:** API key (x-api-key header or Bearer token) **Path parameters** - `id` (string (uuid), required) — Booking id. **Body parameters** - `reason` (string, optional) — Optional cancellation reason. **Response** — 200 The cancelled booking, wrapped as { booking } and using the same booking view as the other v1 booking endpoints. ```json { "booking": { "id": "b7e0c2d1-4f3a-4b5c-8d6e-7f8a9b0c1d2e", "eventTypeId": "3f2c1e90-7a1b-4c3d-9e0f-1a2b3c4d5e6f", "status": "cancelled", "start": "2026-07-10T15:00:00.000Z", "end": "2026-07-10T15:30:00.000Z", "inviteeName": "Grace Hopper", "inviteeEmail": "grace@example.com", "inviteeNotes": "Looking forward to it.", "timezone": "America/New_York", "meetUrl": "https://meet.google.com/abc-defg-hij", "cancelledAt": "2026-07-02T09:00:00.000Z", "cancelledBy": "host", "cancelReason": "Something came up", "createdAt": "2026-07-01T12:00:00.000Z" } } ``` **Example** ```bash curl -X POST https://app.example.com/api/v1/bookings/b7e0c2d1-4f3a-4b5c-8d6e-7f8a9b0c1d2e/cancel \ -H "x-api-key: $KAIGI_API_KEY" \ -H "content-type: application/json" \ -d '{ "reason": "Something came up" }' ``` **Notes** - 404 BOOKING_NOT_FOUND if the booking is missing or the caller may not cancel it. - Idempotent: cancelling an already-cancelled booking returns it unchanged. ### GET /api/v1/team-bookings List bookings across a team's event types. The caller must be an admin or owner of the organization. **Authentication:** API key (x-api-key header or Bearer token) **Query parameters** - `organizationId` (string (uuid), required) — The organization (team) to list bookings for. **Response** — 200 A data array of booking objects, each with an added eventTitle field. ```json { "data": [ { "id": "b7e0c2d1-4f3a-4b5c-8d6e-7f8a9b0c1d2e", "eventTypeId": "3f2c1e90-7a1b-4c3d-9e0f-1a2b3c4d5e6f", "status": "confirmed", "start": "2026-07-10T15:00:00.000Z", "end": "2026-07-10T15:30:00.000Z", "inviteeName": "Grace Hopper", "inviteeEmail": "grace@example.com", "inviteeNotes": null, "timezone": "America/New_York", "meetUrl": "https://meet.google.com/abc-defg-hij", "cancelledAt": null, "cancelledBy": null, "cancelReason": null, "createdAt": "2026-07-01T12:00:00.000Z", "eventTitle": "Team intro" } ] } ``` **Example** ```bash curl "https://app.example.com/api/v1/team-bookings?organizationId=5d4c3b2a-1e0f-4a9b-8c7d-6e5f4a3b2c1d" \ -H "x-api-key: $KAIGI_API_KEY" ``` **Notes** - 400 BAD_REQUEST if organizationId is missing. - 403 FORBIDDEN if the caller is not an admin or owner of the team.