# Bookings

Service products can be booked online. The customer picks a date, a time and optionally a team
member; the slot is held while they pay and confirmed once the order is paid.

The merchant turns booking on per service in the dashboard (Online shop → Bookings) and sets its
rules: duration, buffers, places per slot (1 = appointment, more = a class), start interval,
minimum notice, how far ahead, the free cancellation window, full price or a deposit online, the
location and who performs it. Opening hours can be set for the business, a location or one team
member, with blocked time and extra openings on top.

## Is a product bookable?

In the [catalog](https://developers.baxpay.com/online-shop/storefront-api#read-the-catalog), a bookable service has a
`booking` object:

```json
{
  "durationMinutes": 45,
  "capacity": 1,
  "paymentMode": "DEPOSIT",
  "depositPercent": 30,
  "cancelWindowHours": 24
}
```

- `capacity` (number): Places per slot. More than 1 is a class: several customers share a start time.
- `paymentMode` (FULL | DEPOSIT): What is charged online. With a deposit, the rest is paid on site.
- `cancelWindowHours` (number): Until this many hours before the start, the customer can cancel (refunded) or reschedule online.

## Find free slots

- `GET /shop/{slug}/products/{productId}/slots` — ?from=2026-11-03&to=2026-11-09&staffId=&seats=1

```bash
curl https://api.baxpay.com/v1/shop/maison-lumiere/products/8b21c7f0-…/slots
```

```json
{
  "success": true,
  "data": {
    "timeZone": "Europe/Paris",
    "today": "2026-11-02",
    "from": "2026-11-02",
    "to": "2026-11-15",
    "service": { "durationMinutes": 60, "capacity": 1, "paymentMode": "FULL", "cancelWindowHours": 1 },
    "staff": [],
    "slots": [{ "startsAt": "2026-11-03T08:00:00.000Z", "endsAt": "2026-11-03T09:00:00.000Z", "seatsLeft": 1, "staffIds": [] }]
  }
}
```

- `from, to` (YYYY-MM-DD): Dates in the shop's time zone. Defaults to today and the next 13 days; at most 62 days at a time, never in the past or beyond the booking horizon.
- `staffId` (uuid): Only this team member's slots.
- `seats` (number): Places wanted (classes). Defaults to 1.

```json
{
  "timeZone": "Europe/Paris",
  "today": "2026-11-02",
  "lastDate": "2026-12-31",
  "from": "2026-11-03",
  "to": "2026-11-09",
  "service": { "durationMinutes": 45, "capacity": 1, "paymentMode": "DEPOSIT", "depositPercent": 30, "cancelWindowHours": 24 },
  "staff": [{ "id": "…", "name": "Anna B." }],
  "slots": [
    { "startsAt": "2026-11-03T08:00:00.000Z", "endsAt": "2026-11-03T08:45:00.000Z", "seatsLeft": 1, "staffIds": ["…"] }
  ]
}
```

Slots are instants (UTC): show them in `timeZone`. Opening hours follow the wall clock, so 09:00
stays 09:00 across daylight-saving changes. Team members are shown by first name and initial.
A service that cannot be booked online answers `404`.

## Book at checkout

A bookable line carries the slot picked, in both the
[quote](https://developers.baxpay.com/online-shop/storefront-api#price-a-cart) and the
[checkout](https://developers.baxpay.com/online-shop/storefront-api#check-out). `quantity` is the number of places.

```json
{
  "productId": "…",
  "quantity": 1,
  "booking": { "startsAt": "2026-11-03T08:00:00.000Z", "teamMemberId": "…" }
}
```

```bash
curl -X POST https://api.baxpay.com/v1/shop/maison-lumiere/checkout \
  -H "Content-Type: application/json" \
  -d "{
    \"items\": [{ \"productId\": \"8b21c7f0-…\", \"quantity\": 1, \"booking\": { \"startsAt\": \"$SLOT_START\" } }],
    \"customer\": { \"name\": \"Camille Martin\", \"email\": \"camille@example.fr\" },
    \"locale\": \"fr\"
  }"
```

Leave out `teamMemberId` for "anyone". The line is priced at the full price or the deposit and
named after its time, so the receipt and e-invoice show exactly what was charged. Services never
need delivery: a cart of services only gets the `NONE` option.

At checkout the slot is held for about 17 minutes while the customer pays (the payment link
itself closes after 15). If someone else took the time meanwhile, the checkout fails with `409`
and `"reason": "SLOT_TAKEN"`, and the order is cancelled: ask the customer to pick another time.

## Booking lifecycle

Held while the customer pays.
The order was paid. The customer gets a confirmation email with a `booking.ics` calendar file, and a reminder 24 hours before.
Marked by the merchant after the appointment.

A booking can be `CANCELLED` by the customer (inside the window), by the merchant (optionally
refunded), or by BaxPay when the payment is not completed in time. An order made only of
bookings follows them: cancelled when all are, completed when all are done.

## The customer's booking page

Every confirmation email links to `https://{shop}/booking/{token}`. Your own front end can use the
same endpoints:

- `GET /shop/{slug}/bookings/{token}`
- `GET /shop/{slug}/bookings/{token}/ics` — text/calendar file
- `POST /shop/{slug}/bookings/{token}/cancel`
- `POST /shop/{slug}/bookings/{token}/reschedule`

```bash
curl https://api.baxpay.com/v1/shop/maison-lumiere/orders/kW3c…
```

```bash
curl https://api.baxpay.com/v1/shop/maison-lumiere/bookings/Q14uj_pG…
```

```json
{
  "success": true,
  "data": {
    "serviceName": "…",
    "status": "PENDING_PAYMENT",
    "startsAt": "2026-11-03T08:00:00.000Z",
    "timeZone": "Europe/Paris",
    "seats": 1,
    "canChange": false,
    "canReschedule": false,
    "order": { "number": 1001 }
  }
}
```

```bash
curl https://api.baxpay.com/v1/shop/maison-lumiere/bookings/Q14uj_pG…/ics -o booking.ics && head -3 booking.ics
```

The booking returns `serviceName`, `startsAt`, `endsAt`, `timeZone`, `staffName`, `seats`,
`status`, `location`, `paidMinor`, `balanceDueMinor` (still to pay on site), `refundedMinor`,
`changeDeadline`, and `canChange` / `canReschedule`. Booking tokens are found in the order's
`bookings[].token` ([order page](https://developers.baxpay.com/online-shop/storefront-api#show-the-order)).

**Cancel** refunds what the booking cost online, through the order's payment. **Reschedule** takes
a free slot from `…/slots`:

```bash
curl -X POST https://api.baxpay.com/v1/shop/maison-lumiere/bookings/Q14uj_pG…/reschedule \
  -H "Content-Type: application/json" \
  -d '{ "startsAt": "2026-11-04T09:00:00.000Z" }'
```

Both need a paid booking (`canChange: true`) and only work until `changeDeadline`; otherwise they
answer `400` and the customer should contact the shop. They are limited to 10 requests a minute. The customer is emailed the new time,
or the cancellation with the refund amount.

## What the merchant sees

New, moved and cancelled bookings arrive in the Business dashboard calendar and as push
notifications in the Business app. The merchant can mark bookings completed or no-show, move them,
cancel them with a refund, add bookings taken by phone, and block time.
Online shop sales paid by bookings are labelled **Booking** in payments and analytics
(see [Online shop](https://developers.baxpay.com/online-shop#seeing-shop-sales-in-your-integration)).

## Related

- [Storefront API](https://developers.baxpay.com/online-shop/storefront-api) — catalog, quote, checkout
- [Online shop](https://developers.baxpay.com/online-shop) — orders, domains, roles
- [Products & catalog](https://developers.baxpay.com/products) — creating service products
