> For the complete Lynkow documentation index in agent-friendly format, see [llms.txt](/llms.txt).

# Reservations and Deposits

**Publié le** : 2026-08-14
**Catégorie** : Features

> Turn a bookable product into a reservation: list offerings, check availability, optionally pick a resource, and create a booking. Previous: [Guide 18](/docs/sdk/features/commerce-products) | Next: [Guide 20](/docs/sdk/features/commerce-customer-accounts)

> **Reference** - This guide is a tutorial. For exhaustive, always-current signatures and field lists, see the auto-generated reference (it regenerates on every `docs:sync`, so it never goes stale):
> 
> - SDK: [`ReservationsService`](/docs/sdk/services/sdk-reservations-service), [`ProductsService`](/docs/sdk/services/sdk-products-service)
> - Types: [SDK Types](/docs/sdk/types)
> - API: [Storefront: Commerce](/docs/storefront-api/storefront-commerce)

## Prerequisites

- Lynkow SDK installed ([Quick Start](/docs/sdk/getting-started/quick-start))
- A **publishable key** on the client (see [Guide 18](/docs/sdk/features/commerce-products)). Reservation endpoints reject a call without one.
- A **bookable product**. Reservation offerings and availability hang off a product whose `kind` is `'bookable'`. Find one with `lynkow.products.list({ kind: 'bookable' })` or `lynkow.products.getBySlug(...)` from Guide 18.

```typescript
import { createClient } from 'lynkow'

const lynkow = createClient({
  siteId: '550e8400-e29b-41d4-a716-446655440000',
  publishableKey: 'lkw_pk_9f8g7h6j5k4w3m2n1p0qrstv',
})
```

The always-available reservation flow is three steps: read the product's offerings and availability, then create a booking. That booking is confirmed immediately. Payment is taken **on site**, in person. See "Payment" at the end for how online deposits fit in.

## Step 1: List offerings (optional)

Some bookable products expose several formulas (for example a 60-minute cut versus a 90-minute cut-and-color), each with its own price and duration. `lynkow.reservations.offerings(productIdOrSlug)` lists them in the merchant's display order. It is not cached, since a merchant can change a price or archive a formula at any time.

```typescript
// Each offering (partial): { id, label, priceCents, durationMin }
const offerings = await lynkow.reservations.offerings('haircut')

for (const offering of offerings) {
  const price = (offering.priceCents / 100).toFixed(2)
  console.log(`${offering.label}: ${price} (${offering.durationMin} min)`)
}
```

A product with no formulas returns an empty array, so a single-price product keeps a simple flow. When the array is non-empty and offering selection is enabled, the chosen `offering.id` becomes required on the availability query and the booking (see below).

## Step 2: Check availability

`lynkow.reservations.availability(productIdOrSlug, filters)` returns the open slots across a date range. `from` and `to` are calendar dates (`YYYY-MM-DD`) and `to` must not precede `from`. Pass the selected `offeringId` when the product uses offerings, so the slot grid reflects that formula's duration.

```typescript
// Each slot (partial): { startsAt, endsAt, available, seatsRemaining }
const { slots, timezone } = await lynkow.reservations.availability('haircut', {
  from: '2026-07-10',
  to: '2026-07-17',
  offeringId: offerings[0]?.id, // omit for a single-price product
})

for (const slot of slots) {
  if (slot.available) {
    console.log(`${slot.startsAt} - ${slot.seatsRemaining} seat(s) left`)
  }
}
console.log(`Times shown in ${timezone}`)
```

Availability is never cached: it changes as other buyers book. Read each slot's `seatsRemaining` to decide what to offer. The response also carries a `window` whose `to` may be earlier than requested when the server clamps an over-wide span.

## Step 3: Pick a resource (optional)

When the merchant runs the product in `customer_optional` mode with a resource pool (for example specific stylists or rooms), `lynkow.reservations.resources(productIdOrSlug)` lists the resources a buyer may choose from. In auto-assign mode, or for a product with no pool, it returns an empty array and you render no picker.

```typescript
// Each resource (partial): { id, name }
const resources = await lynkow.reservations.resources('haircut')

for (const resource of resources) {
  console.log(`${resource.name} (${resource.id})`)
}
```

Pass the chosen `resource.id` as `resourceId` on the booking to request that specific resource. Omit it to let the server auto-assign a free one.

## Step 4: Create the booking

`lynkow.reservations.bookings.create(productIdOrSlug, data)` creates the reservation. No account is required: a guest supplies their own name and email. The returned booking is `confirmed` right away, because payment is handled on site.

```typescript
// Booking (partial): { reference, status, startsAt, endsAt, partySize }
const booking = await lynkow.reservations.bookings.create('haircut', {
  startsAt: slots[0].startsAt, // copied from an available slot
  partySize: 1,
  guestName: 'Alice Martin',
  guestEmail: 'alice@example.com',
  guestPhone: '+33 6 12 34 56 78', // optional
  offeringId: offerings[0]?.id,    // required when the product uses offerings
  resourceId: resources[0]?.id,    // optional; omit to auto-assign
})

console.log(`Reservation ${booking.reference} is ${booking.status}. Payment is taken on site.`)
```

The SDK injects anti-spam honeypot and timestamp fields automatically. Two consequences to handle on a long-lived or non-browser client: a booking sent within a few seconds of `createClient(...)` is rejected `'TOO_MANY_REQUESTS'` (too fast), and one sent more than roughly an hour later is rejected `'BAD_REQUEST'` (form expired). On a storefront that enforces reCAPTCHA rather than the default honeypot, pass `options.recaptchaToken`.

### Booking errors

```typescript
import { isLynkowError } from 'lynkow'

try {
  await lynkow.reservations.bookings.create('haircut', { /* ... */ })
} catch (error) {
  if (isLynkowError(error) && error.code === 'VALIDATION_ERROR') {
    // Bad input, or the slot filled between the availability read and this call.
    // Re-fetch availability and let the buyer pick again.
  } else {
    throw error
  }
}
```

`'VALIDATION_ERROR'` (HTTP 422) also covers a slot that filled after you read availability, so re-fetching slots on this error is the right recovery.

## Attach a booking to a signed-in buyer

When the same client already holds a customer session (see [Guide 20](/docs/sdk/features/commerce-customer-accounts)), booking creation attaches it automatically, and the booking then shows up in `lynkow.customers.me()` right away. For a signed-in buyer the recorded email is always the account email, and the name is the stored account name once their email is verified, so both can differ from what a form submitted. Guest bookings are unchanged when no session is present.

## Payment

Today the live, always-available payment model for a reservation is **on site**: the booking above is `confirmed` immediately and the buyer pays in person.

An **online deposit** (a card payment taken to hold the booking) is a separate, per-site capability. It may be enabled for your site, or it may be coming. Treat it as optional: do not wire a checkout redirect into your default booking flow.

- When your site has the deposit capability enabled for a product, `lynkow.reservations.bookings.create(...)` throws `'CONFLICT'` (HTTP 409) instead of silently returning an unpaid booking, because that flow only ever returns a `confirmed` reservation. `lynkow.reservations.book(...)` is the deposit-aware entry point for those products: it returns either the same confirmed booking or a `pending` reservation carrying the deposit details.
- `lynkow.reservations.refreshPayment(...)` exists to reconcile a deposit's status after the buyer returns from checkout. It is only relevant once the deposit capability is active for your site.

If the deposit capability is not enabled for your site, you do not need `book()` or `refreshPayment()` at all: `bookings.create()` is the complete flow. Check the current commerce capability and payment shapes in the [Storefront: Commerce](/docs/storefront-api/storefront-commerce) reference before building an online-payment step.

## Reference

Do not treat this guide as the field list. For every method, signature, and property, link the source of truth:

- **SDK services:** [`ReservationsService`](/docs/sdk/services/sdk-reservations-service), [`ProductsService`](/docs/sdk/services/sdk-products-service) - every method and signature.
- **Types:** [SDK Types](/docs/sdk/types) - the full `AvailabilityResponse`, `ReservationOffering`, `ReservationResource`, and `Booking` shapes.
- **API:** [Storefront: Commerce](/docs/storefront-api/storefront-commerce) - request and response schemas, and the current payment capability.

## Next Steps

- [Guide 20: Customer Accounts](/docs/sdk/features/commerce-customer-accounts) - let buyers sign up, sign in, and see their own bookings.