Booking statuses
What each status means at service and booking level, what the guest should see, how to handle on-request bookings, and which status changes after booking are normal.
Two levels of status
The API keeps a status for each service (each booked hotel) and derives the booking status from them. Both use the ServiceStatus enum. The API spells cancelled as CANCELED.
| Where you read it | Booking level | Service level |
|---|---|---|
| Book, booking detail, Refresh and Cancel responses | status | accommodation.status |
getBookings (whole trip) | status | hotelservice[].status |
| Webhooks | Not included. The body carries only timestamp, type, micrositeId and bookingReference, so fetch the booking. |
Service status
| Status | Meaning |
|---|---|
BOOKED | Confirmed by the supplier. |
BOOK_ERROR | Error while booking or closing the service. |
CANCELED | Cancelled. |
PRICE_ERROR | Booked, but the supplier's closing price differed beyond the tolerance. |
NOT_BOOKED | The supplier did not confirm. |
RQ | On request. Waiting for the supplier to confirm. |
PENDING_BOOK | Still in the booking process. |
Booking status
| Status | Derived when |
|---|---|
NOT_BOOKED | All services ended unconfirmed (BOOK_ERROR or NOT_BOOKED). |
RQ | At least one service is RQ. |
PRICE_ERROR | At least one service has a price-change error. |
PENDING_BOOK | At least one service is still PENDING_BOOK. |
BOOKED | All services are BOOKED and no earlier condition applies. |
BOOK_ERROR | Fallback: not fully confirmed and none of the above apply. |
CANCELED | The booking is cancelled. |
What to do after Book
Keep what the guest sees separate from the API status. Show "Confirmed" only for BOOKED. The same outcomes appear on the Book page.
On request: RQ and PENDING_BOOK
RQ means the supplier has not confirmed yet. The platform's scheduler polls the supplier and updates the booking. Expect it to end as BOOKED, or as CANCELED or NOT_BOOKED. Handle PENDING_BOOK the same way.
onRequest: truein a combination means there is no confirmed quota and the supplier must accept.- A booking can close
RQeven whenonRequestwasfalsethrough the whole flow.
Tell the guest "Pending hotel confirmation". Don't show it as confirmed.
Poll booking detail (GET /booking/{bookingReference}/accommodations/{accommodationBookingReference}) every 5 to 15 minutes. It reads stored data and makes no supplier call, so it is cheap. Or react to MODIFIED and CANCELED webhooks and fetch the booking when one arrives.
On BOOKED, confirm to the guest. On CANCELED or NOT_BOOKED, tell the guest, and refund them if you charged.
Set an SLA, for example 24 to 48 hours. When it runs out, your operations team contacts your Nava account manager or the supplier.
const SLA_MS = 24 * 60 * 60 * 1000; // your choice, for example 24 to 48 hours
export async function checkPending(token: string, b: PendingBooking) {
// Booking detail: stored data only, no supplier call.
const detail = await get(token, `/booking/${b.ref}/accommodations/${b.accRef}`);
const view = customerView(detail.status);
if (view.state !== 'pending') return settle(b, detail, view); // confirmed, not confirmed, cancelled or review
if (Date.now() - b.bookedAt > SLA_MS) return escalate(b, detail); // a person follows up
// Still RQ or PENDING_BOOK: check again in 5 to 15 minutes.
}Price-change tolerance
Two separate mechanisms deal with price changes.
| When | What you get | What to do | |
|---|---|---|---|
PRICE_CHANGE warning | Confirm or Prebook, before booking | warnings[].type is PRICE_CHANGE | Show the old and new price. Continue only with the guest's consent. |
PRICE_ERROR status | Book, or later | Booked, but the closing price moved beyond the tolerance | Your operations team decides to accept or cancel. |
The tolerance is set per microsite, as a percentage or a fixed amount. The default is 0.5% between the confirmed price and the supplier's closing price. Within it, the booking closes BOOKED. Beyond it, it closes PRICE_ERROR.
CANCELLATION_POLICIES_CHANGE works like PRICE_CHANGE: a refundable rate may have become non-refundable, so show the new policy and get consent again.
Whether your rates are net or commissionable depends on your microsite and credential configuration, and responses do not flag it. Ask your Nava account manager which one you have, and what your tolerance is.
Statuses that change after booking
A status in a Book response is not final. We have seen all of these transitions in production, and they are by design, not anomalies.
BOOK_ERRORlater becomesBOOKED. Operators sometimes fix a failed booking, sometimes within half an hour.RQbecomesCANCELED, and the service is repriced to 0 within the hour. Accept a service price going to 0.PRICE_ERRORcan be transient or stored. It may already beBOOKEDby the next fetch, or it may stay.NOT_BOOKEDcan still carry a value and resolve later toBOOKEDorCANCELED. Don't zero it on first sight.- Cancel and rebook leaves a
CANCELEDservice at price 0 next to a newBOOKEDone at the same hotel and dates. Both are real, and it is not a duplicate.
Keep reading bookings after Book, through webhooks and booking detail, and let status and price change. Alert only when a non-final status lingers for days while it still carries a value.
Everything else needs attention
These 14 ServiceStatus values are not documented as booking or service outcomes:
NOT_QUOTED, QUOTED, LOAD_CANCELLATION_POLICIES_ERROR, CONFIRMATION_ERROR, CONFIRMED, PREBOOKED, PAID, NEED_QUOTE, CANCEL_ERROR, LOCKED, PENDING_UPDATE, WITH_PROPOSALS, WAITING_SUPPLIER, WAITING_ACCEPTANCE.
- Map each of them to "unknown, needs attention" and route the booking to a person.
- Don't crash on them, and never treat them as success.
- New enum values appear over time. Parse statuses as plain strings, not a closed enum, and send unknown values to review.
Map statuses to what the guest sees
One function turns the API status into the state your UI shows. Anything it doesn't recognise goes to review.
export type CustomerState = 'confirmed' | 'pending' | 'review' | 'not_confirmed' | 'cancelled';
type View = { state: CustomerState; label: string; needsPerson: boolean };
const VIEWS: Record<string, View> = {
BOOKED: { state: 'confirmed', label: 'Confirmed', needsPerson: false },
RQ: { state: 'pending', label: 'Pending confirmation', needsPerson: false },
PENDING_BOOK: { state: 'pending', label: 'Pending confirmation', needsPerson: false },
PRICE_ERROR: { state: 'review', label: 'Under review', needsPerson: true },
BOOK_ERROR: { state: 'not_confirmed', label: 'Not confirmed', needsPerson: false },
NOT_BOOKED: { state: 'not_confirmed', label: 'Not confirmed', needsPerson: false },
CANCELED: { state: 'cancelled', label: 'Cancelled', needsPerson: false },
};
const UNKNOWN: View = { state: 'review', label: 'Under review', needsPerson: true };
/** Maps an API status (booking level) to the customer-facing state. Unknown values are never success. */
export function customerView(apiStatus: string | null | undefined): View {
return (apiStatus && VIEWS[apiStatus]) || UNKNOWN;
}not_confirmed is not final: keep watching the booking for a while, because BOOK_ERROR and NOT_BOOKED can still turn into BOOKED.
Related
Rooms and guests
How to describe rooms and guests in Quote and Prebook, which guest details Confirm asks for, and the validation rules that reject passenger data before a booking can close.
Cancellations and refunds
How to read cancellation policies, quote the fee before you cancel, cancel without double calls, and return money to the guest as a separate step.