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.
Every endpoint shares the same headers, the same audit block and the same thin error model. This page covers those conventions once; the API reference covers each endpoint's fields.
Headers
| Header | Value | Send it on | |
|---|---|---|---|
auth-token | The token from POST /authentication/authenticate. A custom header, not Authorization: Bearer. | Every call except authenticate | |
Accept-Encoding | gzip. Mandatory: requests without it may be rejected. | Every call | |
Content-Type | application/json. The API is JSON only; there is no XML. | Every call with a body |
POST /resources/booking/accommodations/quote HTTP/1.1
Host: nava.travel
auth-token: eyJhbGciOiJIUzUxMiJ9.…
Accept-Encoding: gzip
Content-Type: application/jsonThe auditData block
Most responses carry an auditData object.
These responses have no auditData, even on success:
- Authenticate, which returns only
tokenandexpirationInSeconds. - Meal plans and both facilities lists.
- Provider configurations, whose body is a bare array.
- The bookings listing (
GET /booking/bookings). - Schema-validation errors (a
400whose message is a field path, such asquote.request.quote.stay.checkIn: must not be null) and permission errors (401).
Errors raised after validation do carry auditData: a stay longer than 30 nights, a missing guest field at Prebook, an unknown booking reference.
| Field | Type | What it is |
|---|---|---|
timestamp | date-time | When the platform answered, for example 2026-10-09 07:58:28. |
processTime | integer, ms | Time the platform spent on the call. |
authToken | string | Your live token, echoed back. Redact it before you log or store the response. |
traceId | string | The trace id of your token: every call made with the same token returns the same value. Log it every time. See Trace ids. |
availabilityId | integer | Ties together the calls of one quote flow. |
server | string | Server identifier. |
{
"auditData": {
"timestamp": "2026-10-09 07:58:28",
"processTime": 18099,
"authToken": "[REDACTED]",
"traceId": "D4AE54C6-FAED-40D8-9B9F-1B8555459610",
"availabilityId": 188,
"server": "…"
},
"total": 2,
"accommodations": ["…"]
}Trace ids
Responses carry up to three ids.
| Id | Where you find it | What it identifies |
|---|---|---|
auditData.traceId | The body of most responses | Your token. Every call made with one token returns the same value. |
Travelc-Trace-Id header | Authenticate, and the POST, PUT and DELETE booking calls, errors included | The same value as auditData.traceId. Authenticate's header is the trace id of the token it issues. |
x-request-id header | Every response | This one request. |
Because a booking flow runs on one token, the trace id names the flow: Quote, Confirm, Prebook, Book, and the detail, fee, Cancel and Refresh calls made with that token all share it. A new token gets a new trace id. To point support at one call, give the trace id together with that call's x-request-id, the operation and the time.
- Log on every call: the operation name, the HTTP status, the trace id, the
x-request-idand the time. - Store with each booking: the trace id,
bookingReference,accommodation.bookingReferenceand yourexternalReference. - Calls without a trace id. Meal plans, facilities, provider configurations and the bookings listing return neither
auditDatanorTravelc-Trace-Id. For these, support needs the full request and response, so keep both in your logs (with the token redacted).
Redact the token before you log
auditData.authToken echoes your live token in every response. Replace it with "[REDACTED]" as soon as you parse a response, before the body can reach a log line, an error object, an analytics event or your database. The fetch wrapper below does this in one place.
Two more things to keep out of logs and browsers:
combinationKeyvalues can contain net prices and commission data. Keep them on your server and give your front end an id of your own.- Guest data (names, emails, phones, documents, birth dates). Store only what you need and redact it from logs and analytics.
Errors
The spec documents no error schema and no status codes: every accommodation operation has only a default response. So:
- Treat any non-2xx response as a failure.
- Keep the raw body and the trace id with the error.
- Name the failure with the operation and the status (
quote 401,bookingDetail 404). A generic "unclassified error" costs hours.
An error is a small JSON body with an error array and a status. Validation and permission errors stop there; errors raised later also carry auditData, with the trace id.
{
"error": ["quote.request.quote.stay.checkIn: must not be null"],
"status": "BAD_REQUEST"
}400names the field that failed as a path, such asquote.request.filter.maxCombinations: must be less than or equal to 60. Some messages include internal class names and the values you sent, so log them but don't show them to guests.401on authenticate,User not authorized to access, means the credentials were rejected: a wrong password or a deleted API user.401on any other call,User … not allowed to access here, means your token is valid but your account isn't enabled for that endpoint in this environment. A new token won't help: ask your Nava account manager.- Request validation runs before the permission check, so a malformed request gets
400even from an account that can't call the endpoint.
An unknown booking reference returns 404 with status: NOT_FOUND and Booking reference not found.
What to retry
| Status | What it means | What to do | |
|---|---|---|---|
400 | The request is malformed: a missing micrositeId on authenticate, or a field that fails validation. | Fix the request. Don't retry. | |
401, 403 | Not authorised. | Re-authenticate once and retry once, then alert. In a booking flow, a new token means a new flow from Quote. Don't retry not allowed to access here: the account needs enabling. Don't retry the sandbox's accepts test accounts only either: those are production credentials on the sandbox URL. | |
404 | Doesn't exist, in this microsite. Not an outage. | Don't retry. Check the reference and the microsite. | |
406 | On Refresh: the supplier doesn't support refreshing. | Don't retry. Read booking detail instead. | |
429, 5xx, timeout | Transient. | Retry reads and quotes, with backoff. Never Book or Cancel (below). |
Dates
| Where | Format | Example | |
|---|---|---|---|
Booking flow: checkIn, checkOut, birthDate, cancellation policy dates | yyyy-MM-dd | 2026-11-10 | |
Bookings listing (GET /booking/bookings): from, to | yyyyMMdd | 20261110 | |
Bookings listing: fromtime, totime | HH:mm:ss | 09:30:00 | |
auditData.timestamp | date and time | 2026-10-09 07:58:28 |
The listing's from and to filter on the booking's creation date, not the stay dates.
Timeouts
Some calls wait on suppliers and take many seconds. Set your HTTP client timeouts per operation, and show progress in your UI.
| Call | HTTP timeout | Observed in the docs examples |
|---|---|---|
| Quote, Quote single | The request's timeout plus 30 s, or more | 18 s |
| Book | 120 s or more | 11 s |
| Cancel | 120 s or more | 9 s |
A Quote's timeout field (milliseconds) caps how long the platform waits for suppliers. It must be at least 3000: 2999 is rejected with 400. Send null or leave it out to use your account's default. The whole call can still run a few seconds longer, which is why your HTTP timeout needs the margin.
Currency
Prices always come in your microsite currency. There is no per-request currency, so convert for display yourself.
One exception: on booked services, supplier net amounts can come in the supplier's own currency. See Reading bookings.
Parse enums leniently
New enum values appear: new statuses, providers and trip types. Don't model enums as closed types that throw on an unknown value.
ServiceStatus has 21 values in the spec. Only seven are documented booking outcomes: BOOKED, RQ, PENDING_BOOK, PRICE_ERROR, BOOK_ERROR, NOT_BOOKED and CANCELED. Map anything else to "unknown, needs a person". Never treat an unknown status as success.
const OUTCOMES = new Set(['BOOKED', 'RQ', 'PENDING_BOOK', 'PRICE_ERROR', 'BOOK_ERROR', 'NOT_BOOKED', 'CANCELED']);
// A plain string, not a closed union: new values appear.
export function outcomeOf(status: string): string {
return OUTCOMES.has(status) ? status : 'UNKNOWN'; // route UNKNOWN to review, never to success
}See Enums for every value.
Names that vary
The spec and the documentation samples don't always spell the same field the same way. Accept every variant when you parse a response.
| Field | Variants | |
|---|---|---|
| Guests in a distribution | person in responses and the original OpenAPI definition; persons in Prebook requests and the documentation samples. Prebook accepts both. | |
| Birth date | birthDate (spec) and birthdate (some documentation samples) | |
| Comment to the hotel | commentToAccommodation in the booking flow, commentsToAccommodation on booked services | |
| Hotel category | A string such as "S4" in datasheets, an object with code and name in the booking flow |
// Read both spellings.
const guestsOf = (d: any) => d.person ?? d.persons ?? [];
const birthDateOf = (p: any) => p.birthDate ?? p.birthdate;
const categoryCode = (c: any) => (typeof c === 'string' ? c : c?.code);For the guest array you send at Prebook, see Rooms and guests.
A fetch wrapper
One function for every call: it sets the headers, redacts the token, logs the trace id and the request id, and throws an error that carries the operation, the status and both ids.
// Node 18+. fetch() decompresses gzip itself; the explicit header states the API requirement.
const BASE = process.env.NAVA_BASE_URL!.replace(/\/$/, '');
export class NavaApiError extends Error {
constructor(
readonly operation: string,
readonly status: number,
readonly traceId: string | undefined,
readonly requestId: string | undefined,
readonly body: unknown, // raw body, token already redacted
) {
super(`Nava API ${operation} failed with HTTP ${status}${traceId ? ` (traceId ${traceId})` : ''}${requestId ? ` (x-request-id ${requestId})` : ''}`);
this.name = 'NavaApiError';
}
}
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';
export async function call<T>(
operation: string, // 'quote', 'book', 'bookingDetail'...: named in every log line and error
method: Method,
path: string,
opts: { token?: string; body?: unknown; timeoutMs?: number } = {},
): Promise<T> {
const headers: Record<string, string> = { 'Accept-Encoding': 'gzip' };
if (opts.token) headers['auth-token'] = opts.token;
if (opts.body !== undefined) headers['Content-Type'] = 'application/json';
// A timeout throws here, before any status exists.
// For Book and Cancel that means "outcome unknown": reconcile, never retry.
const res = await fetch(BASE + path, {
method,
headers,
body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
signal: AbortSignal.timeout(opts.timeoutMs ?? 60_000),
});
const text = await res.text();
let json: any;
try {
json = text ? JSON.parse(text) : undefined;
} catch {
json = text; // keep a non-JSON error body as raw text
}
// Redact BEFORE the body can reach a log line, an error or storage.
if (json?.auditData?.authToken) json.auditData.authToken = '[REDACTED]';
// traceId: one per token (body, or the header on booking calls). x-request-id: one per request.
const traceId: string | undefined = json?.auditData?.traceId ?? res.headers.get('Travelc-Trace-Id') ?? undefined;
const requestId: string | undefined = res.headers.get('x-request-id') ?? undefined;
console.info(JSON.stringify({ operation, status: res.status, traceId, requestId, processTime: json?.auditData?.processTime }));
// Calls with no traceId (meal plans, facilities, provider configurations, bookings listing):
// keep the full request and response in your logs for support.
if (!res.ok) throw new NavaApiError(operation, res.status, traceId, requestId, json);
return json as T;
}// Quote: HTTP timeout = the request's timeout + 30 s
const quote = await call<QuoteResponse>('quote', 'POST', '/booking/accommodations/quote', {
token: flow.value,
body: { tripType: 'ONLY_HOTEL', ...search, timeout: 8000 }, // tripType optional, explicit is clearer
timeoutMs: 8_000 + 30_000,
});
// Booking detail: stored data, any valid token
const booking = await call<BookedResponse>('bookingDetail', 'GET', `/booking/${ref}/accommodations/${accRef}`, {
token,
});Related
Environments and credentials
The two base URLs, one for your test account and one for production, the credentials that go with each, what to expect from test suppliers, and what depends on your microsite.
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.