Reading bookings
Fetch a whole trip with getBookings, find its hotel services, passengers and money, and design a downstream sync that survives cancellations, rebookings and repricing.
A booking is a trip. It holds one or more hotel services, the passengers, the money and an audit trail. Read it with getBookings when a webhook arrives, when you reconcile, or whenever your back office needs the current state.
Fetch the whole trip
Returns BookedTripVO, the whole trip for every service type. The only query parameter is lang. Any valid token works, so a back-office process can keep its own cached token instead of using a booking-flow token. See the Get booking reference.
curl -s --compressed \
"$NAVA_BASE_URL/booking/getBookings/$NAVA_MICROSITE_ID/TST-1464?lang=EN" \
-H "auth-token: $TOKEN" \
-H "Accept-Encoding: gzip" \
| jq '{bookingReference, status, hotels: [.hotelservice[] | {bookingReference, status, hotelName}]}'{
"auditData": { "authToken": "[REDACTED]", "traceId": "D4AE54C6-…" },
"bookingReference": "TST-1464",
"status": "BOOKED",
"tripType": "ONLY_HOTEL",
"distribution": [
{
"id": "TST-1464-0",
"person": [
{ "id": "TST-1464-0-0", "name": "Ana", "lastName": "Ruiz", "requestedAge": 34 },
{ "id": "TST-1464-0-1", "name": "Luis", "lastName": "Ruiz", "requestedAge": 36 }
]
}
],
"agencyBookingReference": "ORDER-8812",
"hotelservice": [
{
"id": "TST-1464-0",
"bookingReference": "FAKE-1775464987",
"status": "BOOKED",
"hotelId": "MASTER-1782232",
"hotelName": "Cristine Bedfor Mahón",
"category": "S4",
"pricebreakdown": {
"totalPrice": {
"microsite": { "amount": 134.05, "currency": "EUR" },
"operator": { "amount": 134.05, "currency": "EUR" }
}
}
}
],
"manualServices": { "hotel": [] }
}How to read the response codes:
- A 404 means the reference doesn't exist in that microsite. It is not an outage. 5xx, 429 and timeouts are retryable. A 401 or 403 gets one re-authentication, then an alert.
- Look a booking up with its own microsite first. We have seen a mismatched
micrositeIdin the path ignored and the booking resolved through another microsite of the same operator. The platform has signalled that it will close this, so on a 404 fall back to your other microsites explicitly, and map all of them to one canonical key downstream.
Where things live
| You want | Read |
|---|---|
| Hotels booked through a supplier | hotelservice[] |
| Hotels an agent added by hand | manualServices.hotel[] |
| Passengers | distribution[].person[] |
| The lead contact | contactPerson |
| Money for the whole trip | pricebreakdown |
| Cancellation policies for the whole trip | cancellationPolicies[] |
| A human-readable change log | historical[] |
| Conversations on the booking | clientrequest[] |
Your own order id (externalReference at Book) | agencyBookingReference. See below. |
The trip also carries customBookingReference, creationDate, lastUpdateDate, startDate, endDate, nightsCount, adultCount, childCount, infantCount, user, payment, bookedNotes[], notes, invoices[], salesChannel, language, sourceMarket, voucherUrl and pdfs[].
Hotel services
Each hotelservice[] entry is a BookedHotelServiceVO. The fields come from the spec; the notes come from production use.
| Field | Notes |
|---|---|
id | Service id, for example TST-1464-0. The same value as accommodation.bookingReference in the Book response, so it works as the accommodationBookingReference for booking detail, Refresh, Cancel and Cancellation fee. Probably also the {serviceId} in the generic cancel service and service cancellation fee endpoints. |
bookingReference | Service-level reference issued by the supplier (a test supplier returns FAKE-…). The best stable identity for a hotel service. It is occasionally missing, so build a fallback identity. |
status | Service status. A hotel that was cancelled and rebooked keeps its CANCELED service (see below). |
hotelId, hotelName, providerHotelId, globalMappingId | Hotel code, display name, the supplier's code for the hotel, and the platform's mapping id. |
startDate, endDate, nights | Check-in and check-out, as date-time strings. |
mealPlan | A plain string in the spec, not the MealPlanVO object from the booking flow. Don't parse it as an object. |
category | HotelCategory code, such as S4. |
locationName, destinationCode, destinationName, country | Where the hotel is. |
hotelData | address, postalCode, phoneNumber, accomodationType (sic). |
room[] | One entry per room: id, roomTypeDescription and a per-room pricebreakdown. |
remarks[] | Supplier remarks. Treat them as untrusted text and strip markup before display. |
commentsToAccommodation | Plural here, singular commentToAccommodation in the booking flow. Map both. |
cancelPolicy[] | CancellationPolicyVO[], with dates in UTC. See cancellation dates. |
pricebreakdown, originalpricebreakdown | ServicePriceBreakdownVO: current amounts and amounts at booking time. See the money model. |
provider, providerDescription, providerConfigurationId, operatorProvider, apiPlatform, rate | Which supplier connection sold it. |
supplierId, supplierName | The supplier as a business entity, for payables. |
providerBookingReference | The supplier's own confirmation number, the one the hotel recognises. |
priceType | RETAIL, RETAIL_OVER, NET, NET_BASE or NET_RETAIL. |
cancelationDate (sic), providerCancellationDate, cancellationType | Set once the service is cancelled. |
confirmationErrorCause | Why it failed, for BOOK_ERROR and NOT_BOOKED. |
amendments[] | Post-booking changes: id, reference, description, priceBreakDown, originalPriceBreakDown. |
repricing | bookingReference and serviceId of the booking this service was repriced into. |
lastUpdateDate, consolidated, reav, externalCode, fiscalInformation, membershipSavedPrice | Miscellaneous. |
Service names and remarks can embed raw HTML; we have seen it in production. Strip it and cap lengths before you store text in fixed-length fields.
Cancellation dates
Booked hotels have no service-level currentCancellationType. Read the policy steps in cancelPolicy[] instead.
Passengers
- Passengers live in the booking-level
distribution[], one entry per room, each{ id, person[] }. - Person ids follow
<bookingReference>-<roomIndex>-<personIndex>, for exampleTST-1464-0-1. Every real passenger we have seen in production carried an id, so useperson.idas the passenger identity. nameis the first name andlastNamethe surname. Join them for display. Some payloads usefirstNameorfullNameinstead, so accept those too.- The trip's lead contact is also in
contactPerson(aPersonVO).
Manually added hotels
A hotel that an agent entered by hand, without booking it through a supplier, is not in hotelservice[]. It is in manualServices.hotel[]. manualServices has other keys too, such as other[] and accountingAdjustment[], and they can carry money.
Other trip fields
historical[]holds{ dateTime, message }entries: the platform's human-readable audit trail. It is the best source for "what changed" notes. It grows over time, so leave it out of change detection.originalBookingAgencyis a plain string (an agency name), both in the spec and on real bookings. It is not an agency object, so don't model it as one.- Your order id, sent as
externalReferenceat Book, comes back here asagencyBookingReference. There is noexternalReferencefield ingetBookings. Booking detail returns it asexternalReference; call it withhotelservice[].idas theaccommodationBookingReference(the supplier'shotelservice[].bookingReferenceis accepted too). customBookingReferenceis the number of the trip reference, zero-padded:00001464forTST-1464.
Cancel-and-rebook leaves two services
When an agent cancels a room and rebooks it, hotelservice[] holds two services at the same hotel and dates, with different bookingReferences.
| First service | Second service | |
|---|---|---|
bookingReference | Its own | A different one |
| Hotel and dates | Same | Same |
| Supplier | May differ | May differ |
status | CANCELED | BOOKED |
| Price | 0 | The new price |
Both are real, and the trip total stays correct. This is not a duplicate: never dedupe services by hotel and dates.
The money model
pricebreakdown on the trip (PriceBreakdownVO) and on each service (ServicePriceBreakdownVO) express every amount as a MoneyAmountVO: the same amount in the microsite's currency (microsite) and in the operator's currency (operator).
{
"microsite": { "amount": 134.05, "currency": "EUR" },
"operator": { "amount": 134.05, "currency": "EUR" }
}| Field | Meaning |
|---|---|
totalPrice | What the customer pays: the selling price. |
netProvider | The supplier's net cost. |
totalOperatorRevenue | The operator's revenue for the service, the price a B2B operator sells at. |
operatorFee, operatorManagementFee, micrositeFee, agencyFee, agencyManagerFee, agencyManagementFee, paymentFee, groundServicesManagementFee | Fee components. |
taxes, withHoldings[], countryTaxes[], taxBreakdowns[] | Taxes. |
nonCommissionableNetPrice | The part excluded from commission, such as taxes. |
pricePayableAtProperty | Paid at the hotel (resort fees, city tax). Not in the prepaid total. |
providerNetPrice (a MoneyVO), netProviderPrice, providerFee, consolidatorFee, marketPlaceFee, consolidatorNetPrice | Supplier-side detail, possibly in the supplier's currency. |
exchangeRateMicrosite, exchangeRateCommissionMicrosite | The exchange rates the platform applied. |
totalPrice is not totalOperatorRevenue. The difference is agency commission and fees.
What production teaches
Supplier currencies leak into the cost legs. We have seen customer totals in one currency while providerNetPrice.currency and netProvider.microsite carried the supplier's own currency (EUR, SAR, QAR and others). A sync that validates the currency of the total only will still fail on a cost line. Either support every currency or read the .operator legs.
{
"totalPrice": {
"microsite": { "amount": 536.2, "currency": "EUR" },
"operator": { "amount": 536.2, "currency": "EUR" }
},
"netProvider": {
"microsite": { "amount": 1650.0, "currency": "QAR" },
"operator": { "amount": 412.5, "currency": "EUR" }
},
"providerNetPrice": { "amount": 1650.0, "currency": "QAR" }
}Use the .operator legs for single-currency accounting. We recommend totalOperatorRevenue.operator for the sale and netProvider.operator for the cost, because the platform has already converted them. The customer totalPrice is then informational. Whichever legs you pick, pick them deliberately and write the choice down.
The trip total can exceed the sum of the hotel services. Booking-level fees (agency, microsite, payment and operator fees, taxes) and non-hotel or manual services mean pricebreakdown.totalPrice is often higher than the sum of hotelservice[].pricebreakdown.totalPrice. We have seen this on a meaningful share of bookings. Reconcile the difference explicitly instead of expecting equality.
Amounts change after booking, down to 0. originalpricebreakdown keeps the amounts at booking time; pricebreakdown follows repricing. We have seen an RQ service cancelled and repriced to 0 within an hour, and PRICE_ERROR both as a passing state and as a stored one. Your downstream system must accept a service going to 0.
Design a downstream sync
The pattern for an ERP, accounting or CRM sync: webhook, queue, getBookings, normalise hotelservice[], idempotent upsert. Add an hourly reference-sequence sweep for lost webhooks and microsites without one.
Identities
| Record | Key |
|---|---|
| Booking | Canonical micrositeId plus bookingReference. If several microsites can see the same booking, map them to one canonical microsite first, or one booking becomes two records. |
| Hotel service | hotelservice[].bookingReference. When it is missing, a deterministic hash of array name, index, hotel name, dates and supplier. Never key by hotel and dates. |
| Passenger | person.id |
Keep the fallback formula stable across code versions. Changing it re-keys existing records and creates duplicates.
Rules
- Detect changes with a hash of the normalised booking. Leave out volatile fields such as
historical[]andauditData, so audit-trail growth alone doesn't count as a change. - Upsert idempotently by the keys above. A crash between two writes must be safe to replay.
- Keep
CANCELEDlines. Map them to zero or neutralised lines instead of deleting them, so accounting keeps the history. - Let statuses settle. A non-final status is often followed by a later change. Don't zero a value on first sight; alert when a non-final status lingers for days while it still carries value.
- Redact
auditData.authTokenbefore you store raw payloads. It echoes a live token. - Strip HTML and cap lengths in names and remarks before you store them.
import { createHash } from 'node:crypto';
// Sorted keys, so the same data always gives the same hash.
function stableStringify(value: unknown): string {
if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
if (value && typeof value === 'object') {
const obj = value as Record<string, unknown>;
return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(',')}}`;
}
return JSON.stringify(value);
}
// Audit-trail growth alone is not a change.
export function changeHash(trip: any): string {
const { historical, auditData, ...rest } = trip;
return createHash('sha256').update(stableStringify(rest)).digest('hex');
}
// Never change this formula once records exist: it would re-key them.
export function serviceKey(s: any, index: number): string {
if (s.bookingReference) return s.bookingReference;
const basis = ['hotelservice', index, s.hotelName, s.startDate, s.endDate, s.supplierId].join('|');
return 'fallback-' + createHash('sha256').update(basis).digest('hex').slice(0, 32);
}
export async function upsertTrip(canonicalMicrositeId: string, trip: any) {
const bookingKey = `${canonicalMicrositeId}:${trip.bookingReference}`;
const hash = changeHash(trip);
if ((await db.lastHash(bookingKey)) === hash) return; // nothing but the audit trail moved
await db.upsertBooking(bookingKey, { status: trip.status, raw: trip }); // authToken already redacted
for (const [i, s] of (trip.hotelservice ?? []).entries()) {
await db.upsertServiceLine(bookingKey, serviceKey(s, i), {
status: s.status, // CANCELED lines stay: zero or neutralise them, never delete
sale: s.pricebreakdown?.totalOperatorRevenue?.operator,
cost: s.pricebreakdown?.netProvider?.operator,
});
}
for (const room of trip.distribution ?? []) {
for (const p of room.person ?? room.persons ?? []) {
await db.upsertPassenger(bookingKey, p.id, { firstName: p.name ?? p.firstName, lastName: p.lastName });
}
}
await db.saveHash(bookingKey, hash); // last, so a crash before this line replays the whole sync
}db stands for your own storage layer. The sketch reads hotel services and passengers only: add manualServices and the other service arrays if your accounting needs them.
Related
- Webhooks: when to fetch, and the sweep for lost webhooks.
- Get booking: the
getBookingsreference. - Booking detail: the per-hotel view that returns
externalReference. - Booking statuses: what each service status means.
- Cancellations: policies, fees and refunds.
Webhooks
The API calls your endpoint when a booking is created, modified, cancelled or refunded, or gets a client request. The call is only a signal, so fetch the booking to see what changed.
Hotel catalogue
Build and sync a local copy of the hotel catalogue for hotel pages, maps, filters, autocomplete and SEO. Prices never come from the catalogue, only from Quote.