# Bookings

> A booking workspace over bookingsV2 — the six sections, where availability comes from, and the time-zone rule the schema does not carry.

Page: https://sdk.chatfuel.com/docs/modules/bookings
Markdown: https://sdk.chatfuel.com/docs/modules/bookings.md

`bookingsV2(startTime, endTime)` takes a window and nothing else — no cursor, no filter, no
sort, no search, no count. Every section of this module is built on that: it asks for exactly
the days it draws, filters and sorts in the browser, and says how much it is holding rather
than pretending to page.

Six sections over one booking list: a calendar you drag appointments around, an appointments
table, staff with weekly hours and Google Calendar sync, the services catalog, the AI booking
settings and the bot time zone, and insights over the loaded window.

## Installed as [#installed-as]

|            |                               |
| ---------- | ----------------------------- |
| Wizard id  | `bookings`                    |
| Flag       | `--modules bookings`          |
| Selection  | Default — `--yes` installs it |
| Requires   | —                             |
| Recommends | `knowledge-base`, `contacts`  |
| Skill      | `chatfuel-bookings`           |

## Routes and views [#routes-and-views]

The section is a path segment; everything else is a query parameter. `calendar` is
`DEFAULT_VIEW`, so it has no segment of its own.

| Path                     | What it shows                                                                                                                    |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `/bookings`              | Calendar — day, week or month, columns by day or by specialist, drag to move, drag an edge to resize, drag empty grid to create. |
| `/bookings/appointments` | Upcoming, past or a custom range as a table, with bulk status changes and CSV export.                                            |
| `/bookings/staff`        | Specialists: weekly working hours, the services they offer, and their Google Calendar connection.                                |
| `/bookings/services`     | The catalog — duration, price, availability, images.                                                                             |
| `/bookings/settings`     | Notification channel, confirmation, the 24 h / 2 h notices, language, the bot time zone. AI autonomy is read here, not set.      |
| `/bookings/insights`     | Status mix, no-show and cancel rates, attended revenue per currency, utilisation per specialist, busiest weekdays and hours.     |

The parameters the module owns:

| Parameter                         | Value                                                                                                         |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `b`                               | The open booking's id. A drawer below 1280px, an inline second column above it.                               |
| `specialist`, `service`, `status` | Comma lists. Shared by the calendar, the appointments list and insights.                                      |
| `mode`                            | `day`, `week` (the default) or `month`.                                                                       |
| `date`                            | Anchor day, `YYYY-MM-DD`. Absent means today.                                                                 |
| `by`                              | `time` (the default) or `specialist` — what a column is.                                                      |
| `color`                           | `specialist` (the default) or `status`.                                                                       |
| `range`                           | `upcoming` (the default), `past` or `custom`.                                                                 |
| `from`, `to`                      | The custom range, and the custom insights period.                                                             |
| `q`                               | Search over the loaded rows.                                                                                  |
| `sort`                            | `<column>:asc` or `:desc`.                                                                                    |
| `period`                          | `week`, `month`, `30d` (the default), `90d` or `custom`.                                                      |
| `s`                               | A specialist id, or `new`, on the Staff section.                                                              |
| `density`                         | `comfortable` (the default) or `compact`.                                                                     |
| `new`                             | `1` opens the booking wizard, prefilled by `start`, `end`, `contact`, `ns` (specialist) and `nsvc` (service). |

`?new=1&contact=<id>` is the deep link a "Book" button in Live Chat uses: the wizard opens on
the customer step with that contact chosen. A `?week=` from an older link is still read, as
`mode=week&date=`, and never written back. Defaults are omitted from what is written, and an
unrecognised value anywhere renders the default rather than throwing.

## The model [#the-model]

An appointment is a `BookingBase` in `bot.bookingsV2`, and queries and mutations answer with
that interface while subscriptions answer with the concrete type — so a selection always goes
through `... on Booking` / `... on BookingWithGoogleCalendarRef`. Deleted services and
specialists still arrive, as `DeletedGoodsService` and `DeletedSpecialist`, keeping enough of
themselves to render a past appointment.

Six statuses, in the order the whole module uses: Pending, Confirmed, Attended, No-show,
Reschedule, Canceled. Everything but Canceled occupies its slot. &#x2A;*Only five of them are
targets.** A booking is born Pending and can never return to it: `→ Pending` answers
`InternalServerError` from every state, including Pending → Pending, so the app never offers
it.

Availability is a separate call, not a field on the calendar.
`GoodsService.bookingAvailableStartTime(botID, date)` answers one service × one day, per
specialist, with **start-time ranges whose end is inclusive** — a 30-minute service on a
09:00–18:00 day answers `09:00–17:30`. A start `s` is bookable when `start ≤ s ≤ end`; slicing
it with the usual `s + duration ≤ end` silently drops the last slot of every period. Existing
bookings are already subtracted, Canceled and deleted ones free the slot, and the module
offers a start every 15 minutes inside what comes back.

Which leaves the fact that decides every timestamp you send:

<Callout type="warn">
  A `Time` with a **zero offset** is read as the bot's wall clock, not as UTC.
  `2026-08-21T13:00:00Z` was stored and echoed as `13:00:00-06:00` on a bot in
  `America/Mexico_City`. A non-zero offset is honoured as an instant. So `toISOString()` puts
  a booking at the wrong hour and nothing errors — the record round-trips, and only a
  comparison against the specialist's schedule shows it. Format every instant with the bot
  zone's real offset (`lib/zone.ts`, `toZoneIso`), range bounds included. Schedules and
  availability are `HH:mm` in that same zone, which is why slots stay in the bot's zone even
  when you view in your own.
</Callout>

Specialists are `bot.specialists`, unpaginated. A schedule is seven named days with one
optional break each, `HH:mm` in the bot zone, and availability is computed from that schedule
— not from the knowledge base's business hours, which are a different, unrelated shape.
`specialistCreate` and `specialistUpdate` replace profile, schedule and services together.

## Configuration [#configuration]

| Variable                     | Notes                                     |
| ---------------------------- | ----------------------------------------- |
| `CHATFUEL_TOKEN`             | Secret. Server-side only.                 |
| `VITE_CHATFUEL_WORKSPACE_ID` | The wizard's workspace step writes it.    |
| `CHATFUEL_API_BASE`          | Defaults to `https://panel.chatfuel.com`. |

Nothing here is specific to this module. [Environment variables](/docs/deploy/environments)
has what each one is and which side reads it.

## Permissions [#permissions]

| Object | Action | Required for                                                               |
| ------ | ------ | -------------------------------------------------------------------------- |
| People | View   | Reading the calendar and appointments                                      |
| People | Edit   | Creating, moving, resolving and deleting bookings                          |
| Ai     | Edit   | Managing staff and services, the AI booking settings and the bot time zone |

## Limits [#limits]

**There is no server-side filter, search, sort, count or pagination over bookings.** Filters,
search and sort all run over the loaded window, which is why the header says `12 of 40` and
the search box says "Search loaded rows…". To see more, the list asks for a bigger window:
Upcoming grows 90 days forward, Past 30 days backward, and every window is capped at 366 days.

**There is no aggregation in the booking API.** Every insights figure is a fold over the
loaded rows and every card prints its coverage. Four things are absent from the data rather
than unbuilt: lead time (a booking carries no `createdAt`), trends across more than one
window, anything server-aggregated, and revenue as one number across currencies.

**`bookingUpdateV2` is a full replace, and there is no patch.** There is no status on the
inputs and no dedicated cancel or reschedule mutation either — Canceled and Reschedule are
status values, moved through `bookingStatusResolveV2`, and a reschedule is an update of the
times. Delete has no counterpart: there is no restore mutation, so delete asks first and undo
does not cover it.

**The API validates nothing on the calendar.** It accepted overlapping bookings for one
specialist, bookings outside working hours, bookings in the past, a 24-hour booking, and a
booking with neither service nor specialist. Only the wizard, going through availability, is
opinionated — it warns, and the panel does not stop you.

**Availability needs a service, and answers one day at a time.** There is no availability
without a service and none for a range of days. A specialist with no working hours answers
`hasSchedule: false`, always empty — their existing bookings still show on the calendar and an
operator can still book them by hand. A schedule allows exactly one break per day, because the
input has one.

**Only WhatsApp contacts can be booked** — anything else is
`BookingContactPlatformNotAllowed`. On a WhatsApp-connected bot the API turns inline-contact
input into a real `WhatsappContact`, so the same phone booked twice yields one real contact
rather than an inline one.

**Two things this module cannot finish.** AI autonomy is read on the Settings page and
changed in the automations module — the legacy setter is deprecated and answers
`BotMigratedToNewFuelySettings` on a migrated bot. And Google's sign-in happens on Chatfuel's
own page: this app creates the connection link, shows who created it, starts the sync and
follows the task, but the OAuth round trip needs credentials it does not have.

**The only reminders are bot-wide.** The 2-hour and 24-hour appointment notices in Settings
are the whole set — there is no per-booking reminder and no recurring booking in the API.
`calendarLandingURL`, the hosted booking page, is read-only and an experiment: null outside
it.

The module's own ceilings:

|                                     |                                           |
| ----------------------------------- | ----------------------------------------- |
| Offered start times, every          | 15 minutes                                |
| Calendar snap, and a keyboard nudge | 15 minutes                                |
| Shortest booking a drag can make    | 15 minutes                                |
| Booking duration in the panel       | 5 minutes to 24 hours                     |
| Upcoming window grows by            | 90 days                                   |
| Past window grows by                | 30 days                                   |
| Any window                          | 366 days                                  |
| Undo window                         | 60 seconds, one entry, never into Pending |
| Default service duration            | 30 minutes                                |
| Service title / description         | 120 / 1000 characters                     |
| Images on a service                 | 10                                        |
| Specialist first and last name      | 60 characters each                        |
| About a specialist                  | 1000 characters                           |

<Cards>
  <Card title="Bookings, as a product" href="/modules/bookings" description="The calendar, the staff hours that decide what can be booked, and how far the AI books on its own." />

  <Card title="Knowledge base" href="/docs/modules/knowledge-base" description="Recommended alongside this one — it shows the same services and specialists from the AI's side." />

  <Card title="Contacts" href="/docs/modules/contacts" description="Recommended alongside this one — the customer on a booking is a contact." />

  <Card title="Your agent's notes" href="/docs/concepts/agent-skills" description="The wizard installs .claude/skills/chatfuel-bookings/ (Codex: .agents/skills/chatfuel-bookings/) — the time-zone rule, availability semantics, the statuses and the traps verified live." />
</Cards>
