Quickstart
Make your first booking in your test account with cURL and jq. Authenticate, quote Madrid, confirm, prebook two adults, book, then read, price and cancel the booking.
This walk-through runs the whole booking flow in your test account from a terminal, then the post-booking calls.
You need bash, curl, jq and test credentials (username, password and micrositeId) from your Nava account manager.
Prefer Postman? The Postman collection runs the same flow, with scripts that chain the calls for you.
1. Set your environment
Keep credentials in environment variables, never in code or scripts you commit. Your test account has its own base URL, the sandbox, and your test credentials put you on a test microsite that only has test suppliers.
export NAVA_BASE_URL="https://sandbox.nava.travel/resources"
export NAVA_USERNAME="<test username from your account manager>"
export NAVA_PASSWORD="<test password>"
export NAVA_MICROSITE_ID="<your test microsite id>"2. Authenticate
POST /authentication/authenticate returns a token that lasts 7200 seconds. It goes in a custom auth-token header, not Authorization: Bearer. The H array holds the headers every later call sends, including the mandatory Accept-Encoding: gzip.
TOKEN=$(curl -s --compressed -X POST "$NAVA_BASE_URL/authentication/authenticate" \
-H 'Content-Type: application/json' -H 'Accept-Encoding: gzip' \
-d "$(jq -n '{username: env.NAVA_USERNAME, password: env.NAVA_PASSWORD, micrositeId: env.NAVA_MICROSITE_ID}')" \
| jq -r .token)
# One token for the whole flow. Don't re-authenticate until you have booked.
H=(-H "auth-token: $TOKEN" -H 'Accept-Encoding: gzip' -H 'Content-Type: application/json' --compressed)
[ -n "$TOKEN" ] && [ "$TOKEN" != "null" ] && echo "Authenticated" || echo "Authentication failed"If authentication fails, see When authentication fails.
3. Quote by destination
Search Madrid (MAD) for one room with two adults. Each distributions[] entry is one room, and each guest has an age: send the age at checkout. tripType is optional (default ONLY_HOTEL), but sending it keeps the request self-explanatory. includeOnRequestOptions: false keeps out rooms the supplier still has to accept.
timeout is how long the platform waits for suppliers, in milliseconds (minimum 3000). A quote can still take 18 seconds or more, so give curl 30 seconds more than timeout.
Q=$(curl -s "${H[@]}" --max-time 40 -X POST "$NAVA_BASE_URL/booking/accommodations/quote" -d '{
"checkIn": "2026-11-10",
"checkOut": "2026-11-14",
"distributions": [{ "persons": [{ "age": 30 }, { "age": 30 }] }],
"language": "EN",
"sourceMarket": "ES",
"tripType": "ONLY_HOTEL",
"timeout": 8000,
"filter": { "bestCombinations": true, "maxCombinations": 4, "includeOnRequestOptions": false },
"destinationId": "MAD"
}')
echo "$Q" | jq '{total, traceId: .auditData.traceId}'total is the number of hotels returned. If it is 0, check that your dates are in the future, and look at providerTraces[] in the response for supplier errors.
4. Pick a combination
Each hotel has one or more combinations: a set of rooms, a meal plan, a price and a cancellation policy. Prices and policies at this stage are informational; Confirm makes them authoritative.
# Show the first three hotels and their combinations
echo "$Q" | jq '[.accommodations[:3][] | {
code, quoteSingleNeeded,
combinations: [.combinations[] | {
price: .price.amount, currency: .price.currency, meal: .mealPlan.type,
rooms: [.rooms[].description], cancellation: .currentCancellationType.type
}]
}]'
# Take the first combination of the first hotel: this is key 1
ACC=$(echo "$Q" | jq -r '.accommodations[0].code')
KEY=$(echo "$Q" | jq -r '.accommodations[0].combinations[0].combinationKey')
echo "Hotel $ACC"The key from a Quote expires 40 minutes after the response. In a real integration, keep combinationKey values on your server: they can contain net prices. Give your front end an id of your own.
5. Confirm
Confirm returns the authoritative price and cancellation policy, and a new key. Two parts of the response need action:
warnings[]:PRICE_CHANGEorCANCELLATION_POLICIES_CHANGE. Show them to the guest and get consent again.requiredField: the guest fields Prebook needs, for the contact person, other guests and room holders.
C=$(curl -s "${H[@]}" -X POST "$NAVA_BASE_URL/booking/accommodations/$ACC/confirm" \
-d "$(jq -n --arg key "$KEY" '{accommodation: {combinationKey: $key}}')")
echo "$C" | jq '{
warnings,
requiredField,
price: .accommodation.combination.price,
cancellationPolicies: .accommodation.combination.cancellationPolicies
}'
# Key 2 replaces key 1
KEY=$(echo "$C" | jq -r '.accommodation.combination.combinationKey')6. Prebook
Send the guests in the same rooms and order as the quote. requestedAge is mandatory and must equal the quoted age. Names must be in Latin script. The first person of the first room is the contact person, so put the adult with an email and phone first. Add any other field requiredField listed.
P=$(curl -s "${H[@]}" --max-time 90 -X POST "$NAVA_BASE_URL/booking/accommodations/$ACC/prebook" \
-d "$(jq -n --arg key "$KEY" '{
accommodation: { combinationKey: $key, commentToAccommodation: "Late arrival" },
distributions: [{ persons: [
{ name: "Ana", lastName: "Ruiz", requestedAge: 30, courtesyTitle: "MRS",
email: "ana@example.com", phoneCountryCode: "+34", phone: "600000000" },
{ name: "Omar", lastName: "Haddad", requestedAge: 30, courtesyTitle: "MISTER" }
] }]
}')")
# Compare with Confirm before you book: price, meal plan, cancellation policies
echo "$P" | jq '{
warnings,
price: .accommodation.combination.price,
meal: .accommodation.combination.mealPlan.type,
cancellationPolicies: .accommodation.combination.cancellationPolicies
}'
# Key 3 replaces key 2
KEY=$(echo "$P" | jq -r '.accommodation.combination.combinationKey')Send the guest array as persons, the recommended name. Prebook also accepts person, and responses use person. Send every field Confirm's requiredField lists, or Prebook answers 400 naming the missing field.
7. Book
Book with key 3 and your own order id in externalReference (max 50 characters). In a real integration, save the order id before this call.
B=$(curl -s "${H[@]}" --max-time 180 -X POST "$NAVA_BASE_URL/booking/accommodations/$ACC/book" \
-d "$(jq -n --arg key "$KEY" '{accommodation: {combinationKey: $key}, externalReference: "ORDER-8812"}')")
echo "$B" | jq '{
bookingReference,
status,
accommodationReference: .accommodation.bookingReference,
traceId: .auditData.traceId
}'
# Both references are needed for every post-booking call
REF=$(echo "$B" | jq -r .bookingReference)
AREF=$(echo "$B" | jq -r .accommodation.bookingReference)Branch on status: BOOKED, RQ, PENDING_BOOK, PRICE_ERROR, BOOK_ERROR and NOT_BOOKED each need different handling. See Booking statuses. Test suppliers may not behave exactly like real suppliers, so don't assume a test booking closes BOOKED; ask your Nava account manager how they respond.
8. Read the booking
Booking detail returns the stored booking without calling the supplier, so it is the call to poll while a booking is RQ. It also returns your externalReference.
curl -s "${H[@]}" "$NAVA_BASE_URL/booking/$REF/accommodations/$AREF" \
| jq '{bookingReference, externalReference, status, hotel: .accommodation.name,
guests: [.distributions[] | (.person // .persons)[] | {id, name, lastName}]}'9. Check the cancellation fee
This returns what cancelling today would cost. It is a quote and cancels nothing.
curl -s "${H[@]}" "$NAVA_BASE_URL/booking/$REF/accommodations/$AREF/cancellation-fee" \
| jq '.cancellationFee'10. Cancel
DELETE cancels at the supplier and returns the booking with status: CANCELED. Like Book, it is not safe to retry blindly: after a timeout, read the booking detail before you try again.
curl -s "${H[@]}" --max-time 120 -X DELETE "$NAVA_BASE_URL/booking/$REF/accommodations/$AREF" \
| jq '{status, accommodationStatus: .accommodation.status}'A successful cancel shows CANCELED at both levels.
What you did
- Used one token from Quote to Book.
- Replaced the
combinationKeyafter Quote, Confirm and Prebook. - Read
warningsandrequiredFieldat Confirm, and compared Prebook with Confirm. - Saved both booking references and the trace id from Book.
Each response also echoes your live token in auditData.authToken. The commands above never print it; in your own code, redact it before you log anything. See Requests and responses.
Next steps
The booking flow
Each step in detail, with TypeScript and Python.
Booking statuses
What to do for BOOKED, RQ, PRICE_ERROR and the rest.
Rooms and guests
Several rooms, children, ages and required guest fields.
Authentication
Token lifetime, one token per flow and caching.
Certification
The three test bookings you need before going live.
Introduction
The Nava Hotels API lets your agency search, price and book hotels from the suppliers connected to your microsite, manage those bookings and build a hotel catalogue.
Postman collection
Download a ready-made Postman collection for the whole accommodation flow, from authentication to cancellation, with scripts that chain every call and block bookings outside your test account.