The booking flow
Five calls, one of them optional, take a guest from search results to a confirmed room. Every call returns a new combinationKey, and the next call must send that newest key.
At a glance
| Step | Call | Sends | Returns |
|---|---|---|---|
| 1. Quote | POST /booking/accommodations/quote | Dates, rooms, ages, hotel codes or a destination | Combinations, each with key₁ |
| 2. Quote single (optional) | POST /booking/accommodations/{accommodationId}/quote | Dates, rooms, ages | Every combination of one hotel, each with key₁ |
| 3. Confirm | POST /booking/accommodations/{accommodationId}/confirm | key₁ | Authoritative price and policies, required guest fields, key₂ |
| 4. Prebook | POST /booking/accommodations/{accommodationId}/prebook | key₂ and guest details | Re-validated offer, key₃ |
| 5. Book | POST /booking/accommodations/{accommodationId}/book | key₃ and your order id | Booking references and a status |
Before you start
Every request carries the same three headers. The token comes from Authentication. It is a custom header, not Authorization: Bearer.
| Header | Value | |
|---|---|---|
auth-token | Token from POST /authentication/authenticate | |
Accept-Encoding | gzip. Requests without it may be rejected. | |
Content-Type | application/json. JSON only, no XML. |
Three clocks run during a flow
Every response restarts the key clock. When anything expires, start again from Quote with a fresh token.
Step by step
1. Quote
Search by one of these: a list of up to 3,000 hotel codes that lie within about 200 km of each other, or a destinationId such as MAD. One 3,000-code call beats many small ones.
- Each
distributions[]entry is one room. Each person has anage. Send each guest's age at checkout, because Prebook will check it. tripTypeandfilterare optional: without them you get a hotel-only search (ONLY_HOTEL) with the default filter. The samples sendtripTypeanyway, because an explicit request is easier to read in your logs.- A stay can be at most 30 nights (
checkOutminuscheckIn). A longer one gets400. timeoutis in milliseconds, at least 3000; sendnullor leave it out for your account's default. Set your HTTP timeout well above it: quotes can take 18 seconds or more.- Prices and cancellation policies here are informational. Only Confirm guarantees them.
2. Quote single (optional)
Returns every room combination for one hotel. Use it when Quote marked a hotel quoteSingleNeeded: true, or when the guest starts from a hotel page. quoteSingleNeeded does not mean "not bookable": the combinations you already have are bookable, there are simply more. Never call Quote single in parallel for many hotels; use Quote with a code list instead.
3. Confirm
The first authoritative price and cancellation policy. The response also tells you which guest data to collect.
warnings[]can containPRICE_CHANGEorCANCELLATION_POLICIES_CHANGE. Show them and get the guest's consent again.requiredFieldlists the passenger fields Prebook needs: title, names, document, email, phone and so on. See Rooms and guests.
4. Prebook
Sends guest details and re-validates the offer with the supplier.
- Repeat the quote's rooms in the same order, with the same number of people in each.
requestedAgeis mandatory and is the age at the end of the stay. It must equal the quoted age.- Names must be in Latin script. Transliterate Arabic, Cyrillic or CJK names first.
- Compare price, meal plan and cancellation policies with Confirm before you call Book.
5. Book
Closes the booking. A 200 response is not a confirmation: branch on status, and never retry Book automatically. Save your externalReference before you call it.
Full example
The same flow end to end. Credentials come from environment variables, the token is checked once before Quote, and auditData.authToken is removed before anything is logged.
// One token and one flow. combinationKeys never leave your server.
const BASE = process.env.NAVA_BASE_URL!; // e.g. https://nava.travel/resources
export async function bookHotel(search: QuoteRequest, guests: Distribution[], orderId: string) {
const token = await getToken({ minMinutesLeft: 60 }); // never refreshed mid-flow
// 1. Quote
const quote = await post(token, '/booking/accommodations/quote', {
tripType: 'ONLY_HOTEL',
...search,
});
const hotel = quote.accommodations[0];
let key = hotel.combinations[0].combinationKey; // key 1
// 3. Confirm: authoritative price and cancellation policies
const confirm = await post(token, `/booking/accommodations/${hotel.code}/confirm`, {
accommodation: { combinationKey: key },
});
if (confirm.warnings?.length) await askGuestToAccept(confirm.warnings);
key = confirm.accommodation.combination.combinationKey; // key 2
// 4. Prebook: same rooms, same order, ages at the end of the stay
const prebook = await post(token, `/booking/accommodations/${hotel.code}/prebook`, {
accommodation: { combinationKey: key },
distributions: guests,
});
assertSameOffer(confirm, prebook); // price, meal plan, cancellation policies
key = prebook.accommodation.combination.combinationKey; // key 3
// 5. Book: called once, never retried
await saveOrder(orderId);
const booked = await post(token, `/booking/accommodations/${hotel.code}/book`, {
accommodation: { combinationKey: key },
externalReference: orderId,
});
return handleBookStatus(booked);
}
async function post(token: string, path: string, body: unknown) {
const res = await fetch(BASE + path, {
method: 'POST',
headers: {
'auth-token': token,
'Accept-Encoding': 'gzip',
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
const json = await res.json();
if (json?.auditData?.authToken) json.auditData.authToken = '[REDACTED]';
log(path, res.status, json.auditData?.traceId);
if (!res.ok) throw new ApiError(res.status, json);
return json;
}Requests and responses
The headers every call sends, the auditData block most responses carry, trace ids, token redaction, errors, retries, date formats, timeouts, currency and lenient parsing.
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.