Data 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. 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, so you can copy these into your project when you need the extra fields.
Enums
/** `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.
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.
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.
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 is read in.
BookingQuestion
A question the host asks every guest.
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 returns this. It's everything a booking form needs.
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.
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, List bookings and Confirm or cancel a booking.
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 adds the guest's answers.
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:
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.
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.
export interface UpdateBookingInput {
status: 'confirmed' | 'cancelled';
/** Stored as `cancellation_reason`. Omitting it clears any earlier reason. */
reason?: string;
}ApiError
Every non-2xx response body.
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 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 | Used for both list() and get(). On get(), the extra 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 | is_required typed as boolean. |
UserProfile | UserProfile | timezone typed as string | null. |
BookingPageDetail | BookingPageDetail | Same shape. |
TimeSlot | TimeSlot | Same. |
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 | Same. |
ClndrError | ApiError | A class: message, status, data. |
If you'd rather generate types than copy them, the OpenAPI spec is at /openapi.json:
npx openapi-typescript https://docs.clndr.pro/openapi.json -o types/clndr-api.d.tsThat gives you components['schemas']['Booking'] and friends, plus typed paths for clients like openapi-fetch.