# Data types

Every object the clndr.pro API returns or accepts, as TypeScript interfaces, with enum values, nullability, and how they map to the SDK's exported types.

Response objects are database rows, so their fields are snake_case (`duration_minutes`, `guest_email`). Request bodies are camelCase (`bookingPageId`, `guestEmail`). Every success response wraps its payload in `data`; every error is an [`ApiError`](#apierror). Timestamps are ISO 8601 strings in UTC.

The interfaces below describe the full API responses. `@clndr-pro/sdk` exports narrower versions of some of them, listed in [What the SDK types cover](#what-the-sdk-types-cover), so you can copy these into your project when you need the extra fields.

## Enums

```ts title="types/clndr.ts"
/** `direct`: confirmed on the spot. `approval`: `pending` until the host decides. */
export type BookingType = 'direct' | 'approval';

/**
 * Who can open the hosted page at www.clndr.pro/{username}/{slug}.
 * API keys see all of their owner's pages regardless.
 */
export type Visibility = 'public' | 'private' | 'team';

/** The dashboard creates text, email, textarea and phone; select comes with `options`. */
export type QuestionType = 'text' | 'email' | 'textarea' | 'phone' | 'select' | 'checkbox';

/** `completed` is reserved; nothing sets it today. */
export type BookingStatus = 'pending' | 'confirmed' | 'cancelled' | 'completed';

/**
 * Whether the booking's Google Calendar event was created, as recorded when
 * the guest booked or the host acted in the dashboard. PATCH/DELETE through
 * the API don't update it: check google_calendar_event_id after those.
 */
export type SyncStatus = 'not_synced' | 'pending' | 'synced' | 'failed';
```

## BookingPageSummary

An item of [List booking pages](/api-reference/operations/listBookingPages).

```ts title="types/clndr.ts"
export interface BookingPageSummary {
  /** Send as `bookingPageId` when you create a booking. */
  id: string;
  /** Unique per account: lowercase letters, digits and hyphens. */
  slug: string;
  title: string;
  /** Markdown written by the host. Render it as Markdown. */
  description: string | null;
  /** Length of every slot, 1–480. */
  duration_minutes: number;
  booking_type: BookingType;
  /** How far ahead guests can book. `null` = no limit. */
  max_days_ahead: number | null;
  /** Gap between consecutive slots. */
  buffer_time_minutes: number | null;
  visibility: Visibility;
  /** Confirmed bookings get a Google Calendar event with a Meet link. */
  auto_generate_meet_link: boolean | null;
  /** Inactive pages are listed but can't be read, offer no slots and can't be booked. */
  is_active: boolean | null;
  created_at: string;
}
```

## BookingPage

The full row returned inside [`BookingPageDetail`](#bookingpagedetail).

```ts title="types/clndr.ts"
export interface BookingPage extends BookingPageSummary {
  /** The host's clndr.pro account id. */
  user_id: string;
  updated_at: string;
  /** The availability schedule the page uses. `null` = all of the host's schedules. */
  availability_set_id: string | null;
  /** The team allowed to book a `team` page. */
  allowed_team_id: string | null;
  /** Whether the hosted page asks visitors of a private page for an access code. */
  show_access_code_prompt: boolean;
  /** Whether the page is listed on www.clndr.pro/{username}. */
  show_on_profile: boolean;
}
```

## UserProfile

The host's public profile.

```ts title="types/clndr.ts"
export interface UserProfile {
  /** 3–30 characters: lowercase letters, digits, `_` and `-`. */
  username: string;
  full_name: string | null;
  bio: string | null;
  avatar_url: string | null;
  /** IANA timezone the host's availability is defined in, e.g. "Europe/London". */
  timezone: string;
}
```

`timezone` is also the timezone the `date` parameter of [List open slots](/api-reference/operations/listSlots) is read in.

## BookingQuestion

A question the host asks every guest.

```ts title="types/clndr.ts"
export interface BookingQuestion {
  /** Send as `responses[].questionId`. */
  id: string;
  question_text: string;
  question_type: QuestionType;
  /** Choices for a `select` question; `null` otherwise. */
  options: string[] | null;
  /** Collect an answer before submitting. The API doesn't enforce it. */
  is_required: boolean | null;
  /** Show questions in ascending order. */
  order_index: number;
}
```

## BookingPageDetail

[Get a booking page](/api-reference/operations/getBookingPage) returns this. It's everything a booking form needs.

```ts title="types/clndr.ts"
export interface BookingPageDetail {
  bookingPage: BookingPage;
  userProfile: UserProfile;
  /** Active questions, sorted by `order_index`. */
  questions: BookingQuestion[];
}
```

The three keys are camelCase because they're the API's own envelope; the objects inside are rows, so their fields are snake_case.

## TimeSlot

An item of [List open slots](/api-reference/operations/listSlots).

```ts title="types/clndr.ts"
export interface TimeSlot {
  /** UTC with milliseconds, e.g. "2026-10-15T08:40:00.000Z". */
  start: string;
  /** `start` plus the page's `duration_minutes`. */
  end: string;
}
```

Book a slot by sending `start` and `end` back unchanged as `startTime` and `endTime`.

## Booking

A meeting on the host's calendar, as returned by [Create a booking](/api-reference/operations/createBooking), [List bookings](/api-reference/operations/listBookings) and [Confirm or cancel a booking](/api-reference/operations/updateBooking).

```ts title="types/clndr.ts"
export interface Booking {
  id: string;
  booking_page_id: string;
  /** The host. */
  user_id: string;
  guest_name: string;
  guest_email: string;
  /** Postgres timestamps, e.g. "2026-10-15T08:40:00+00:00". Parse with `new Date()`. */
  start_time: string;
  end_time: string;
  status: BookingStatus;
  cancellation_reason: string | null;
  reminder_sent: boolean | null;
  /** Set once a Meet link exists: at booking time on direct pages, at confirmation on approval pages. */
  google_meet_link: string | null;
  google_calendar_event_id: string | null;
  google_calendar_id: string | null;
  /** Not updated by PATCH/DELETE through the API; see SyncStatus. */
  sync_status: SyncStatus;
  last_synced_at: string | null;
  /** Why the Google event couldn't be created, e.g. "Google Calendar is not connected". */
  sync_failure_reason: string | null;
  created_at: string;
  /** Changes on every status change: use it to detect updates when you sync. */
  updated_at: string;
}
```

`start_time`, `created_at` and the other row timestamps arrive in Postgres's format (`+00:00`) while slot times use `Z` with milliseconds. They're the same instant: compare with `Date.parse()`, never as strings.

## BookingWithResponses

[Get a booking](/api-reference/operations/getBooking) adds the guest's answers.

```ts title="types/clndr.ts"
export interface BookingResponse {
  question_id: string;
  answer: string;
}

export interface BookingWithResponses extends Booking {
  meeting_responses: BookingResponse[];
}
```

The SDK's `bookings.get()` is typed as returning `Booking`, so cast the result to read the answers:

```ts
import type { BookingWithResponses } from '@/types/clndr';

const booking = (await clndr.bookings.get(id)) as unknown as BookingWithResponses;
for (const r of booking.meeting_responses) console.log(r.question_id, r.answer);
```

Match `question_id` against the page's `questions[].id` to show the question text next to each answer.

## CreateBookingInput

The body of [Create a booking](/api-reference/operations/createBooking).

```ts title="types/clndr.ts"
export interface CreateBookingInput {
  /** `id` of one of your booking pages. */
  bookingPageId: string;
  guestName: string;
  /** Confirmation emails go here. */
  guestEmail: string;
  /** A slot's `start`, unchanged. */
  startTime: string;
  /** The same slot's `end`. */
  endTime: string;
  /**
   * Answers, one per answered question. Leave out skipped questions. Every
   * `questionId` must belong to this page: one unknown id and none are saved.
   */
  responses?: Array<{ questionId: string; answer: string }>;
}
```

Answers are always strings. For a `checkbox` question send something readable like `"Yes"`; for `select`, send the chosen option.

## UpdateBookingInput

The body of [Confirm or cancel a booking](/api-reference/operations/updateBooking).

```ts title="types/clndr.ts"
export interface UpdateBookingInput {
  status: 'confirmed' | 'cancelled';
  /** Stored as `cancellation_reason`. Omitting it clears any earlier reason. */
  reason?: string;
}
```

## ApiError

Every non-2xx response body.

```ts title="types/clndr.ts"
export interface ApiError {
  /** A sentence you can log, e.g. "Booking page not found". */
  error: string;
}
```

The SDK throws it as a `ClndrError` with `message` set to `error`, `status` set to the HTTP status and `data` set to this body. [Errors and rate limits](/errors) lists the messages.

## What the SDK types cover

`@clndr-pro/sdk` (and `@clndr-pro/react`, which re-exports them) ships these types:

| SDK type             | Matches                                     | Differences                                                                                                                                                                                                                                        |
| -------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BookingPage`        | [`BookingPageSummary`](#bookingpagesummary) | Used for both `list()` and `get()`. On `get()`, the extra [`BookingPage`](#bookingpage) fields are present at runtime but not typed. `max_days_ahead`, `buffer_time_minutes`, `auto_generate_meet_link` and `is_active` are typed as never `null`. |
| `BookingQuestion`    | [`BookingQuestion`](#bookingquestion)       | `is_required` typed as `boolean`.                                                                                                                                                                                                                  |
| `UserProfile`        | [`UserProfile`](#userprofile)               | `timezone` typed as `string \| null`.                                                                                                                                                                                                              |
| `BookingPageDetail`  | [`BookingPageDetail`](#bookingpagedetail)   | Same shape.                                                                                                                                                                                                                                        |
| `TimeSlot`           | [`TimeSlot`](#timeslot)                     | Same.                                                                                                                                                                                                                                              |
| `Booking`            | [`Booking`](#booking)                       | Declares `id`, `booking_page_id`, `user_id`, `guest_name`, `guest_email`, `start_time`, `end_time`, `status` (without `completed`), `google_meet_link` and `created_at`. The other fields arrive but aren't typed.                                 |
| `CreateBookingInput` | [`CreateBookingInput`](#createbookinginput) | Same.                                                                                                                                                                                                                                              |
| `ClndrError`         | [`ApiError`](#apierror)                     | A class: `message`, `status`, `data`.                                                                                                                                                                                                              |

If you'd rather generate types than copy them, the OpenAPI spec is at [`/openapi.json`](/openapi.json):

```bash
npx openapi-typescript https://docs.clndr.pro/openapi.json -o types/clndr-api.d.ts
```

That gives you `components['schemas']['Booking']` and friends, plus typed paths for clients like `openapi-fetch`.
