{
  "openapi": "3.1.0",
  "info": {
    "title": "clndr.pro API",
    "version": "1.0.0",
    "description": "The clndr.pro REST API puts your booking pages on your own site. List your booking pages, read the open time slots for a day, and create bookings from a browser or a server. From a server you can also list, confirm and cancel the bookings that come in.\n\n**Base URL:** `https://www.clndr.pro/api/v1`\n\nUse the `www` host. `https://clndr.pro` redirects to it, and HTTP clients drop the `Authorization` header when they follow a redirect to another host, so requests to the bare domain fail with `401 Missing API key`.\n\n## Authentication\n\nSend an API key with every request, as a bearer token or in the `x-clndr-key` header:\n\n```http\nAuthorization: Bearer clndr_sk_4f1c…\n```\n\nCreate keys in the clndr.pro dashboard under **API Keys**. A key belongs to the clndr.pro account that created it and only ever sees that account's booking pages and bookings. See [API keys](/api-keys) for rotation and storage.\n\n| Key type | Prefix | Use it in | Default scopes |\n| --- | --- | --- | --- |\n| Secret | `clndr_sk_` | Your server only | `booking_pages:read` `booking_pages:slots` `bookings:create` `bookings:read` `bookings:write` |\n| Publishable | `clndr_pk_` | Browsers and mobile apps | `booking_pages:read` `booking_pages:slots` `bookings:create` |\n\n## Scopes\n\n| Scope | Allows |\n| --- | --- |\n| `booking_pages:read` | List booking pages and read one with its questions |\n| `booking_pages:slots` | Read open time slots |\n| `bookings:create` | Create a booking on one of your pages |\n| `bookings:read` | List and read bookings (guest names and emails included) |\n| `bookings:write` | Confirm and cancel bookings |\n\nA request without a required scope gets `403`. Publishable keys can never hold `bookings:read` or `bookings:write`.\n\n## Errors\n\nErrors return a JSON body with a human-readable message:\n\n```json\n{ \"error\": \"Booking page not found\" }\n```\n\n| Status | Meaning |\n| --- | --- |\n| `400` | The request is malformed: a missing field, invalid JSON, a bad email address or timestamp. The message names it. |\n| `401` | The key is missing, unknown, revoked or expired. |\n| `403` | The key lacks a scope, or the booking page belongs to another account. |\n| `404` | No such booking page or booking for this key's account. |\n| `409` | The slot is taken, or (with a publishable key) isn't one the slots endpoint offers. Fetch the slots again. |\n| `429` | Rate limit hit: the key's per-minute limit (wait `Retry-After` seconds), or the guest email's 5 bookings an hour. |\n| `500` | Something failed on our side. Retry with backoff. |\n\n## Rate limits\n\nLimits are per key, in fixed 60-second windows: **300 requests** for secret keys and **120** for publishable keys. A `429` carries `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix seconds). Successful responses don't carry these headers.\n\nSeparately, one guest email address can create at most 5 bookings an hour.\n\n## CORS\n\nEvery endpoint answers preflight requests with `Access-Control-Allow-Origin: *`, so a publishable key works from any origin.\n\n## Dates and times\n\nTimestamps are ISO 8601 in UTC, for example `2026-10-15T13:30:00.000Z`. Format them in the guest's timezone when you display them. A host's own timezone is `userProfile.timezone` on [Get a booking page](/api-reference/operations/getBookingPage).\n\n## Field names\n\nRequest bodies use camelCase (`guestEmail`). Response objects are database rows and use snake_case (`guest_email`). The [TypeScript SDK](/sdk) has types for all of them.\n",
    "contact": {
      "name": "clndr.pro support",
      "email": "hello@devsforfun.com",
      "url": "https://docs.clndr.pro"
    }
  },
  "servers": [
    {
      "url": "https://www.clndr.pro/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyHeader": []
    }
  ],
  "tags": [
    {
      "name": "Booking pages",
      "description": "Booking pages, their questions and their open slots."
    },
    {
      "name": "Bookings",
      "description": "Create bookings from any key; read, confirm and cancel them from a secret key."
    },
    {
      "name": "Legacy",
      "description": "Endpoints kept for older integrations."
    }
  ],
  "paths": {
    "/booking-pages": {
      "get": {
        "operationId": "listBookingPages",
        "summary": "List booking pages",
        "description": "Returns every booking page on the key's account, newest first. The list includes inactive and private pages: check is_active (and visibility, if it matters to you) before you show a page to guests. Scope: booking_pages:read. Keys: secret and publishable.",
        "tags": [
          "Booking pages"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "x-required-scopes": [
          "booking_pages:read"
        ],
        "x-key-types": [
          "secret",
          "publishable"
        ],
        "responses": {
          "200": {
            "description": "Your booking pages.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BookingPageSummary"
                      }
                    }
                  },
                  "example": {
                    "data": [
                      {
                        "id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                        "slug": "intro-call",
                        "title": "Intro call",
                        "description": "A 30 minute call to see if we're a fit.\n\n- What you're building\n- Timeline and budget",
                        "duration_minutes": 30,
                        "booking_type": "direct",
                        "max_days_ahead": 30,
                        "buffer_time_minutes": 10,
                        "visibility": "public",
                        "auto_generate_meet_link": true,
                        "is_active": true,
                        "created_at": "2026-09-02T08:14:55.120+00:00"
                      }
                    ]
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                      "slug": "intro-call",
                      "title": "Intro call",
                      "description": "A 30 minute call to see if we're a fit.\n\n- What you're building\n- Timeline and budget",
                      "duration_minutes": 30,
                      "booking_type": "direct",
                      "max_days_ahead": 30,
                      "buffer_time_minutes": 10,
                      "visibility": "public",
                      "auto_generate_meet_link": true,
                      "is_active": true,
                      "created_at": "2026-09-02T08:14:55.120+00:00"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/booking-pages/{slug}": {
      "get": {
        "operationId": "getBookingPage",
        "summary": "Get a booking page",
        "description": "Returns one active booking page with the host's public profile and the questions to ask guests. This is everything you need to render a booking form. Scope: booking_pages:read. Keys: secret and publishable.",
        "tags": [
          "Booking pages"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "x-required-scopes": [
          "booking_pages:read"
        ],
        "x-key-types": [
          "secret",
          "publishable"
        ],
        "responses": {
          "200": {
            "description": "The booking page, its host and its questions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BookingPageDetail"
                    }
                  },
                  "example": {
                    "data": {
                      "bookingPage": {
                        "id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                        "slug": "intro-call",
                        "title": "Intro call",
                        "description": "A 30 minute call to see if we're a fit.\n\n- What you're building\n- Timeline and budget",
                        "duration_minutes": 30,
                        "booking_type": "direct",
                        "max_days_ahead": 30,
                        "buffer_time_minutes": 10,
                        "visibility": "public",
                        "auto_generate_meet_link": true,
                        "is_active": true,
                        "created_at": "2026-09-02T08:14:55.120+00:00",
                        "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
                        "updated_at": "2026-09-30T11:02:10.004+00:00",
                        "availability_set_id": "a1f0c2d3-4e5b-4c6d-8e7f-9a0b1c2d3e4f",
                        "allowed_team_id": null,
                        "show_access_code_prompt": true,
                        "show_on_profile": true
                      },
                      "userProfile": {
                        "username": "ada",
                        "full_name": "Ada Lovelace",
                        "bio": "Building analytical engines.",
                        "avatar_url": "https://www.clndr.pro/avatars/ada.png",
                        "timezone": "Europe/London"
                      },
                      "questions": [
                        {
                          "id": "0e4b7a2c-9d1f-4b3e-a6c8-5f2d1e9b7c40",
                          "question_text": "What would you like to talk about?",
                          "question_type": "textarea",
                          "options": null,
                          "is_required": true,
                          "order_index": 0
                        },
                        {
                          "id": "6f1c3e8a-2d7b-4a9e-b5c1-8e0d4f2a6b93",
                          "question_text": "Phone number",
                          "question_type": "phone",
                          "options": null,
                          "is_required": false,
                          "order_index": 1
                        }
                      ]
                    }
                  }
                },
                "example": {
                  "data": {
                    "bookingPage": {
                      "id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                      "slug": "intro-call",
                      "title": "Intro call",
                      "description": "A 30 minute call to see if we're a fit.\n\n- What you're building\n- Timeline and budget",
                      "duration_minutes": 30,
                      "booking_type": "direct",
                      "max_days_ahead": 30,
                      "buffer_time_minutes": 10,
                      "visibility": "public",
                      "auto_generate_meet_link": true,
                      "is_active": true,
                      "created_at": "2026-09-02T08:14:55.120+00:00",
                      "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
                      "updated_at": "2026-09-30T11:02:10.004+00:00",
                      "availability_set_id": "a1f0c2d3-4e5b-4c6d-8e7f-9a0b1c2d3e4f",
                      "allowed_team_id": null,
                      "show_access_code_prompt": true,
                      "show_on_profile": true
                    },
                    "userProfile": {
                      "username": "ada",
                      "full_name": "Ada Lovelace",
                      "bio": "Building analytical engines.",
                      "avatar_url": "https://www.clndr.pro/avatars/ada.png",
                      "timezone": "Europe/London"
                    },
                    "questions": [
                      {
                        "id": "0e4b7a2c-9d1f-4b3e-a6c8-5f2d1e9b7c40",
                        "question_text": "What would you like to talk about?",
                        "question_type": "textarea",
                        "options": null,
                        "is_required": true,
                        "order_index": 0
                      },
                      {
                        "id": "6f1c3e8a-2d7b-4a9e-b5c1-8e0d4f2a6b93",
                        "question_text": "Phone number",
                        "question_type": "phone",
                        "options": null,
                        "is_required": false,
                        "order_index": 1
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No active booking page with this slug on the key's account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "Booking page not found"
                  }
                },
                "example": {
                  "error": "Booking page not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The booking page's slug.",
            "schema": {
              "type": "string"
            },
            "example": "intro-call"
          }
        ]
      }
    },
    "/booking-pages/{slug}/slots": {
      "get": {
        "operationId": "listSlots",
        "summary": "List open slots",
        "description": "Returns the slots a guest can book on one day, in start order. The date is a calendar day in the host's timezone (userProfile.timezone on the booking page), so a slot can fall on the previous or next day for a guest elsewhere: convert each start to the guest's timezone before you group or label it. A slot is offered when it sits inside the host's weekly availability and doesn't overlap a booking or a synced calendar event. Slots that have already started, and days beyond the page's max_days_ahead, are left out. Scope: booking_pages:slots. Keys: secret and publishable.",
        "tags": [
          "Booking pages"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "x-required-scopes": [
          "booking_pages:slots"
        ],
        "x-key-types": [
          "secret",
          "publishable"
        ],
        "responses": {
          "200": {
            "description": "Open slots for the day. Empty when the host has no availability that day.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TimeSlot"
                      }
                    }
                  },
                  "example": {
                    "data": [
                      {
                        "start": "2026-10-15T08:00:00.000Z",
                        "end": "2026-10-15T08:30:00.000Z"
                      },
                      {
                        "start": "2026-10-15T08:40:00.000Z",
                        "end": "2026-10-15T09:10:00.000Z"
                      },
                      {
                        "start": "2026-10-15T09:20:00.000Z",
                        "end": "2026-10-15T09:50:00.000Z"
                      }
                    ]
                  }
                },
                "example": {
                  "data": [
                    {
                      "start": "2026-10-15T08:00:00.000Z",
                      "end": "2026-10-15T08:30:00.000Z"
                    },
                    {
                      "start": "2026-10-15T08:40:00.000Z",
                      "end": "2026-10-15T09:10:00.000Z"
                    },
                    {
                      "start": "2026-10-15T09:20:00.000Z",
                      "end": "2026-10-15T09:50:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "date is missing or not a date.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "Missing required `date` query param (YYYY-MM-DD)"
                  }
                },
                "example": {
                  "error": "Missing required `date` query param (YYYY-MM-DD)"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No active booking page with this slug on the key's account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "Booking page not found"
                  }
                },
                "example": {
                  "error": "Booking page not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The booking page's slug.",
            "schema": {
              "type": "string"
            },
            "example": "intro-call"
          },
          {
            "name": "date",
            "in": "query",
            "required": true,
            "description": "The day, as YYYY-MM-DD, in the host's timezone. A full ISO 8601 timestamp also works and means the host's day that contains that instant.",
            "schema": {
              "type": "string",
              "examples": [
                "2026-10-15"
              ]
            },
            "example": "2026-10-15"
          }
        ]
      }
    },
    "/bookings": {
      "get": {
        "operationId": "listBookings",
        "summary": "List bookings",
        "description": "Returns bookings across all of the account's booking pages, latest start_time first. Responses include guest names and emails, so call this from your server only. Scope: bookings:read. Keys: secret.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "x-required-scopes": [
          "bookings:read"
        ],
        "x-key-types": [
          "secret"
        ],
        "responses": {
          "200": {
            "description": "Bookings, latest start time first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Booking"
                      }
                    }
                  },
                  "example": {
                    "data": [
                      {
                        "id": "c3a7e5d1-2b9f-4c6e-8a1d-7f4b0e2c9a58",
                        "booking_page_id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                        "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
                        "guest_name": "Grace Hopper",
                        "guest_email": "grace@example.com",
                        "start_time": "2026-10-15T08:40:00+00:00",
                        "end_time": "2026-10-15T09:10:00+00:00",
                        "status": "confirmed",
                        "cancellation_reason": null,
                        "reminder_sent": false,
                        "google_meet_link": "https://meet.google.com/abc-defg-hij",
                        "google_calendar_event_id": "6v1m0r2k8f3t5q9b7n4c",
                        "google_calendar_id": "primary",
                        "sync_status": "synced",
                        "last_synced_at": "2026-10-10T12:31:07.481+00:00",
                        "sync_failure_reason": null,
                        "created_at": "2026-10-10T12:31:05.902+00:00",
                        "updated_at": "2026-10-10T12:31:07.481+00:00"
                      }
                    ]
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "c3a7e5d1-2b9f-4c6e-8a1d-7f4b0e2c9a58",
                      "booking_page_id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                      "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
                      "guest_name": "Grace Hopper",
                      "guest_email": "grace@example.com",
                      "start_time": "2026-10-15T08:40:00+00:00",
                      "end_time": "2026-10-15T09:10:00+00:00",
                      "status": "confirmed",
                      "cancellation_reason": null,
                      "reminder_sent": false,
                      "google_meet_link": "https://meet.google.com/abc-defg-hij",
                      "google_calendar_event_id": "6v1m0r2k8f3t5q9b7n4c",
                      "google_calendar_id": "primary",
                      "sync_status": "synced",
                      "last_synced_at": "2026-10-10T12:31:07.481+00:00",
                      "sync_failure_reason": null,
                      "created_at": "2026-10-10T12:31:05.902+00:00",
                      "updated_at": "2026-10-10T12:31:07.481+00:00"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only return bookings with this status.",
            "schema": {
              "$ref": "#/components/schemas/BookingStatus"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many bookings to return. Values above 200 are capped at 200. There's no cursor; narrow with status instead.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ]
      },
      "post": {
        "operationId": "createBooking",
        "summary": "Create a booking",
        "description": "Books a slot on one of your booking pages. Fetch the slots first and send the chosen slot's start and end unchanged. On a direct page the booking is confirmed straight away: the guest gets a confirmation email, the host is notified, and if the page creates Meet links a Google Calendar event is added and Google invites the guest. On an approval page the booking is pending: the host gets a request email and the guest hears nothing until the host confirms it with PATCH /bookings/{id}. With a publishable key, the start and end must match a slot that List open slots offers right now (future, inside the host's hours, within max_days_ahead), because anyone can copy that key from your page. A secret key, which only your server holds, can book any time that doesn't overlap another pending or confirmed booking, even outside the host's hours. One guest email address gets 5 booking attempts per clock hour. Scope: bookings:create. Keys: secret and publishable.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "x-required-scopes": [
          "bookings:create"
        ],
        "x-key-types": [
          "secret",
          "publishable"
        ],
        "responses": {
          "201": {
            "description": "The new booking. It doesn't include the guest's answers; read them with Get a booking.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Booking"
                    }
                  },
                  "example": {
                    "data": {
                      "id": "c3a7e5d1-2b9f-4c6e-8a1d-7f4b0e2c9a58",
                      "booking_page_id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                      "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
                      "guest_name": "Grace Hopper",
                      "guest_email": "grace@example.com",
                      "start_time": "2026-10-15T08:40:00+00:00",
                      "end_time": "2026-10-15T09:10:00+00:00",
                      "status": "confirmed",
                      "cancellation_reason": null,
                      "reminder_sent": false,
                      "google_meet_link": "https://meet.google.com/abc-defg-hij",
                      "google_calendar_event_id": "6v1m0r2k8f3t5q9b7n4c",
                      "google_calendar_id": "primary",
                      "sync_status": "synced",
                      "last_synced_at": "2026-10-10T12:31:07.481+00:00",
                      "sync_failure_reason": null,
                      "created_at": "2026-10-10T12:31:05.902+00:00",
                      "updated_at": "2026-10-10T12:31:07.481+00:00"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c3a7e5d1-2b9f-4c6e-8a1d-7f4b0e2c9a58",
                    "booking_page_id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                    "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
                    "guest_name": "Grace Hopper",
                    "guest_email": "grace@example.com",
                    "start_time": "2026-10-15T08:40:00+00:00",
                    "end_time": "2026-10-15T09:10:00+00:00",
                    "status": "confirmed",
                    "cancellation_reason": null,
                    "reminder_sent": false,
                    "google_meet_link": "https://meet.google.com/abc-defg-hij",
                    "google_calendar_event_id": "6v1m0r2k8f3t5q9b7n4c",
                    "google_calendar_id": "primary",
                    "sync_status": "synced",
                    "last_synced_at": "2026-10-10T12:31:07.481+00:00",
                    "sync_failure_reason": null,
                    "created_at": "2026-10-10T12:31:05.902+00:00",
                    "updated_at": "2026-10-10T12:31:07.481+00:00"
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing, the body isn't JSON, guestEmail isn't an email address, the timestamps don't parse or end before they start, or responses isn't an array.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "`guestEmail` must be a valid email address"
                  }
                },
                "example": {
                  "error": "`guestEmail` must be a valid email address"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "The key lacks bookings:create, or bookingPageId belongs to another account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "This booking page does not belong to the API key owner"
                  }
                },
                "example": {
                  "error": "This booking page does not belong to the API key owner"
                }
              }
            }
          },
          "409": {
            "description": "The slot can't be booked. With a publishable key the time isn't an open slot (\"This time slot is not available. Fetch the slots again and pick another.\"); with any key, another booking got there first (\"This time slot is no longer available. Please pick another.\"). Fetch the slots again and let the guest pick another.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "This time slot is not available. Fetch the slots again and pick another."
                  }
                },
                "example": {
                  "error": "This time slot is not available. Fetch the slots again and pick another."
                }
              }
            }
          },
          "429": {
            "description": "Either the key's per-minute rate limit (with Retry-After and X-RateLimit headers), or this guest email has used its 5 booking attempts for the current clock hour (\"Too many booking requests. Please try again later.\", no headers; resets at the top of the hour).",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the key's window resets. Absent for the guest-email limit.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "Too many booking requests. Please try again later."
                  }
                },
                "example": {
                  "error": "Too many booking requests. Please try again later."
                }
              }
            }
          },
          "500": {
            "description": "Creating the booking failed on our side (\"Failed to create meeting\" or \"An unexpected error occurred\"). Check with List bookings whether it exists before you retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "Failed to create meeting"
                  }
                },
                "example": {
                  "error": "Failed to create meeting"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBookingRequest"
              },
              "example": {
                "bookingPageId": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                "guestName": "Grace Hopper",
                "guestEmail": "grace@example.com",
                "startTime": "2026-10-15T08:40:00.000Z",
                "endTime": "2026-10-15T09:10:00.000Z",
                "responses": [
                  {
                    "questionId": "0e4b7a2c-9d1f-4b3e-a6c8-5f2d1e9b7c40",
                    "answer": "Moving our sales demos onto clndr.pro."
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/bookings/{id}": {
      "get": {
        "operationId": "getBooking",
        "summary": "Get a booking",
        "description": "Returns one booking with the guest's answers. Scope: bookings:read. Keys: secret.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "x-required-scopes": [
          "bookings:read"
        ],
        "x-key-types": [
          "secret"
        ],
        "responses": {
          "200": {
            "description": "The booking and the guest's answers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BookingWithResponses"
                    }
                  },
                  "example": {
                    "data": {
                      "id": "c3a7e5d1-2b9f-4c6e-8a1d-7f4b0e2c9a58",
                      "booking_page_id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                      "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
                      "guest_name": "Grace Hopper",
                      "guest_email": "grace@example.com",
                      "start_time": "2026-10-15T08:40:00+00:00",
                      "end_time": "2026-10-15T09:10:00+00:00",
                      "status": "confirmed",
                      "cancellation_reason": null,
                      "reminder_sent": false,
                      "google_meet_link": "https://meet.google.com/abc-defg-hij",
                      "google_calendar_event_id": "6v1m0r2k8f3t5q9b7n4c",
                      "google_calendar_id": "primary",
                      "sync_status": "synced",
                      "last_synced_at": "2026-10-10T12:31:07.481+00:00",
                      "sync_failure_reason": null,
                      "created_at": "2026-10-10T12:31:05.902+00:00",
                      "updated_at": "2026-10-10T12:31:07.481+00:00",
                      "meeting_responses": [
                        {
                          "question_id": "0e4b7a2c-9d1f-4b3e-a6c8-5f2d1e9b7c40",
                          "answer": "Moving our sales demos onto clndr.pro."
                        }
                      ]
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c3a7e5d1-2b9f-4c6e-8a1d-7f4b0e2c9a58",
                    "booking_page_id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                    "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
                    "guest_name": "Grace Hopper",
                    "guest_email": "grace@example.com",
                    "start_time": "2026-10-15T08:40:00+00:00",
                    "end_time": "2026-10-15T09:10:00+00:00",
                    "status": "confirmed",
                    "cancellation_reason": null,
                    "reminder_sent": false,
                    "google_meet_link": "https://meet.google.com/abc-defg-hij",
                    "google_calendar_event_id": "6v1m0r2k8f3t5q9b7n4c",
                    "google_calendar_id": "primary",
                    "sync_status": "synced",
                    "last_synced_at": "2026-10-10T12:31:07.481+00:00",
                    "sync_failure_reason": null,
                    "created_at": "2026-10-10T12:31:05.902+00:00",
                    "updated_at": "2026-10-10T12:31:07.481+00:00",
                    "meeting_responses": [
                      {
                        "question_id": "0e4b7a2c-9d1f-4b3e-a6c8-5f2d1e9b7c40",
                        "answer": "Moving our sales demos onto clndr.pro."
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No booking with this id on the key's account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "Booking not found"
                  }
                },
                "example": {
                  "error": "Booking not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The booking's id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "c3a7e5d1-2b9f-4c6e-8a1d-7f4b0e2c9a58"
          }
        ]
      },
      "patch": {
        "operationId": "updateBooking",
        "summary": "Confirm or cancel a booking",
        "description": "Sets a booking's status to confirmed or cancelled, which is how you approve or decline pending bookings on approval pages. Confirming creates the Google Calendar event and Meet link when the page has auto_generate_meet_link and the booking has no event yet, and Google sends the guest the invite. clndr.pro doesn't send its own confirmation email for confirmations made through the API, so email the guest yourself when the page has no Meet link. Cancelling deletes the booking's Google Calendar event (Google tells the guest) and opens the slot up again. There's no status check: confirming a booking the host already declined confirms it again, so read it first if that matters. Scope: bookings:write. Keys: secret.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "x-required-scopes": [
          "bookings:write"
        ],
        "x-key-types": [
          "secret"
        ],
        "responses": {
          "200": {
            "description": "The updated booking.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Booking"
                    }
                  },
                  "example": {
                    "data": {
                      "id": "c3a7e5d1-2b9f-4c6e-8a1d-7f4b0e2c9a58",
                      "booking_page_id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                      "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
                      "guest_name": "Grace Hopper",
                      "guest_email": "grace@example.com",
                      "start_time": "2026-10-15T08:40:00+00:00",
                      "end_time": "2026-10-15T09:10:00+00:00",
                      "status": "confirmed",
                      "cancellation_reason": null,
                      "reminder_sent": false,
                      "google_meet_link": "https://meet.google.com/abc-defg-hij",
                      "google_calendar_event_id": "6v1m0r2k8f3t5q9b7n4c",
                      "google_calendar_id": "primary",
                      "sync_status": "synced",
                      "last_synced_at": "2026-10-10T12:31:07.481+00:00",
                      "sync_failure_reason": null,
                      "created_at": "2026-10-10T12:31:05.902+00:00",
                      "updated_at": "2026-10-10T12:31:07.481+00:00"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c3a7e5d1-2b9f-4c6e-8a1d-7f4b0e2c9a58",
                    "booking_page_id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                    "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
                    "guest_name": "Grace Hopper",
                    "guest_email": "grace@example.com",
                    "start_time": "2026-10-15T08:40:00+00:00",
                    "end_time": "2026-10-15T09:10:00+00:00",
                    "status": "confirmed",
                    "cancellation_reason": null,
                    "reminder_sent": false,
                    "google_meet_link": "https://meet.google.com/abc-defg-hij",
                    "google_calendar_event_id": "6v1m0r2k8f3t5q9b7n4c",
                    "google_calendar_id": "primary",
                    "sync_status": "synced",
                    "last_synced_at": "2026-10-10T12:31:07.481+00:00",
                    "sync_failure_reason": null,
                    "created_at": "2026-10-10T12:31:05.902+00:00",
                    "updated_at": "2026-10-10T12:31:07.481+00:00"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, or status isn't confirmed or cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "`status` must be \"confirmed\" or \"cancelled\""
                  }
                },
                "example": {
                  "error": "`status` must be \"confirmed\" or \"cancelled\""
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No booking with this id on the key's account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "Booking not found"
                  }
                },
                "example": {
                  "error": "Booking not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The booking's id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "c3a7e5d1-2b9f-4c6e-8a1d-7f4b0e2c9a58"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBookingRequest"
              },
              "examples": {
                "approve": {
                  "summary": "Approve a pending booking",
                  "value": {
                    "status": "confirmed"
                  }
                },
                "decline": {
                  "summary": "Decline with a reason",
                  "value": {
                    "status": "cancelled",
                    "reason": "I'm travelling that week."
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "cancelBooking",
        "summary": "Cancel a booking",
        "description": "Cancels a booking and deletes its Google Calendar event. The same as Confirm or cancel a booking with status: \"cancelled\", except that it never stores a reason. Scope: bookings:write. Keys: secret.",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "x-required-scopes": [
          "bookings:write"
        ],
        "x-key-types": [
          "secret"
        ],
        "responses": {
          "200": {
            "description": "The booking is cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "example": {
                    "success": true
                  }
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No booking with this id on the key's account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "Booking not found"
                  }
                },
                "example": {
                  "error": "Booking not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The booking's id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "c3a7e5d1-2b9f-4c6e-8a1d-7f4b0e2c9a58"
          }
        ]
      }
    },
    "/embed/booking": {
      "get": {
        "operationId": "listPublicBookingPages",
        "summary": "List a user's profile pages (legacy)",
        "description": "Kept for the original embed SDK. Returns any user's public profile and the active booking pages they show on it. Use List booking pages in new code. Scope: booking_pages:read. Keys: secret and publishable.",
        "tags": [
          "Legacy"
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "x-required-scopes": [
          "booking_pages:read"
        ],
        "x-key-types": [
          "secret",
          "publishable"
        ],
        "responses": {
          "200": {
            "description": "The user's profile and the booking pages listed on it. Unlike every other endpoint, the body is not wrapped in data.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "userProfile",
                    "bookingPages"
                  ],
                  "properties": {
                    "userProfile": {
                      "$ref": "#/components/schemas/PublicProfile"
                    },
                    "bookingPages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicBookingPage"
                      }
                    }
                  },
                  "example": {
                    "userProfile": {
                      "id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
                      "username": "ada",
                      "full_name": "Ada Lovelace",
                      "bio": "Building analytical engines.",
                      "avatar_url": null
                    },
                    "bookingPages": [
                      {
                        "id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                        "title": "Intro call",
                        "slug": "intro-call",
                        "description": null,
                        "duration_minutes": 30
                      }
                    ]
                  }
                },
                "example": {
                  "userProfile": {
                    "id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
                    "username": "ada",
                    "full_name": "Ada Lovelace",
                    "bio": "Building analytical engines.",
                    "avatar_url": null
                  },
                  "bookingPages": [
                    {
                      "id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
                      "title": "Intro call",
                      "slug": "intro-call",
                      "description": null,
                      "duration_minutes": 30
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "username is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "Username is required"
                  }
                },
                "example": {
                  "error": "Username is required"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No such user.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "What went wrong, in plain English."
                    }
                  },
                  "example": {
                    "error": "User not found"
                  }
                },
                "example": {
                  "error": "User not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "required": true,
            "description": "A clndr.pro username.",
            "schema": {
              "type": "string"
            },
            "example": "ada"
          }
        ],
        "deprecated": true
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "clndr API key",
        "description": "Authorization: Bearer clndr_sk_… (secret) or clndr_pk_… (publishable)."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-clndr-key",
        "description": "The same key in a custom header, for clients that can't set Authorization."
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "The key is missing, unknown, revoked or expired.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error"
              ],
              "properties": {
                "error": {
                  "type": "string",
                  "description": "What went wrong, in plain English."
                }
              },
              "example": {
                "error": "Missing API key. Send `Authorization: Bearer <key>` or `x-clndr-key` header."
              }
            },
            "example": {
              "error": "Missing API key. Send `Authorization: Bearer <key>` or `x-clndr-key` header."
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key doesn't have the scope this endpoint needs.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error"
              ],
              "properties": {
                "error": {
                  "type": "string",
                  "description": "What went wrong, in plain English."
                }
              },
              "example": {
                "error": "API key is missing required scope(s): bookings:read"
              }
            },
            "example": {
              "error": "API key is missing required scope(s): bookings:read"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests from this key in the current 60-second window.",
        "headers": {
          "Retry-After": {
            "description": "Seconds until the window resets.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Limit": {
            "description": "Requests allowed per window: 300 for secret keys, 120 for publishable keys.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining": {
            "description": "Always 0 on a 429.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset": {
            "description": "When the window resets, in Unix seconds.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error"
              ],
              "properties": {
                "error": {
                  "type": "string",
                  "description": "What went wrong, in plain English."
                }
              },
              "example": {
                "error": "Rate limit exceeded. This publishable key allows 120 requests per 60s window."
              }
            },
            "example": {
              "error": "Rate limit exceeded. This publishable key allows 120 requests per 60s window."
            }
          }
        }
      },
      "ServerError": {
        "description": "Something failed on our side. Retry with exponential backoff.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error"
              ],
              "properties": {
                "error": {
                  "type": "string",
                  "description": "What went wrong, in plain English."
                }
              },
              "example": {
                "error": "Failed to fetch bookings"
              }
            },
            "example": {
              "error": "Failed to fetch bookings"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "What went wrong, in plain English."
          }
        }
      },
      "BookingType": {
        "type": "string",
        "enum": [
          "direct",
          "approval"
        ],
        "description": "direct bookings are confirmed on the spot. approval bookings start as pending until the host confirms or cancels them."
      },
      "Visibility": {
        "type": "string",
        "enum": [
          "public",
          "private",
          "team"
        ],
        "description": "Who can open the hosted booking page at www.clndr.pro/{username}/{slug}. private pages need an access code; team pages need a signed-in team member. API keys see all of their owner's pages regardless."
      },
      "BookingPageSummary": {
        "type": "object",
        "description": "A booking page as returned by List booking pages.",
        "required": [
          "id",
          "slug",
          "title",
          "description",
          "duration_minutes",
          "booking_type",
          "max_days_ahead",
          "buffer_time_minutes",
          "visibility",
          "auto_generate_meet_link",
          "is_active",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Pass this as bookingPageId when you create a booking."
          },
          "slug": {
            "type": "string",
            "description": "URL segment, unique per account. Lowercase letters, digits and hyphens.",
            "examples": [
              "intro-call"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Markdown, written by the host. Render it as Markdown, not as plain text."
          },
          "duration_minutes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 480,
            "description": "Length of every slot."
          },
          "booking_type": {
            "$ref": "#/components/schemas/BookingType"
          },
          "max_days_ahead": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many days ahead guests can book. Slots further out are not returned. null means no limit."
          },
          "buffer_time_minutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Gap the host keeps between consecutive slots."
          },
          "visibility": {
            "$ref": "#/components/schemas/Visibility"
          },
          "auto_generate_meet_link": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "When true, confirmed bookings get a Google Calendar event with a Google Meet link."
          },
          "is_active": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Inactive pages still appear in List booking pages, but can't be fetched, offer no slots and can't be booked."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "example": {
          "id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
          "slug": "intro-call",
          "title": "Intro call",
          "description": "A 30 minute call to see if we're a fit.\n\n- What you're building\n- Timeline and budget",
          "duration_minutes": 30,
          "booking_type": "direct",
          "max_days_ahead": 30,
          "buffer_time_minutes": 10,
          "visibility": "public",
          "auto_generate_meet_link": true,
          "is_active": true,
          "created_at": "2026-09-02T08:14:55.120+00:00"
        }
      },
      "BookingPage": {
        "description": "The full booking page row.",
        "allOf": [
          {
            "$ref": "#/components/schemas/BookingPageSummary"
          },
          {
            "type": "object",
            "required": [
              "user_id",
              "updated_at",
              "show_access_code_prompt",
              "show_on_profile"
            ],
            "properties": {
              "user_id": {
                "type": "string",
                "format": "uuid",
                "description": "The host's clndr.pro account id."
              },
              "updated_at": {
                "type": "string",
                "format": "date-time"
              },
              "availability_set_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "The availability schedule this page uses. null means all of the host's schedules."
              },
              "allowed_team_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "The team allowed to book a team page."
              },
              "show_access_code_prompt": {
                "type": "boolean",
                "description": "Whether the hosted page asks visitors of a private page for an access code."
              },
              "show_on_profile": {
                "type": "boolean",
                "description": "Whether the page is listed on the host's public profile at www.clndr.pro/{username}."
              }
            }
          }
        ],
        "example": {
          "id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
          "slug": "intro-call",
          "title": "Intro call",
          "description": "A 30 minute call to see if we're a fit.\n\n- What you're building\n- Timeline and budget",
          "duration_minutes": 30,
          "booking_type": "direct",
          "max_days_ahead": 30,
          "buffer_time_minutes": 10,
          "visibility": "public",
          "auto_generate_meet_link": true,
          "is_active": true,
          "created_at": "2026-09-02T08:14:55.120+00:00",
          "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
          "updated_at": "2026-09-30T11:02:10.004+00:00",
          "availability_set_id": "a1f0c2d3-4e5b-4c6d-8e7f-9a0b1c2d3e4f",
          "allowed_team_id": null,
          "show_access_code_prompt": true,
          "show_on_profile": true
        }
      },
      "UserProfile": {
        "type": "object",
        "description": "The host's public profile.",
        "required": [
          "username",
          "full_name",
          "bio",
          "avatar_url",
          "timezone"
        ],
        "properties": {
          "username": {
            "type": "string",
            "description": "3–30 characters: lowercase letters, digits, _ and -."
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "bio": {
            "type": [
              "string",
              "null"
            ]
          },
          "avatar_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone the host's availability is defined in.",
            "examples": [
              "Europe/London"
            ]
          }
        },
        "example": {
          "username": "ada",
          "full_name": "Ada Lovelace",
          "bio": "Building analytical engines.",
          "avatar_url": "https://www.clndr.pro/avatars/ada.png",
          "timezone": "Europe/London"
        }
      },
      "QuestionType": {
        "type": "string",
        "enum": [
          "text",
          "email",
          "textarea",
          "phone",
          "select",
          "checkbox"
        ],
        "description": "Which input to render. The dashboard creates text, email, textarea and phone; select comes with options."
      },
      "BookingQuestion": {
        "type": "object",
        "description": "A question the host asks every guest. Send the answers as responses when you create a booking.",
        "required": [
          "id",
          "question_text",
          "question_type",
          "options",
          "is_required",
          "order_index"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Send as responses[].questionId."
          },
          "question_text": {
            "type": "string"
          },
          "question_type": {
            "$ref": "#/components/schemas/QuestionType"
          },
          "options": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "Choices for a select question."
          },
          "is_required": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Collect an answer before you submit. The API doesn't reject bookings with missing answers, so enforce this in your form."
          },
          "order_index": {
            "type": "integer",
            "description": "Show questions in ascending order."
          }
        },
        "example": {
          "id": "0e4b7a2c-9d1f-4b3e-a6c8-5f2d1e9b7c40",
          "question_text": "What would you like to talk about?",
          "question_type": "textarea",
          "options": null,
          "is_required": true,
          "order_index": 0
        }
      },
      "BookingPageDetail": {
        "type": "object",
        "required": [
          "bookingPage",
          "userProfile",
          "questions"
        ],
        "properties": {
          "bookingPage": {
            "$ref": "#/components/schemas/BookingPage"
          },
          "userProfile": {
            "$ref": "#/components/schemas/UserProfile"
          },
          "questions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BookingQuestion"
            },
            "description": "Active questions, sorted by order_index."
          }
        },
        "example": {
          "bookingPage": {
            "id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
            "slug": "intro-call",
            "title": "Intro call",
            "description": "A 30 minute call to see if we're a fit.\n\n- What you're building\n- Timeline and budget",
            "duration_minutes": 30,
            "booking_type": "direct",
            "max_days_ahead": 30,
            "buffer_time_minutes": 10,
            "visibility": "public",
            "auto_generate_meet_link": true,
            "is_active": true,
            "created_at": "2026-09-02T08:14:55.120+00:00",
            "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
            "updated_at": "2026-09-30T11:02:10.004+00:00",
            "availability_set_id": "a1f0c2d3-4e5b-4c6d-8e7f-9a0b1c2d3e4f",
            "allowed_team_id": null,
            "show_access_code_prompt": true,
            "show_on_profile": true
          },
          "userProfile": {
            "username": "ada",
            "full_name": "Ada Lovelace",
            "bio": "Building analytical engines.",
            "avatar_url": "https://www.clndr.pro/avatars/ada.png",
            "timezone": "Europe/London"
          },
          "questions": [
            {
              "id": "0e4b7a2c-9d1f-4b3e-a6c8-5f2d1e9b7c40",
              "question_text": "What would you like to talk about?",
              "question_type": "textarea",
              "options": null,
              "is_required": true,
              "order_index": 0
            },
            {
              "id": "6f1c3e8a-2d7b-4a9e-b5c1-8e0d4f2a6b93",
              "question_text": "Phone number",
              "question_type": "phone",
              "options": null,
              "is_required": false,
              "order_index": 1
            }
          ]
        }
      },
      "TimeSlot": {
        "type": "object",
        "description": "An open slot. Book it by sending start and end unchanged as startTime and endTime.",
        "required": [
          "start",
          "end"
        ],
        "properties": {
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "UTC, with milliseconds."
          },
          "end": {
            "type": "string",
            "format": "date-time",
            "description": "start plus the page's duration_minutes."
          }
        },
        "example": {
          "start": "2026-10-15T08:00:00.000Z",
          "end": "2026-10-15T08:30:00.000Z"
        }
      },
      "BookingStatus": {
        "type": "string",
        "enum": [
          "pending",
          "confirmed",
          "cancelled",
          "completed"
        ],
        "description": "pending is waiting for the host's approval. completed is reserved and not set today."
      },
      "SyncStatus": {
        "type": "string",
        "enum": [
          "not_synced",
          "pending",
          "synced",
          "failed"
        ],
        "description": "Whether the booking's Google Calendar event was created, as recorded when the guest booked or the host acted in the dashboard. Confirming or cancelling through this API doesn't update it, so check google_calendar_event_id and google_meet_link after a PATCH or DELETE. not_synced means no event was needed when it was last recorded (no Meet links on the page, or not confirmed yet)."
      },
      "Booking": {
        "type": "object",
        "description": "A booking (a meeting on the host's calendar).",
        "required": [
          "id",
          "booking_page_id",
          "user_id",
          "guest_name",
          "guest_email",
          "start_time",
          "end_time",
          "status",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "booking_page_id": {
            "type": "string",
            "format": "uuid"
          },
          "user_id": {
            "type": "string",
            "format": "uuid",
            "description": "The host."
          },
          "guest_name": {
            "type": "string"
          },
          "guest_email": {
            "type": "string",
            "format": "email"
          },
          "start_time": {
            "type": "string",
            "format": "date-time"
          },
          "end_time": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "$ref": "#/components/schemas/BookingStatus"
          },
          "cancellation_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "reminder_sent": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "google_meet_link": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Set once a Google Meet link exists: at booking time for direct pages with Meet links on, at confirmation for approval pages."
          },
          "google_calendar_event_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "google_calendar_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "sync_status": {
            "$ref": "#/components/schemas/SyncStatus"
          },
          "last_synced_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "sync_failure_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the Google Calendar event couldn't be created, for example Google Calendar is not connected."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "example": {
          "id": "c3a7e5d1-2b9f-4c6e-8a1d-7f4b0e2c9a58",
          "booking_page_id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
          "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
          "guest_name": "Grace Hopper",
          "guest_email": "grace@example.com",
          "start_time": "2026-10-15T08:40:00+00:00",
          "end_time": "2026-10-15T09:10:00+00:00",
          "status": "confirmed",
          "cancellation_reason": null,
          "reminder_sent": false,
          "google_meet_link": "https://meet.google.com/abc-defg-hij",
          "google_calendar_event_id": "6v1m0r2k8f3t5q9b7n4c",
          "google_calendar_id": "primary",
          "sync_status": "synced",
          "last_synced_at": "2026-10-10T12:31:07.481+00:00",
          "sync_failure_reason": null,
          "created_at": "2026-10-10T12:31:05.902+00:00",
          "updated_at": "2026-10-10T12:31:07.481+00:00"
        }
      },
      "BookingResponse": {
        "type": "object",
        "required": [
          "question_id",
          "answer"
        ],
        "properties": {
          "question_id": {
            "type": "string",
            "format": "uuid"
          },
          "answer": {
            "type": "string"
          }
        },
        "example": {
          "question_id": "0e4b7a2c-9d1f-4b3e-a6c8-5f2d1e9b7c40",
          "answer": "Moving our sales demos onto clndr.pro."
        }
      },
      "BookingWithResponses": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Booking"
          },
          {
            "type": "object",
            "required": [
              "meeting_responses"
            ],
            "properties": {
              "meeting_responses": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BookingResponse"
                },
                "description": "The guest's answers to the page's questions."
              }
            }
          }
        ],
        "example": {
          "id": "c3a7e5d1-2b9f-4c6e-8a1d-7f4b0e2c9a58",
          "booking_page_id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
          "user_id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
          "guest_name": "Grace Hopper",
          "guest_email": "grace@example.com",
          "start_time": "2026-10-15T08:40:00+00:00",
          "end_time": "2026-10-15T09:10:00+00:00",
          "status": "confirmed",
          "cancellation_reason": null,
          "reminder_sent": false,
          "google_meet_link": "https://meet.google.com/abc-defg-hij",
          "google_calendar_event_id": "6v1m0r2k8f3t5q9b7n4c",
          "google_calendar_id": "primary",
          "sync_status": "synced",
          "last_synced_at": "2026-10-10T12:31:07.481+00:00",
          "sync_failure_reason": null,
          "created_at": "2026-10-10T12:31:05.902+00:00",
          "updated_at": "2026-10-10T12:31:07.481+00:00",
          "meeting_responses": [
            {
              "question_id": "0e4b7a2c-9d1f-4b3e-a6c8-5f2d1e9b7c40",
              "answer": "Moving our sales demos onto clndr.pro."
            }
          ]
        }
      },
      "CreateBookingRequest": {
        "type": "object",
        "required": [
          "bookingPageId",
          "guestName",
          "guestEmail",
          "startTime",
          "endTime"
        ],
        "properties": {
          "bookingPageId": {
            "type": "string",
            "format": "uuid",
            "description": "id of one of your booking pages."
          },
          "guestName": {
            "type": "string"
          },
          "guestEmail": {
            "type": "string",
            "format": "email",
            "description": "Confirmation emails go here."
          },
          "startTime": {
            "type": "string",
            "format": "date-time",
            "description": "A slot's start, unchanged."
          },
          "endTime": {
            "type": "string",
            "format": "date-time",
            "description": "The same slot's end."
          },
          "responses": {
            "type": "array",
            "description": "Answers to the page's questions. Leave out questions the guest skipped. Every questionId must belong to this page: one unknown id and none of the answers are saved.",
            "items": {
              "type": "object",
              "required": [
                "questionId",
                "answer"
              ],
              "properties": {
                "questionId": {
                  "type": "string",
                  "format": "uuid"
                },
                "answer": {
                  "type": "string"
                }
              }
            }
          }
        },
        "example": {
          "bookingPageId": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
          "guestName": "Grace Hopper",
          "guestEmail": "grace@example.com",
          "startTime": "2026-10-15T08:40:00.000Z",
          "endTime": "2026-10-15T09:10:00.000Z",
          "responses": [
            {
              "questionId": "0e4b7a2c-9d1f-4b3e-a6c8-5f2d1e9b7c40",
              "answer": "Moving our sales demos onto clndr.pro."
            }
          ]
        }
      },
      "UpdateBookingRequest": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "confirmed",
              "cancelled"
            ]
          },
          "reason": {
            "type": "string",
            "description": "Stored as cancellation_reason. Sending a status without a reason clears any earlier one."
          }
        },
        "example": {
          "status": "confirmed"
        }
      },
      "PublicProfile": {
        "type": "object",
        "required": [
          "id",
          "username",
          "full_name",
          "bio",
          "avatar_url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "username": {
            "type": "string"
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "bio": {
            "type": [
              "string",
              "null"
            ]
          },
          "avatar_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          }
        },
        "example": {
          "id": "5d2e9f40-7c1b-4e8a-b0d6-3a9f1c2e8d77",
          "username": "ada",
          "full_name": "Ada Lovelace",
          "bio": "Building analytical engines.",
          "avatar_url": null
        }
      },
      "PublicBookingPage": {
        "type": "object",
        "required": [
          "id",
          "title",
          "slug",
          "description",
          "duration_minutes"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_minutes": {
            "type": "integer"
          }
        },
        "example": {
          "id": "8b6f2c1e-4a1d-4f0b-9a63-2f0c5d7e9b14",
          "title": "Intro call",
          "slug": "intro-call",
          "description": null,
          "duration_minutes": 30
        }
      }
    }
  }
}
