> Portal Navigation:
> 
> - Append `.md` to any URL under `https://dev.wix.com/docs/` to get its markdown version.
> - Pages are either content pages (article or reference text) or menu pages (a list of links to child pages).
> - To get a menu page, truncate any URL to a parent path and append `.md` (e.g. `https://dev.wix.com/docs/sdk.md`, `https://dev.wix.com/docs/sdk/core-modules.md`).
> - Top-level index of all portals: https://dev.wix.com/docs/llms.txt
> - Full concatenated docs: https://dev.wix.com/docs/llms-full.txt

## Resource: Table Reservations

## Article: Table Reservations

## Article Link: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/skills/table-reservations.md

## Article Content:

# RECIPE: Reserve a Table or Book an Experience

## When to use this recipe

- "Table for 4 tomorrow at 7:30 pm, I'm Noa, phone …"
- "What's the largest group I can book for?" / "Are you open for reservations on Sundays?"
- "Tell me about the Sunday brunch table — when is the next one, how much per person?"
- "Book 2 seats at the next brunch for Maya…"

## Inputs you need before STEP 3

| Input | How to get it |
|---|---|
| Visitor token | STEP 1 |
| Business timezone | `GetBusinessDetails` → `timeZone` |
| `reservationLocationId`, `partySize.min/max`, `businessSchedule.periods`, `approval.mode` | STEP 2 |
| Requested date + time converted to UTC in the business zone; party size within bounds | Visitor + date rules |
| Guest `firstName` and `phone` (required), optional last name/email | Ask the visitor — do not fabricate |
| Experience `id`, per-guest price, weekly schedule (experiences) | STEP 5 |

## Decision tree

- **"How many / open on Sunday?"** → STEP 1 + STEP 2 (one read answers both).
- **"Table for N on <date> at <time>"** → STEP 2 → STEP 3 (slots) → STEP 4 (hold → reserve, or single-shot create).
- **"Tell me about <experience>"** → STEP 5; compute the next occurrence from the weekly schedule.
- **"Book seats at <experience>"** → STEP 5 → STEP 4b (create reservation with `experienceId`).
- On **`428 MUST_BE_PREMIUM`** at any write: stop, explain online reservations aren't enabled on this site, give phone/email from `GetBusinessDetails`.

---

## Date and time rules

- Resolve "tomorrow / Friday / 7:30 pm" in the **business timezone**, then convert to UTC for `date`/`startDate` (`2026-09-17T19:30:00+03:00` → `2026-09-17T16:30:00Z`). Sending the local clock time with a `Z` suffix asks for the wrong hour (a 19:30 request became 22:30 local and returned `NON_WORKING_HOURS`).
- Slot granularity is `timeSlotInterval` (15 min here); ask for `slotsBefore`/`slotsAfter` to offer neighbours.
- Only `status: "AVAILABLE"` slots are bookable; `NON_WORKING_HOURS` and `UNAVAILABLE` are not.
- A held reservation expires in **10 minutes**; collect the guest details before holding.

---

## STEP 1: Get a visitor token

`GenerateVisitorToken` once; reuse it.

---

## STEP 2: Read the reservation location

`GET https://www.wixapis.com/table-reservations/reservation-locations/v1/reservation-locations` (visitor-readable).

```jsonc
{ "reservationLocations": [{
  "id": "993f962b-…",                                     // ← reservationLocationId
  "default": true,
  "location": { "name": "Clearspace Studio", "timeZone": "Asia/Jerusalem" },
  "configuration": { "onlineReservations": {
    "partySize": { "min": 1, "max": 8 },                  // ← bound the party-size question
    "approval": { "mode": "AUTOMATIC" },                  // MANUAL → reservations come back REQUESTED
    "onlineReservationsEnabled": true,                    // ⚠ reads true even when writes return MUST_BE_PREMIUM
    "minimumReservationNotice": { "number": 60, "unit": "MINUTES" },
    "businessSchedule": { "periods": [{ "openDay": "SUNDAY", "openTime": "08:00", "closeDay": "SUNDAY", "closeTime": "22:00" }, …] },
    "timeSlotInterval": 15
  } }
}] }
```

---

## STEP 3: Find free times

`POST https://www.wixapis.com/table-reservations/reservations/v1/time-slots`

```bash
curl -X POST 'https://www.wixapis.com/table-reservations/reservations/v1/time-slots' \
-H 'Authorization: <VISITOR_TOKEN>' -H 'Content-Type: application/json' \
-d '{ "reservationLocationId": "<LOCATION_ID>", "date": "2026-09-17T16:30:00Z", "partySize": 4, "slotsBefore": 3, "slotsAfter": 3 }'
```

→ `{ "timeSlots": [{ "startDate": "2026-09-17T16:30:00Z", "duration": 90, "status": "AVAILABLE", "manualApproval": false }, …] }`. Offer the AVAILABLE ones in local time. `check-time-slot` (same base, `/check-time-slot`) additionally **requires `duration`** — prefer `time-slots`.

---

## STEP 4: Hold, then confirm

```bash
# 4a hold (10-minute lock)
curl -X POST 'https://www.wixapis.com/table-reservations/reservations/v1/reservations/hold' \
-H 'Authorization: <VISITOR_TOKEN>' -H 'Content-Type: application/json' \
-d '{ "reservationDetails": { "reservationLocationId": "<LOCATION_ID>", "startDate": "2026-09-17T16:30:00Z", "partySize": 4 } }'
# → { "reservation": { "id": "…", "revision": "1", "status": "HELD" } }

# 4b confirm with the guest details (firstName + phone required)
curl -X POST 'https://www.wixapis.com/table-reservations/reservations/v1/reservations/<RESERVATION_ID>/reserve' \
-H 'Authorization: <VISITOR_TOKEN>' -H 'Content-Type: application/json' \
-d '{ "reservee": { "firstName": "Noa", "lastName": "Bar", "phone": "+972500000000", "email": "noa@example.com" }, "revision": "1" }'
# → status RESERVED (automatic approval) or REQUESTED (manual approval)
```

Single-shot alternative (also used for experiences): `POST https://www.wixapis.com/table-reservations/reservations/v1/reservations`

```json
{ "reservation": { "details": { "reservationLocationId": "<LOCATION_ID>", "startDate": "2026-09-20T08:00:00Z", "partySize": 2, "experienceId": "<EXPERIENCE_ID>" },
                   "reservee": { "firstName": "Maya", "lastName": "Adler", "phone": "+972520000000", "email": "maya@example.com" } } }
```

On this (free-plan) site both `hold` and `reservations` return **`428 { "message": "site must be premium", "details": { "applicationError": { "code": "MUST_BE_PREMIUM" } } }`**. That is the expected gate — reading slots works, writing does not.

---

## STEP 5: Experiences (brunch table, chef's table…)

`POST https://www.wixapis.com/table-reservations/experiences/v1/experiences/query` with `{ "query": {} }` (visitor-readable).

```jsonc
{ "experiences": [{
  "id": "05dbaba3-…",                                     // ← experienceId for STEP 4b
  "reservationLocationId": "993f962b-…",
  "configuration": {
    "displayInfo": { "name": "Slow Sunday Brunch Table", "shortDescription": "…" },
    "paymentPolicy": { "paymentPolicyType": "PER_GUEST", "perGuestOptions": { "price": "95.00" } },
    "onlineReservations": {
      "partySize": { "min": 2, "max": 8 }, "maxGuests": { "number": 12 },
      "minimumReservationNotice": { "number": 24, "unit": "HOURS" },
      "businessSchedule": { "durationInMinutes": 120,
        "entries": [{ "recurrence": "WEEKLY", "weeklyOptions": { "startDate": "2026-09-20", "startDaysAndTimes": [{ "day": "SUNDAY", "time": "11:00" }] } }] }
    }, "visible": true }
}] }
```

There is **no experience-scoped availability endpoint**: compute the next occurrences yourself from `weeklyOptions.startDaysAndTimes` (+ `startDate`/`endDate`, `oneTimeOptions`) in the business timezone, honouring `minimumReservationNotice`. Price is per guest in the site currency. Booking uses STEP 4b with `details.experienceId`; it is premium-gated like tables.

---

## Common errors and how to avoid them

### 1. `428 MUST_BE_PREMIUM` ("site must be premium") on hold / reserve / create

Online reservations need a premium plan even though the location reads `onlineReservationsEnabled: true`. Don't retry; give the visitor the phone and email.

### 2. Every slot is `NON_WORKING_HOURS`

You sent local time with a `Z`. Convert to UTC from the business timezone first.

### 3. `400 duration must not be empty` from `check-time-slot`

Use `time-slots` (no duration needed) or pass `duration` in minutes.

### 4. `404` on `/table-reservations/v1/reservations/held` or `/table-reservations/v1/reservations`

The base is `/table-reservations/reservations/v1/…`; hold is `/reservations/hold`, confirm is `/reservations/{id}/reserve`, create is `/reservations`.

### 5. Reservee rejected

`firstName` and `phone` are required for every online reservation; use a real E.164 number from the visitor.

### 6. Expecting an experience "time slots" call

None exists; project the schedule (STEP 5).

---

## Constants

| Constant | Value |
|---|---|
| Table Reservations app id | `f9c07de2-5341-40c6-b096-8eb39de391fb` |
| Reservations base | `https://www.wixapis.com/table-reservations/reservations/v1/` |
| Held reservation lifetime | 10 minutes |

---

## References

- [List Reservation Locations](https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservation-locations/list-reservation-locations.md)
- [Get Time Slots](https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/time-slots/get-time-slots.md)
- [Create Held Reservation](https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/create-held-reservation.md) · [Reserve Reservation](https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/reserve-reservation.md) · [Create Reservation](https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/create-reservation.md)
- [Query Experiences](https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/experiences/query-experiences.md)