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

# Reservation lifecycle

> Statuses, bidirectional seating, and money collected upfront.

## Statuses

The API vocabulary is provider-agnostic:

`booked` → `confirmed` → `arrived` → `seated` → `finished`, with
`no_show` and `cancelled` as exits at any point.

* `booked` = not yet confirmed. Optional to push; never annotates the
  floor plan on the till.
* Cancellation is always `status: "cancelled"` via `PUT` — there is no
  DELETE.

**EatNow mapping** (reference implementation):

| EatNow                                                                         | API                 |
| ------------------------------------------------------------------------------ | ------------------- |
| CONFIRMED, CONFIRMED\_PENDING\_PAYMENT, MANUALLY\_CONFIRMED, DOUBLE\_CONFIRMED | `confirmed`         |
| SHOWED\_UP                                                                     | `arrived`           |
| SEATED, PARTIALLY\_SEATED, SEATED\_FOR\_DRINKS                                 | `seated`            |
| DONE                                                                           | `finished`          |
| NO\_SHOW                                                                       | `no_show`           |
| CANCELED\_BY\_USER, CANCELED\_BY\_RESTAURANT, REJECTED                         | `cancelled`         |
| PENDING, PENDING\_PAYMENT                                                      | `booked` (optional) |

`PARTIALLY_SEATED` sends the actually-occupied tables in `tableIds`.

## Seating is bidirectional

**You seat** — write `status: "seated"` with `tableIds`. The API checks
occupancy synchronously (`409 table_occupied` if the table is taken,
with the occupant in `occupiedBy`). On success the seat is **accepted,
not yet executed**: the till opens the order within a couple of seconds
and you receive `table.seated` carrying your `reservationExternalId` as
confirmation. If a walk-in grabbed the table in that window, you receive
`reservation.seat_rejected` instead — re-seat the party; the instruction
will not retry until you change the reservation. A walk-in typed in your
own system is just a reservation that arrives already `seated`.

**Staff seats** — the till links the reservation and you learn about it
through the same `table.seated`. Treat it as you would your own seat
confirmation (e.g. EatNow flips to SEATED).

**After the link, the order belongs to the till.** Your updates can
still move the party (`tableIds` → the till executes the move) and
adjust `covers`, but cancelling the reservation never closes the order —
closing and paying are staff gestures. Staff-side moves come back to you
as `table.freed(moved)` + `table.seated`.

Your own webhook reactions echo back as `PUT`s with a newer `updatedAt`
and identical content — that is expected and converges: identical
content is a no-op on the till.

## Prepayments and imprints

Money you collected upfront travels on the reservation:

```json theme={null}
"prepayment": {
  "prepaidMinor": 3000,
  "currency": "EUR",
  "items": [{ "label": "Deposit", "amountMinor": 3000 }],
  "imprint": { "amountMinor": 5000, "status": "active" }
}
```

* `prepaidMinor` (deposit or full prepayment — same mechanics) is
  spendable on the bill: staff applies it in the payment sheet and it
  shows up in `check.paid` as the `provider_prepaid` tender — that is
  your consumption signal for reconciliation. The till applies at most
  the amount due.
* `imprint` is **display-only** on the till (a guarantee badge).
  Releasing or charging it stays entirely on your side — key it on
  `check.paid` / `table.freed` (normal payment) and
  `reservation.status_changed(no_show)`.

## Guest profile and CRM

Each reservation carries a `guest` object and an `internalNote`. NowOS
**never writes the guest record back** — you own it. Two notes, two
audiences:

* `guest.allergies` — the guest's own note (allergies *and* preferences).
  Shown to staff on the reservation. Read-only on the till. **Send the
  `guest` block whenever you have this note, even for a party you cannot
  name** — `guest.name` is nullable precisely so an allergy is never lost
  for want of a customer record.
* `internalNote` (on the reservation, not the guest) — a staff-only note
  (EatNow's `custom_message`). Never shown to the guest. This one is
  **two-way**: it lands on the order the booking seats, where it prints on
  every kitchen ticket, and an edit made at the till comes back to you as
  `reservation.note_changed`. Last writer wins on `updatedAt`, and you are
  expected to echo it back like any other change.

### The 360° profile (read-back)

NowOS is the **transactional** system of record: every paid check is
pushed to you as `check.paid`, carrying `lines[]`, `tenders[]`, and the
`reservationExternalId` that ties the spend to the party. You keep the
CRM. Aggregate that history and return it on the `guest`, so the till can
show a full profile when staff open the reservation.

These guest fields are **read-back only** — you send them, NowOS displays
them, NowOS never recomputes them. There is no two-way sync of a shared
field: one writer per field.

| Guest field         | What it is                                                                                                                                           |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `memberSince`       | ISO date the guest first visited (nullable)                                                                                                          |
| `averageSpendMinor` | average spend per visit, minor units (nullable)                                                                                                      |
| `noShowCount`       | lifetime no-shows (nullable)                                                                                                                         |
| `recentVisits[]`    | recent visits, newest first: `{ occurredAt, tableLabel, covers, spendMinor, outcome }`, where `outcome` is `completed` \| `in_progress` \| `no_show` |

The loop is: `check.paid` → you update the guest's history on your side →
the next reservation read for that guest carries fresh aggregates → the
till shows them. Leave any aggregate `null` if you don't track it; the
till just hides that line.
