Skip to content

Booking lifecycle

A booking intent is a hold: what the customer wants, bound to a departure, before any money. A booking is the confirmed thing — departure-bound and obligation-bound — and reuses the intent’s id once the purchase settles. GET /booking-intents/{id} reads the hold; GET /bookings/{booking_id} reads the booking.

Every booking has an agreement: a versioned chain of the facts agreed with the customer (start time, meeting point, party, add-ons, price). Changes append to the chain; nothing is overwritten.

Bookings move through a small, fixed set of states. Everything else about a booking is a flag or a fact on its agreement, not a state.

booked ──▶ checked_in ──▶ done
│
├──▶ cancelled
└──▶ no_show
Transition Endpoint Notes
Check in POST /bookings/{id}/check-in Idempotent; a repeat is 200 {already: true}. Can also be done per participant (POST /participants/{id}/check-in, with /undo) for tenants that check in riders one at a time.
No-show POST /bookings/{id}/no-show Applies the policy’s no-show outcome (often retain the payment).
Reopen POST /bookings/{id}/reopen done or no_show back to booked, within the tenant’s reopen window (default 24 hours) of entering that state. A cancelled booking does not reopen.
Cancel POST /cancellations or a cancel amendment See below. Terminal.

Checking in a cancelled or no-show booking is 409 bad_transition; reopening outside the window is 409 reopen_window_closed.

Tenants that assign equipment to riders record it with POST /bookings/{booking_id}/assignments (a unit is held by one open booking per departure window; 409 unit_busy names the holder) and release it with the matching DELETE.

GET /bookings takes exactly one selector: a departure window (window_start + window_end), the guest’s confirmation number (ref, the QS-XXXXXXXX form of the obligation ref), the buyer’s email, or free text q over buyer and rider names. Cancelled bookings ride along with status: cancelled for the caller to filter. For a day’s operations use GET /days/{date}, and for a departure’s roster GET /contexts/{context_id}/manifest.

POST /bookings/{booking_id}/amendments appends one typed amendment to the agreement. Send expected_version equal to the agreement’s current version; a stale version is refused 409 with stored_version and changes nothing.

kind Carries Money
start_time a new start none
meeting_point a name and address, or null to clear none
span a new rental span priced server-side
party_delta names joining or leaving server-priced; growth reserves capacity first (409 capacity_exhausted if it cannot)
add_on_delta an add-on id and a quantity delta server-priced; removal reverses at the line’s own paid rate
add_on_rider which rider an add-on line is bound to none
party_rename a rider’s new name none
price_override a net delta and a reason requires commerce.price_override; a staff action with an audit trail
cancel an effective date the refund fraction comes from the booking’s policy snapshot

The customer update notification is enqueued in the same transaction as the amendment, so the email always describes what was actually recorded. An amendment can only ever lower what is owed down to what has been collected; refunding money that was already collected is the refund lane’s job.

A cancellation is addressed by obligation_ref and carries a reason code because the reason governs the money:

  • operator_trip_cancelled — always a full refund.
  • customer_initiated — refunded according to the booking’s policy snapshot: the tenant’s T-minus schedule (hours of notice → refund basis points) as it stood when the booking was sold, or a per-offering / per-channel policy document that overrides it.

The refund is not settled inline: the booking flips to cancelled and its capacity is released in one transaction, and a cancellation.requested event is enqueued for the ledger to settle against the original capture. Read the obligation’s balance or the ledger to see it land. x-idempotency-key is required.

Cancelling a whole departure (POST /departure-cancellations) cancels and fully refunds every booking on it.

POST /rebookings moves a booking to a new departure (variant + start time) and posts the price revision against its obligation: upcharge (a priced charge is enqueued), downgrade (a partial refund), or even_exchange (no money event). Capacity is swapped in the same transaction — the old seat released, the new one held — and a full target chain refuses 409 capacity_full with nothing moved. The operator can quote first with GET /bookings/{booking_id}/quotes.

Tenants can require rebooks to be approved rather than automatic, and can attach a change fee; both come from the policy.

A policy governs cancellation, reschedule, no-show and weather outcomes, fees, and the text the guest sees. Policies are scoped — tenant default, then per offering, per variant, or per sales channel — and anything a scope leaves unset inherits. PUT /policies/{scope_kind}/{scope_ref} publishes a new version; bookings made from then on snapshot it, existing bookings keep theirs. GET /bookings/{booking_id}/policy shows the terms a given booking is actually under, and POST /bookings/{booking_id}/policy/re-resolve re-snapshots it deliberately. The simple tenant-wide T-minus schedule is set with POST /merchants/cancellation-schedule.

The operator-cancel rule is fixed at full refund and cannot be configured otherwise.

Customers act for themselves through capability links: single-purpose URLs whose token is the authorisation.

Link Minted by Lets the guest
/b/{token} POST /obligations/{ref}/balance-links Pay exactly the obligation’s live balance, read at render and again at pay
/g/{token} group pay Pay their share of a group booking
/p/{token} party link Add or name riders on the booking
/w/{token} POST /readiness-links Complete readiness requirements — sign the waiver, attest eligibility

Minting a new balance link for the same obligation cancels the previous one, so a guest never holds two live links. Tokens are stored as digests and shown once; an unknown, consumed or expired token is a 404.

Signed-in customers get the same abilities without a link through /account/*: their bookings, a cancellation quote, a self-serve cancellation or rebook within policy, and their readiness status.

Every booking, amendment, cancellation and rebook enqueues its customer notification in the same transaction as the change, and it leaves through Quikstik with the operator in copy. POST /bookings/{booking_id}/resend-confirmation re-sends the current confirmation. GET /obligations/{ref}/deliveries lists what was delivered.