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

# Bookings

> The durable first-party commitment: bookings, booking items, allocations, holds, origin and provenance, fulfillments, the status state machine, KMS-backed traveler PII, and the audit-logged action ledger that gates risky mutations.

The bookings module (`@voyant-travel/bookings`) owns the durable, customer-safe operational record of a sale. When a pursuit hardens past the proposal, the result is a Booking: the travelers, the priced line items, the inventory holds, the provenance of how it was created, and the lifecycle state it sits in. Everything operational about a confirmed sale routes through here.

Bookings is the third and fourth rung of the **commitment chain**: `Quote -> accepted Quote Version -> reserve workflow -> Booking -> Fulfillment`. A booking does not require a generic first-party order to exist; it is the first-party commitment, and its origin record carries the provenance of wherever it came from. Identity is referenced by snapshot (`personId` plus `organizationId`), never owned here.

## Key concepts

<ResponseField name="Booking" type="durable commitment">
  The durable first-party commitment and customer-safe operational record: travelers, booking items, allocations, fulfillments, redemptions, origin and provenance, and state. Avoid "reservation" and "order"; the booking is the persistent record. See [Booking](/docs/concepts/glossary).
</ResponseField>

<ResponseField name="Booking Item" type="line on a booking">
  A line item on a Booking (unit, service, extra, fee, tax, discount, accommodation, transport). Each allocation belongs to exactly one booking item, and each item produces zero or more fulfillments. See [Booking Item](/docs/concepts/glossary).
</ResponseField>

<ResponseField name="Allocation" type="capacity hold">
  A capacity hold against a [Slot](/docs/concepts/glossary), Pickup, or Resource, moving `held` to `confirmed` to `fulfilled`. An allocation is the inventory-line entity and belongs to exactly one booking item. See [Allocation](/docs/concepts/glossary).
</ResponseField>

<ResponseField name="Hold" type="time-limited claim">
  The temporal status of a booking before confirmation, carrying a `hold_expires_at`. A hold is a temporary, time-limited claim on inventory that expires if the booking is not confirmed in time. Distinct from an Allocation, which is the inventory line itself. See [Hold](/docs/concepts/glossary).
</ResponseField>

<ResponseField name="Booking Origin" type="provenance record">
  Bookings-owned provenance describing how a Booking was created. The durable `booking_origins` row is keyed by `booking_id` and records one of `accepted_quote_version`, `catalog_price_availability`, `catalog_snapshot`, `provider_source_order`, or `legacy_transaction`, plus the matching reference ids (quote version, trip snapshot, reservation plan, catalog response, provider order ref, or migrated transaction id). New flows write this owner path. See [Booking Origin](/docs/concepts/glossary).
</ResponseField>

<ResponseField name="Fulfillment" type="issued deliverable">
  Issuance of a deliverable artifact (voucher, ticket, PDF, QR, barcode) for a booking item. A fulfillment is delivered over exactly one channel, and consumed at the point of service by a redemption. See [Fulfillment](/docs/concepts/glossary).
</ResponseField>

<ResponseField name="Booking traveler" type="person who travels, with PII">
  A [Traveler](/docs/concepts/glossary) on the booking, carrying category and PII. Travel details (identity document, date of birth, dietary and accessibility needs) are KMS-encrypted and can be pre-filled from a Relationships [Person document](/docs/platform/modules/relationships) snapshot.
</ResponseField>

## The commitment ladder

A booking is the operational endpoint of the chain. Each rung hardens what came before.

```mermaid theme={null}
flowchart LR
    q[Quote] --> qv[Accepted quote version] --> rw[Reserve workflow] --> b[Booking] --> f[Fulfillment]
```

<Steps>
  <Step title="Quote">
    A tracked pursuit in the [proposals](/docs/platform/modules/proposals) module, with a Person or Organization.
  </Step>

  <Step title="Accepted quote version">
    The client accepts a sent quote version. That marks the version accepted, closes the quote won, and seeds the reserve workflow. Acceptance is a sales decision, not supplier confirmation.
  </Step>

  <Step title="Reserve workflow">
    Reserve takes the accepted version's frozen snapshot, rechecks sellability and cost on live lines, moves manual lines into supplier confirmation, and secures the component commitments. A reservation plan captures what was reserved (and what failed) before any booking is created.
  </Step>

  <Step title="Booking">
    Once reserve secures the commitments, the Booking is created. It holds allocations against slots (or pickups, or resources) for each item, snapshots the buyer, and records its origin pointing back at the accepted quote version.
  </Step>

  <Step title="Fulfillment">
    Booking items issue fulfillments (vouchers, tickets), each delivered over one channel and redeemed at the point of service.
  </Step>
</Steps>

### Status state machine

Booking status moves along an explicit transition graph; illegal transitions raise a `BookingTransitionError`. The transitions are:

| From                                | Allowed next states                                     |
| ----------------------------------- | ------------------------------------------------------- |
| `draft`                             | `on_hold`, `awaiting_payment`, `confirmed`, `cancelled` |
| `on_hold`                           | `awaiting_payment`, `confirmed`, `expired`, `cancelled` |
| `awaiting_payment`                  | `confirmed`, `expired`, `cancelled`                     |
| `confirmed`                         | `in_progress`, `cancelled`                              |
| `in_progress`                       | `completed`, `cancelled`                                |
| `completed`, `expired`, `cancelled` | terminal                                                |

```mermaid theme={null}
stateDiagram-v2
    [*] --> draft
    draft --> on_hold
    draft --> awaiting_payment
    draft --> confirmed
    on_hold --> awaiting_payment
    on_hold --> confirmed
    awaiting_payment --> confirmed
    confirmed --> in_progress
    in_progress --> completed
    on_hold --> expired
    awaiting_payment --> expired
    draft --> cancelled
    on_hold --> cancelled
    awaiting_payment --> cancelled
    confirmed --> cancelled
    in_progress --> cancelled
    completed --> [*]
    expired --> [*]
    cancelled --> [*]
```

Transitions stamp the matching timestamp: `confirmedAt` on confirm, `paidAt` additionally when confirming out of `awaiting_payment` (so reporting can split free confirmations from paid ones), `expiredAt`, `cancelledAt`, and `completedAt`. Cancel is the operational reversal of a booking, distinct from voiding an invoice (a financial reversal) and from closing a quote.

<Note>
  An admin can override status outside the transition graph, but only as an explicit, audit-logged action with a required reason. The transition graph is the default; the override is the escape hatch.
</Note>

## PII handling and audit logging

Booking travelers carry sensitive personal data: passport numbers, dates of birth, dietary and accessibility needs. Bookings treats this PII as first-class.

* **Encryption at rest.** Identity, dietary, and accessibility fields are encrypted through a KMS provider into JSON envelopes. The PII service (`getTravelerTravelDetails`, `upsertTravelerTravelDetails`, `deleteTravelerTravelDetails`) is the only path that decrypts them, and decryption is on demand per traveler.
* **Audit logging.** Every encrypt, decrypt, and delete of traveler travel details emits a `BookingPiiAuditEvent` carrying the action, the traveler id, and the acting principal, through the service's `onAudit` hook. Reads of PII are logged, not just writes.
* **Snapshot, do not re-key.** A booking traveler can pre-fill its travel snapshot from a Relationships Person document. Explicit input always wins; the snapshot only fills the gaps. Provenance of the snapshot (the source person-document id) is recorded.
* **Route-boundary redaction.** Callers without the PII-reveal scope receive redacted contact and traveler fields at the API boundary (email, phone, names, tax id, and address lines are masked). Internal callers such as cron jobs and workflows bypass redaction; redaction is an in-flight protection at the route layer.

## Action-ledger approvals

Agent and workflow status mutations that require approval do not mutate the booking directly. They return `202` with the requested action and approval ids, and must send an `Idempotency-Key`. The key is fingerprinted with the command input and the approval-policy inputs, so replaying the same key with different input returns a conflict rather than a second action.

After approval, the same status mutation is executed again with the approval-id header from `@voyant-travel/action-ledger`. The route revalidates that the approval is approved, unexpired, linked to the same requested action and current principal, and command-equivalent to the original request before it touches the booking. Approved execution ledger fields are stamped so execution entries link back to the requested action and its approval.

## What it owns

* **Bookings** (`book`) and their status state machine.
* **Booking travelers** (`bkps`) with KMS-backed PII and snapshot pre-fill.
* **Booking origins** (`booking_origins`): the provenance owner path.
* **Booking items**, allocations, and the hold lifecycle.
* **Booking supplier statuses** (`bkss`), the booking activity log (`bkal`), and booking notes (`bnot`).
* **Booking session states** (`bkst`): first-class persisted wizard state for the public storefront flow, with preview and `applyToSession` repricing.
* **Booking requirements** (`./requirements`) and the supplier extension (`./extensions`).
* **Custom fields** on bookings, validated on write against the deployment [custom-field registry](/docs/platform/fundamentals/custom-fields).
* The reservation-plan and refund workflows, and the dependency-injected checkout capability.

It does **not** own catalog truth, inventory authoring, or money collection. Those belong to [catalog](/docs/platform/modules/catalog), [inventory](/docs/platform/modules/inventory), and the finance module.

## Working with it

Standard modules are not registered by hand. `@voyant-travel/bookings` is part of the standard product graph, so its routes, services, subscribers, and jobs are resolved into the application at build time. See [Configuration](/docs/platform/fundamentals/configuration).

Transitions are validated against the state machine before any write.

```ts theme={null}
import { transitionBooking, canTransitionBooking } from "@voyant-travel/bookings";

if (canTransitionBooking(booking.status, "confirmed")) {
  const patch = transitionBooking(booking.status, "confirmed");
  // patch stamps confirmedAt, and paidAt when coming from awaiting_payment
  await bookingsService.applyStatusPatch(db, booking.id, patch);
}
```

Record provenance so the booking can always explain where it came from.

```ts theme={null}
import { upsertBookingOrigin } from "@voyant-travel/bookings";

await upsertBookingOrigin(db, {
  bookingId: booking.id,
  originSource: "accepted_quote_version",
  quoteVersionId: "qver_01h...",
});
```

Read traveler PII through the PII service, which decrypts on demand and audits the access.

```ts theme={null}
const details = await piiService.getTravelerTravelDetails(
  db,
  travelerId,
  actorId, // logged in the BookingPiiAuditEvent
);
```

<Warning>
  Approval-required status mutations from agents or workflows must send an `Idempotency-Key`, and must replay through the action-ledger approval header after approval. Skipping the ledger path is rejected at the route, not silently executed.
</Warning>

## Links to other modules

* **[proposals](/docs/platform/modules/proposals)** feeds the accepted quote version that a booking's origin points at. The `booking_crm_details` extension links the booking back to its quote.
* **[relationships](/docs/platform/modules/relationships)** owns the buyer snapshotted onto the booking and the person documents that seed traveler PII.
* **[trips](/docs/platform/modules/trips)** groups independent component bookings into one Trip Envelope without erasing their per-component lifecycle boundaries.
* **[catalog](/docs/platform/modules/catalog)** is where the booking engine lands rows; bookings is the shared parent so an itinerary can mix owned and sourced lines.
* The finance module attaches payment schedules and invoices to a booking; legal attaches contracts and policy acceptances.

## React package

`@voyant-travel/bookings-react` is the client tier. Headless consumers import from the root, `./hooks`, `./client`, or `./query-keys` with no styling peers, including public storefront helpers (`usePublicBookingSession`, `usePublicBookingSessionState`, `usePublicBookingSessionFlowMutation`) that target the wizard-state and repricing contract. Styled surfaces live under `./ui`, `./components/*`, `./journey`, and `./admin`, with optional heavier peers. The customer-facing booking journey under `@voyant-travel/bookings-react/journey` pairs with the catalog booking-engine surfaces.

## Next steps

<CardGroup cols={2}>
  <Card title="Proposals" icon="filter" href="/docs/platform/modules/proposals">
    The accepted quote version that seeds the reserve workflow.
  </Card>

  <Card title="Trips" icon="route" href="/docs/platform/modules/trips">
    Trip envelopes that compose multiple component bookings into one itinerary.
  </Card>

  <Card title="Catalog" icon="layer-group" href="/docs/platform/modules/catalog">
    The sellable plane the booking engine lands owned and sourced rows into.
  </Card>

  <Card title="Glossary" icon="book-open" href="/docs/concepts/glossary">
    The shared vocabulary: Booking, Booking Item, Allocation, Hold, Origin, Fulfillment.
  </Card>
</CardGroup>
