Pitfalls
Where the spec and the docs disagree, and the mistakes that break hotel integrations most often, from the booking flow to reading bookings in production. Each one comes with what to do instead.
Read the first three sections before you build your checkout, and the last two before you store bookings or sync them into another system.
Where the spec and the docs disagree
The original OpenAPI definition and the prose docs differ in a few places. Where they do, follow the "Do this" column. Rows marked Confirmed were settled with the API owner, and Nava's OpenAPI spec follows them.
| Topic | Spec says | Docs say | Do this |
|---|---|---|---|
Quote tripType | Required (TripType). | Never sent in the hotel examples. | Optional, default ONLY_HOTEL. A Quote without it returns the same hotels. |
| Passenger array and birth date | distributions[].person and birthDate. | Prebook requests send persons. Some response samples show persons and birthdate. | Send persons, the recommended name. Prebook accepts person too, and responses use person. Parse both spellings in responses. |
Quote filter | A required object. | "All filter params optional". | Optional: leave it out to get the defaults. A Quote without it returns the same hotels. |
| Facilities | accommodationFacilities[] and roomFacilities[] with translations. Catalogues at /facilites/accommodation and /facilites/room (sic). | Samples show a facilities node with includedFacilities, and includedServices and otherServices string lists. | The legacy facilities node and /facilities endpoint are deprecated. Use accommodationFacilities[] and roomFacilities[]. |
category shape | Datasheet: a string such as "S4". Booking flow: an object, { code, name }. | The same. | Normalise both into one type. |
accommodationType | Enum HOTEL, APARTMENT. | "Hotel or Apartement" (sic). | Compare case-insensitively. |
| Comment field | Booking flow: commentToAccommodation. Booked service: commentsToAccommodation. | The same. | Map both. |
| Cancellation fee | "Refund the amount of the penalty". | "Recover the cancellation fees" on the day. | Treat it as a read-only quote. It neither cancels nor refunds. |
| Image classification | "Aerial View", "Hallway-Staircase". | "AERIAL_VIEW", "HALLWAY_OR_STAIRCASE". | Normalise both sides: upper-case, replace non-alphanumerics with _, drop _OR_. Both become HALLWAY_STAIRCASE. |
Where the live API differs from the spec
Live calls in a test environment show a few more differences. Code for what the API does.
- Not every response has
auditData. The spec gives every response an audit block, but authenticate, meal plans, both facilities lists, provider configurations, the bookings listing, and validation and permission errors come without one. ReadauditDataoptionally. - The trace id belongs to the token, not to the call. Every call made with one token returns the same
traceId, so it names a booking flow. The per-request id is thex-request-idheader. Log both. See Trace ids. Travelc-Trace-Idis not on every response. It comes with authenticate and thePOST,PUTandDELETEbooking calls, errors included.GETcalls (static content, booking detail, getBookings, cancellation fee, the listing) don't send it.- The bookings listing needs
micrositeoroperator. The spec marks both optional, but a call with neither fails with400. See List bookings. - Some documented limits aren't checked up front. Rooms, people per room, hotel codes,
timeoutandmaxCombinationsare rejected with400as soon as the request arrives. The 30-night stay limit is checked before any supplier is asked. At least one adult per room, at most five children and "codes or destination, not both" are not checked up front, so check them in your own form. See Limits.
Booking flow
A timed-out Book has an unknown outcome
Book has no idempotency key. A call that timed out may still have booked, and a retry can book the room twice.
Never retry Book automatically. Save your externalReference before the call, and on a timeout reconcile first with the steps in Book. That sequence is a recommendation, not a documented procedure.
Sending an older combinationKey
Every response issues a new key, and later keys carry the step that issued them (QUOTED, CONFIRMED, PREBOOKED). An older key is invalid at the next step even before it expires.
Always send the key from the immediately preceding response. See The booking flow.
Swapping the token mid-flow
The same token is mandatory from Quote to Book. An auto-refresh interceptor that swaps it breaks the flow without an obvious error.
Only start a flow when the token has at least 60 minutes left, and never refresh inside a flow. See Authentication.
Keys expiring during checkout
Keys last 40 minutes after a Quote and 60 minutes after later steps, and every response resets the clock. A guest typing passenger details can run past that.
Show a countdown. On expiry, re-quote the same hotel with Quote single and match the same room, board and refundability. See Checkout idle too long.
Calling Quote single for many hotels in parallel
Parallel Quote single calls across many hotels are explicitly unsupported, and the docs warn that response times suffer.
Use one Quote with a list of up to 3,000 codes. Keep Quote single for one hotel at a time.
Many small Quotes instead of one large one
One Quote with 3,000 codes beats many small calls. The codes must lie within about 200 km of each other.
Group your codes by area and send each group in one call. See Quote.
Ages that don't match between Quote and Prebook
Quote takes persons[].age. Prebook takes requestedAge, the age at the end of the stay, so a child who turns 6 during the stay is 6. A child's value must be identical in both calls, or Prebook fails with "Child age different from availability".
Send the age at checkout in Quote too. If you send birthDate, make it consistent with requestedAge. See Rooms and guests.
Rooms reordered after the quote
Prebook must repeat the quote's rooms in the same order, with the same number of people in each. Reordering rooms in your UI after the quote triggers "order changed" errors.
Build the Prebook distributions from the stored quote request, not from the order of your form. See Rooms and guests.
A phone country code written with 00
phoneCountryCode is + followed by digits, such as +966. 00966 is rejected.
The API validates phone numbers with Google libphonenumber, so validate client-side with the same library. See Limits.
Identical passenger names
Two passengers with the same name in one booking are rejected.
Add a suffix such as JR for a father and son who share a name. See Validation errors.
Document details that fail validation
A document type without a number, a passport that expires before the trip ends and a document number used twice are all rejected.
Check all three before Prebook. See Validation errors.
The wrong guest first in a room
The contact person is the first person of the first room. The room holder of every other room is that room's first person.
Put an adult with an email and a phone number first. See Rooms and guests.
Ignoring warnings at Confirm or Prebook
Confirm and Prebook can return PRICE_CHANGE or CANCELLATION_POLICIES_CHANGE in warnings[]. Booking silently at a new price or on stricter terms creates disputes.
Show the change and get the guest's consent again. See Price or policy changed.
Reading onRequest as a guarantee
onRequest: true means there is no confirmed quota and the supplier must accept the booking. onRequest: false is no promise either: we have seen Book return RQ after onRequest was false throughout the flow.
Handle RQ on every booking. See Booking statuses.
Treating quoteSingleNeeded as not bookable
quoteSingleNeeded: true means more rooms exist. The combinations you already have are bookable.
Call Quote single when the guest wants to see every room, not to unlock booking.
Expecting a currency per request
Prices come in the microsite currency only, and no parameter requests another one.
Convert for display yourself. See Limits.
HTTP timeouts that are too short
In the docs examples a Quote took 18 seconds, Book 11 seconds and Cancel 9 seconds.
Set your HTTP timeout to at least the Quote timeout plus 30 seconds for quotes, and to at least 120 seconds for Book and Cancel. Show a progress indicator while the guest waits. See Limits.
Testing post-booking calls on fake bookings
A booking made with fakeBooking is not saved and not sent to suppliers, even in production. Booking detail answers 404 Booking reference not found for it.
Test post-booking calls on real bookings in your test account. See Certification.
Assuming test suppliers behave like real ones
Your test microsite only has test suppliers, and they may not behave exactly like real suppliers, for example after Refresh or Cancel. Ask your Nava account manager how they respond before your tests depend on a particular status. Some test suppliers answer every Refresh with CANCELED, which says nothing about real suppliers.
Poll with booking detail (GET) in every environment. Refresh calls the supplier.
Quoting hotels that left the catalogue
Quoting a hotel id that has disappeared from /accommodations returns nothing.
Refresh your catalogue weekly, as the docs recommend, and stop quoting hotels that drop out. See Hotel catalogue.
Security and data
combinationKey exposes net rates
The sample keys in the docs are readable HS256 JWTs, some only deflated ("zip":"DEF"), which is not encryption. Decoded, they show the net price (634.31 EUR against a selling price of 808.04 EUR in one sample), commission, marketing fee, the supplier's purchase token and the supplier's hotel and rate ids.
If your margins are confidential, never send keys to browsers or mobile apps. Keep them on your server behind an id of your own. See The booking flow.
The live token in every response
auditData.authToken echoes your live token in every response that has an audit block.
Strip it before you log or store a payload. See Conventions.
The webhook secret lives in the URL
Webhooks carry no custom headers, so the secret has to be in the URL path or query. Reverse proxies and APM tools log URLs, secret included.
Use a long random secret, mask it in logs and compare it in constant time. If it leaks, ask your Nava account manager to help you rotate it. See Webhooks.
Personal data in payloads
Request and response payloads carry names, emails, phone numbers, documents and birth dates.
Store only what you need, redact personal data before logs and analytics, and apply your GDPR or PDPL retention rules. See Prebook.
Reading bookings in production
These come from reading real bookings with getBookings and the booking endpoints. They matter most when you store bookings or sync them into accounting, ERP or CRM systems.
A booking found under the wrong microsite
We have seen getBookings/{micrositeId}/{ref} resolve a booking even when the microsite in the path didn't match.
The platform may close this, so don't depend on it. Look a booking up with its own microsite first, fall back to your other microsites on a 404, and map them all to one canonical key downstream. See Get booking.
Reconciling with the listing endpoint
GET /booking/bookings is strictly scoped to one microsite and filters by creation date (yyyyMMdd). Bookings from sibling microsites that share the same reference sequence don't appear.
Don't build reconciliation on "list and diff". Walk the reference sequence and probe each reference with getBookings. See List bookings and Reading bookings.
Cancel-and-rebook looks like a duplicate
When an agent cancels a room and rebooks it, hotelservice[] holds a CANCELED service at price 0 next to the new BOOKED one, at the same hotel and dates but with different service references.
Both are real. Don't deduplicate services by hotel and dates. See Reading bookings.
Cancellation dates one day apart
The service-level cancelPolicy date is in UTC. The booking-level cancellationPolicies date is in operator local time. Near midnight they differ by a day.
Show the booking-level date: it matches what agents see in the back office. See Cancellations.
Supplier currencies in the cost fields
providerNetPrice and netProvider.microsite can be in the supplier's currency even when the booking total is in the microsite currency. We have seen QAR, EUR, SAR, JOD, AED, IDR and CHF.
Use the .operator legs, which are already converted, or support every currency. See Reading bookings.
Service totals that don't add up
The booking total includes booking-level fees and non-hotel or manual services, so the hotel services add up to less than the booking total on a meaningful share of bookings.
Reconcile the difference explicitly rather than expecting the two to match. See Reading bookings.
Manually added hotels outside hotelservice[]
Hotels an agent adds by hand are in manualServices.hotel[], not in hotelservice[]. Other manual keys such as other and accountingAdjustment can carry money too.
Read manualServices as well when you account for the whole booking. See Get booking.
Services without a bookingReference
Some services arrive without a service-level bookingReference.
Build a deterministic fallback identity, for example from the array, index, name, dates and supplier. Keep the formula stable across releases: changing it re-keys your records and creates duplicates. See Reading bookings.
Looking for Book's references in getBookings
Book returns a trip reference (TST-1464), a service reference in accommodation.bookingReference (TST-1464-0) and echoes your externalReference. In getBookings they appear under other names:
| Book response | getBookings |
|---|---|
bookingReference | bookingReference |
accommodation.bookingReference | hotelservice[].id |
externalReference | agencyBookingReference |
hotelservice[].bookingReference is a different value: the supplier's own reference. Booking detail, Refresh, Cancel and Cancellation fee accept either service reference.
Rejecting webhook types you don't know
The webhook type can be CREATED, MODIFIED, CANCELED, CLIENT_REQUEST, REFUND or a value added later. Answering 400 to a type you don't handle causes retries, an alert email and a lost audit trail.
Answer every type with a 2xx, store the event and ignore the types you don't need. See Webhooks.
401 errors that hide the cause
A wrong password and a deleted API user return the identical 401.
When a working integration suddenly gets 401 on every call, ask your Nava account manager whether the API user still exists. Put the operation name and HTTP status in your error messages: an "unclassified error" costs hours. See Every call returns 401.
Unknown enum values
New ServiceStatus, Provider and TripType values appear over time.
Parse enums leniently and route unknown values to a person for review, never to success. See Enums.
Non-Latin passenger names
The spec validates name and lastName against ^[\p{IsLatin} .'-]+$, so Arabic, Cyrillic, Greek and CJK names fail Prebook.
Transliterate names before Prebook and keep the original script in your own records. The other field rules are in Limits.
First name and surname in separate fields
person.name is the first name and lastName is the surname. Some payloads use firstName or fullName instead.
Join name and lastName for display, and accept the other spellings too. See Reading bookings.
HTML inside text fields
Service names and remarks can contain HTML.
Strip it, treat remarks as untrusted text, and cap lengths before you store text in fixed-length fields. See Reading bookings.
Treating a 404 as an outage
A 404 means the booking doesn't exist, at least not in this microsite. It is not an outage.
On read calls such as getBookings, retry 5xx, 429 and timeouts, and re-authenticate once on 401 or 403 before you alert. Never apply this retry rule to Book or Cancel. See Get booking.
A booking that closes RQ without warning
A booking can close RQ even when onRequest was false throughout the flow.
Keep polling booking detail, or wait for a webhook, until it becomes BOOKED, CANCELED or NOT_BOOKED. See On request (RQ).
Values that change after booking
We have seen an RQ booking cancelled and repriced to 0 within an hour. PRICE_ERROR can be transient or stored.
Let statuses and amounts change after booking, and accept a service going to 0. See Booking statuses.
Related
Limits
Hard limits on rooms, guests, stay length and search size, the field rules the API validates, the clocks that run during a booking flow, and what the API does not support.
Scenario playbooks
Step-by-step handling for the situations every hotel integration meets, from a standard booking to on-request bookings, Book timeouts, cancellations, back-office sync and a sudden wave of 401 errors.