> ## Documentation Index
> Fetch the complete documentation index at: https://docs.liteshop.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Bookings

> Declare inventory booked by the night, by the day or by timed slot, and accept nonblocking Booking Requests for Owner review.

Liteshop projects booking Resources and Offers from the active Site Bundle into
the Owner dashboard. Visitors can submit write-only Booking Requests for
declared Offers; the Owner explicitly confirms, declines, or cancels them in
the Bookings dashboard. A new request also queues a retryable email notification
to the Owner with a private dashboard link; the email contains no Guest details.
On the **Resources** page, the Owner can click **Edit in ChatGPT** to change
Resources and Offers through the usual Draft, preview, and Publish approval
steps.

> [!WARNING]
>
> `liteshop.bookings.request` is nonblocking; an accepted request is **not** a
> confirmed Booking. Confirmed Bookings, active Manual Blocks, and successfully
> imported Calendar Events block mapped Resources.
> `liteshop.bookings.getAvailability` shows which dates or slots can be
> requested, but it doesn't report whether imported feeds are fresh: present
> them as open for requests, never as reserved. Only add a Visitor request form
> when the Owner explicitly requests it.
> For Sites with imported calendars, the Owner must configure the declared
> connections before confirmation can succeed. Confirmation refreshes relevant
> feeds, then atomically checks occupancy; a failed refresh leaves the request
> pending. A Visitor request is never an instant reservation.

## Calendar Connections

To add an import, in the dashboard, click **Calendars** > **Connect calendar**.
Enter a calendar name, paste the calendar's private iCal link, select every Resource its events occupy, then choose **Connect and
publish calendar**. This one explicit Owner action validates and Publishes
the connection declaration in the Site Bundle, saves the URL privately
outside that bundle, and queues the first refresh. Check **Last sync**
before confirming a Booking Request. If your Site has no booking Resources
yet, declare Resources and Offers first.

The Site Bundle contains each connection's stable ID, direction, and
Resource mapping, but **never** its feed URL or export token. A connection
created through the dashboard has this shape in `calendarConnections`
(Liteshop generates its stable ID):

```json theme={null}
{
  "id": "calendar-123e4567-e89b-42d3-a456-426614174000",
  "name": { "en": "Dune calendar" },
  "provider": "ical",
  "mode": "import",
  "resources": ["dune-cottage"],
  "configuredIn": "dashboard"
}
```

The dashboard also accepts a private URL for an already-declared `import`
or `bidirectional` slot, and shows failures and a **Sync now**
action. If the declaration Publishes but saving the URL fails, use that
slot's **Add link** form to retry; confirmation stays blocked until
a fresh import succeeds. A failed import preserves the last known events but
doesn't permit confirmation.

Imports accept Booking.com and Airbnb stay calendars, a Google Calendar
secret address in iCal format, and an iCloud published calendar. An event
with a time blocks exactly that time, so an appointment leaves the rest of
the day bookable. An all-day event blocks whole days from a personal
calendar, and check-in to check-out from a rental platform. Events marked
free, such as birthdays, block nothing.

For an `export` or `bidirectional` slot, the Owner can generate a private
export link to subscribe from another calendar. The link is shown once;
regenerating it immediately revokes the previous link. Exports include
confirmed Bookings and active Manual Blocks whose Resources intersect the
connection's mapping, without Guest contact or private notes. Bookings and
blocks of hours export as timed events, including any buffer before and
after; nightly stays and whole days export as all-day events. Keep that
link private, just like a password.

## Manual Blocks

In the Owner's Bookings dashboard, select one or more active Resources, what to
block, and an optional private note to create a Manual Block. An active block
prevents a conflicting Booking confirmation and makes the time it covers
occupied for any Offer that consumes its Resources.

A block covers either nights or hours, in the Site timezone:

* **Nights** take an arrival-inclusive and departure-exclusive date range and
  occupy 16:00 on the first date to 11:00 on the end date. They can cover
  multi-year closures; the 366-date limit applies to one availability *read*,
  not to a block.
* **Hours** take a start and end time, up to 366 days apart, such as a morning
  off or whole days from midnight to midnight.

Creating a block is rejected if a confirmed Booking or another active Manual
Block overlaps on a shared Resource. Deactivating releases its dates while
preserving the record and note for the Owner. Blocks are Operational Data, so
Publish and Rollback don't remove them. Their private notes, source, and
identifiers are never returned by public availability. Manual Blocks aren't
declared in the Site Bundle. With the optional bookings permission, the
[ChatGPT connector](https://docs.liteshop.me/guides/chatgpt.md) can also create and remove them.

## Inventory model

A Resource is one atomic piece of inventory. Its optional `quantity` (default

1. says how many Bookings it holds at the same time, such as five identical
   kayaks or a team where anyone can take a call. An Offer is the Visitor-facing
   option and consumes one or more Resources. Its `unit` says how it is booked:
   `nightly` stays (the default), `daily` whole days, or `timed` fixed-length
   slots.

For example, two cottages can be booked separately while a whole-property Offer
consumes both:

```json theme={null}
{
  "capabilities": {
    "bookings": {
      "resources": [
        {
          "id": "dune-cottage",
          "name": { "en": "Dune Cottage" }
        },
        {
          "id": "pine-cottage",
          "name": { "en": "Pine Cottage" }
        }
      ],
      "offers": [
        {
          "id": "dune-cottage-stay",
          "name": { "en": "Dune Cottage stay" },
          "consumes": ["dune-cottage"],
          "minimumNights": 2,
          "maximumGuests": 2,
          "minimumAdvanceDays": 1,
          "maximumAdvanceDays": 365
        },
        {
          "id": "whole-property",
          "name": { "en": "Whole property" },
          "consumes": ["dune-cottage", "pine-cottage"],
          "minimumNights": 3,
          "maximumGuests": 6
        }
      ],
      "requestFields": [
        {
          "id": "name",
          "type": "text",
          "label": { "en": "Name" },
          "required": true,
          "maxLength": 120
        },
        {
          "id": "email",
          "type": "email",
          "label": { "en": "Email" },
          "required": true,
          "maxLength": 200
        }
      ],
      "calendarConnections": []
    }
  }
}
```

`requestFields` declares the accepted contact keys. Required fields must be
submitted, and undeclared fields are rejected. In bundled JavaScript loaded
after `<script src="/_liteshop/sdk/v1.js"></script>`, submit a Visitor request
using the same-origin SDK:

```js theme={null}
await liteshop.bookings.request({
  offerId: "dune-cottage-stay",
  arrival: "2027-06-01",
  departure: "2027-06-05",
  guests: 2,
  contact: { name: "Guest Name", email: "guest@example.com" },
  locale: document.documentElement.lang || "en"
});
```

Replace the example values with the Visitor's validated form values. Prevent
the native form submit, show pending and error states, and present success as
“Request sent; the Owner will review it,” never as a reservation. Arrival is
inclusive, departure exclusive. Liteshop checks the Offer's minimum nights,
guest limit, advance window in the Site timezone, declared contact fields,
and current confirmed-Booking and active Manual Block occupancy. A Booking
Request never occupies Resources until the Owner confirms it. Feedback on a
pending request doesn't establish that an external feed is fresh.

## Daily and timed Offers

A `daily` Offer books whole days. It takes no `minimumNights`, and a request
sends its first and last day:

```json theme={null}
{
  "id": "kayak-day",
  "name": { "en": "Kayak for a day" },
  "unit": "daily",
  "consumes": ["kayaks"],
  "maximumGuests": 2,
  "minimumAdvanceDays": 1
}
```

```js theme={null}
await liteshop.bookings.request({
  offerId: "kayak-day",
  startDate: "2027-06-01",
  endDate: "2027-06-03",
  guests: 2,
  contact: { name: "Guest Name", email: "guest@example.com" },
  locale: document.documentElement.lang || "en"
});
```

A `timed` Offer books fixed-length slots within weekly opening hours in the
Site timezone. Slots start at each `from` time and every `durationMinutes`
after it. `bufferBeforeMinutes` and `bufferAfterMinutes` block time around
each Booking, for example to travel; the Visitor only sees the slot, and the
Owner can change a request's buffers before confirming it.
`minimumNoticeMinutes` closes slots that start too soon, and
`maximumAdvanceDays` closes slots too far ahead.

```json theme={null}
{
  "id": "sales-call",
  "name": { "en": "Talk to sales" },
  "unit": "timed",
  "consumes": ["sales-team"],
  "maximumGuests": 3,
  "maximumAdvanceDays": 60,
  "schedule": {
    "durationMinutes": 30,
    "hours": [
      { "days": ["mon", "tue", "wed", "thu", "fri"], "from": "09:00", "to": "12:00" },
      { "days": ["mon", "tue", "wed", "thu"], "from": "14:00", "to": "17:00" }
    ],
    "bufferBeforeMinutes": 15,
    "bufferAfterMinutes": 15,
    "minimumNoticeMinutes": 720
  }
}
```

List the slots for up to 31 dates, then send the chosen slot's `start` back
unchanged:

```js theme={null}
const { slots } = await liteshop.bookings.getAvailability({
  offerId: "sales-call",
  from: "2027-06-01",
  to: "2027-06-08"
});
const open = slots.filter((slot) => slot.available);
// open[0].start is like "2027-06-01T09:00:00+02:00"

await liteshop.bookings.request({
  offerId: "sales-call",
  start: open[0].start,
  guests: 1,
  contact: { name: "Visitor Name", email: "visitor@example.com" },
  locale: document.documentElement.lang || "en"
});
```

For nightly and daily Offers, `getAvailability` returns `dates` instead of
`slots`, for up to 366 dates. Every response includes the Offer's `unit`.
A slot taken in the meantime fails with `BOOKING_SLOT_UNAVAILABLE`; dates
fail with `BOOKING_DATES_UNAVAILABLE`.

## Editing rules

* Configure inventory only when the Owner explicitly asks for it.
* Keep Resource and Offer IDs stable across updates.
* Every `consumes` entry must reference a declared Resource.
* Choose each Offer's `unit` with the Owner. Use `minimumNights` for nightly
  Offers, a `schedule` for timed Offers, and `maximumGuests` for every Offer.
* Set a Resource's `quantity` only when it holds several Bookings at once.
* Declare Calendar Connections only when the Owner requests external calendars;
  they must configure private import sources in the dashboard before a
  Booking Request can be confirmed.
* Keep connection IDs and Resource mappings stable while configured. A
  configured connection can't be silently remapped by Publish or Rollback.
* Treat arrival as inclusive and departure as exclusive; a daily `endDate` is
  the last booked day.
* Preserve unrelated manifest configuration and the exact `baseDeployment`.
* Use Draft validation and preview before requesting explicit Publish approval.
* Show slot times in the Site timezone, as `getAvailability` returns them.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.