{
  "info": {
    "_postman_id": "5b0c1f6e-8d3a-4b6e-9a51-2f4c7d9e0a11",
    "name": "Nava Hotels API",
    "description": "Search, price, book and manage hotels with the Nava Hotels API, from authentication to cancellation.\n\n## Start here\n\n1. Import this collection and the **Nava Hotels API · Test** environment, and select the environment (top right).\n2. In the environment, fill in the **username**, **password** and **micrositeId** of your test account. Credentials come from your Nava account manager.\n3. Open **3. Booking flow** and send the requests in order, or right-click the collection and choose **Run collection**. A full run books one test booking and cancels it.\n\nThe scripts chain the calls for you: they save the token, forward the newest `combinationKey`, build the guests Prebook needs, and save both booking references. Open the Postman Console to see what each step picked and the ids support asks for.\n\n## Rules the collection follows\n\n- **One token per booking flow.** Every call from Quote to Book uses the same token. Quote renews a token with less than 60 minutes left; Confirm, Prebook and Book refuse a different one.\n- **Always the newest `combinationKey`.** Every step returns a new one.\n- **Branch on the Book status.** HTTP 200 alone does not mean the room is confirmed.\n- **Never re-send Book or Cancel because it timed out.** Neither is idempotent. Read the booking first.\n- **Send `Accept-Encoding: gzip`** on every call. Every request here does.\n\n## Safety\n\n- **Book, Cancel and Refresh are blocked** unless the environment's `navaEnv` is `test` and its `baseUrl` is not production. The production environment ships with `navaEnv` = `production`; to send one of these requests there, set `allowBookingWrites` to `true`, send it, and set it back to `false`.\n- **Each environment has its own base URL**: `https://sandbox.nava.travel/resources` (the sandbox) in the test environment and `https://nava.travel/resources` in the production environment. Keep test credentials in the test environment and production credentials in the production environment.\n- Responses echo your token in `auditData.authToken`. Don't share responses, exported runs or Console logs without removing it.\n- `combinationKey` values can contain net prices. In a real integration, keep them on your server.\n\n## Learn more\n\n[Quickstart](https://nava-hotels-docs.vercel.app/docs/guides/quickstart), [Booking flow](https://nava-hotels-docs.vercel.app/docs/guides/booking-flow), [Booking statuses](https://nava-hotels-docs.vercel.app/docs/guides/booking-statuses), [Certification](https://nava-hotels-docs.vercel.app/docs/guides/certification). The OpenAPI specification is at `/openapi.json` on the docs site.",
    "version": "1.0.0",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "auth-token",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{authToken}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Token handling for every request.",
          "// - Outside a booking flow, a missing or expiring token is renewed automatically.",
          "// - Quote, Quote single and Quote by hotel codes start a flow. They renew a token that has less than 60 minutes",
          "//   left, and remember it as the flow's token.",
          "// - Confirm, Prebook and Book must use that same token, so they never renew it. A new token means a new Quote.",
          "const path = pm.request.url.getPath();",
          "// Newman sends a request even when this script fails, so point a failed request at an address that never resolves.",
          "const stop = (message) => {",
          "  pm.request.url.update('https://blocked.invalid/not-sent');",
          "  throw new Error(message);",
          "};",
          "if (!/\\/authentication\\/authenticate$/.test(path)) {",
          "  const token = pm.collectionVariables.get('authToken');",
          "  const minutesLeft = (Number(pm.collectionVariables.get('authTokenExpiresAt') || 0) - Date.now()) / 60000;",
          "  const startsFlow = /\\/booking\\/accommodations\\/(quote|[^/]+\\/quote)$/.test(path);",
          "  const inFlow = /\\/booking\\/accommodations\\/[^/]+\\/(confirm|prebook|book)$/.test(path);",
          "  if (inFlow) {",
          "    if (!token || token !== pm.collectionVariables.get('flowAuthToken')) {",
          "      stop('No booking flow in progress with the current token. Run Quote again: every call from Quote to Book must use the same token.');",
          "    }",
          "    if (minutesLeft <= 0) stop('The token expired during the booking flow. Run Quote again.');",
          "  } else if (!token || minutesLeft < (startsFlow ? 60 : 2)) {",
          "    const credentials = { username: pm.variables.get('username'), password: pm.variables.get('password'), micrositeId: pm.variables.get('micrositeId') };",
          "    if (!credentials.username || !credentials.password || !credentials.micrositeId) {",
          "      stop('Select a Nava environment (top right) and fill in username, password and micrositeId.');",
          "    }",
          "    pm.sendRequest(",
          "      {",
          "        url: pm.variables.replaceIn('{{baseUrl}}/authentication/authenticate'),",
          "        method: 'POST',",
          "        header: [",
          "          { key: 'Content-Type', value: 'application/json' },",
          "          { key: 'Accept-Encoding', value: 'gzip' },",
          "        ],",
          "        body: { mode: 'raw', raw: JSON.stringify(credentials) },",
          "      },",
          "      (err, r) => {",
          "        if (err || r.code !== 200) {",
          "          console.error('Automatic authentication failed: ' + (err ? err.message : 'HTTP ' + r.code + ' ' + r.text()));",
          "          return;",
          "        }",
          "        const j = r.json();",
          "        pm.collectionVariables.set('authToken', j.token);",
          "        pm.collectionVariables.set('authTokenExpiresAt', String(Date.now() + (Number(j.expirationInSeconds) || 7200) * 1000));",
          "        if (startsFlow) pm.collectionVariables.set('flowAuthToken', j.token);",
          "        console.log('Authenticated automatically. The new token is valid for ' + (Number(j.expirationInSeconds) || 7200) + ' seconds.');",
          "      },",
          "    );",
          "  } else if (startsFlow) {",
          "    pm.collectionVariables.set('flowAuthToken', token);",
          "  }",
          "}"
        ]
      }
    },
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Log the ids support asks for. auditData.traceId belongs to the token; x-request-id identifies this one request.",
          "if (!pm.response || !pm.response.code) {",
          "  console.warn(pm.info.requestName + ': not sent. See the pre-request script error.');",
          "} else {",
          "  let j = null;",
          "  try {",
          "    j = pm.response.json();",
          "  } catch (e) {",
          "    j = null;",
          "  }",
          "  const traceId = j && !Array.isArray(j) && j.auditData ? j.auditData.traceId : undefined;",
          "  const requestId = pm.response.headers.get('x-request-id');",
          "  if (traceId) pm.collectionVariables.set('lastTraceId', traceId);",
          "  if (requestId) pm.collectionVariables.set('lastRequestId', requestId);",
          "  console.log(pm.info.requestName + ': HTTP ' + pm.response.code + ', ' + pm.response.responseTime + ' ms' + (traceId ? ', traceId ' + traceId : '') + (requestId ? ', x-request-id ' + requestId : ''));",
          "  if (pm.response.code === 401) {",
          "    console.warn('401: the token is missing or expired, or this API user may not call this endpoint. If every call returns 401, check your credentials with your Nava account manager.');",
          "  }",
          "  if (pm.response.code >= 400 && j && j.error) console.warn('Error: ' + [].concat(j.error).join(' | '));",
          "}"
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "language",
      "value": "EN",
      "type": "string",
      "description": "Language for names and descriptions, for example EN."
    },
    {
      "key": "sourceMarket",
      "value": "AE",
      "type": "string",
      "description": "The country your customer books from (ISO 3166-1 alpha-2). It can change prices and availability."
    },
    {
      "key": "destinationId",
      "value": "MAD",
      "type": "string",
      "description": "Destination for Quote. Destinations replaces it when your microsite has no such destination."
    },
    {
      "key": "checkIn",
      "value": "",
      "type": "string",
      "description": "Check-in date, yyyy-MM-dd. Empty: 42 days from today."
    },
    {
      "key": "nights",
      "value": "4",
      "type": "string",
      "description": "Length of stay in nights, at most 30."
    },
    {
      "key": "fakeBookingStatus",
      "value": "",
      "type": "string",
      "description": "Empty for a real test booking. BOOK_ERROR or BOOKED forces that outcome; a fake booking is not saved."
    },
    {
      "key": "productionBaseUrl",
      "value": "https://nava.travel/resources",
      "type": "string",
      "description": "Used by the safety guard: Book, Cancel and Refresh never run against this base URL unless allowBookingWrites is true."
    },
    {
      "key": "authToken",
      "value": "",
      "type": "string",
      "description": "Set by Authenticate or automatically. Sent in the auth-token header."
    },
    {
      "key": "authTokenExpiresAt",
      "value": "",
      "type": "string",
      "description": "Set automatically: when authToken expires (epoch milliseconds)."
    },
    {
      "key": "flowAuthToken",
      "value": "",
      "type": "string",
      "description": "Set automatically: the token the current booking flow started with."
    },
    {
      "key": "accommodationId",
      "value": "",
      "type": "string",
      "description": "Set by Quote: the hotel code of the picked combination."
    },
    {
      "key": "combinationKey",
      "value": "",
      "type": "string",
      "description": "Set by Quote, Quote single, Confirm and Prebook: always the newest key. It can contain net prices: keep it on your server in a real integration."
    },
    {
      "key": "quoteCheckIn",
      "value": "",
      "type": "string",
      "description": "Set by Quote."
    },
    {
      "key": "quoteCheckOut",
      "value": "",
      "type": "string",
      "description": "Set by Quote."
    },
    {
      "key": "quoteDistributions",
      "value": "",
      "type": "string",
      "description": "Set by Quote: the rooms and ages it sent."
    },
    {
      "key": "quotedOffer",
      "value": "",
      "type": "string",
      "description": "Set by Quote: the picked combination."
    },
    {
      "key": "confirmedOffer",
      "value": "",
      "type": "string",
      "description": "Set by Confirm: price, meal plan and cancellation policies."
    },
    {
      "key": "requiredField",
      "value": "",
      "type": "string",
      "description": "Set by Confirm: the guest fields Prebook must send."
    },
    {
      "key": "prebookDistributions",
      "value": "",
      "type": "string",
      "description": "Set by Prebook: the guests it sends."
    },
    {
      "key": "externalReference",
      "value": "",
      "type": "string",
      "description": "Set by Book: your order id for this booking."
    },
    {
      "key": "bookingReference",
      "value": "",
      "type": "string",
      "description": "Set by Book: the trip reference."
    },
    {
      "key": "accommodationBookingReference",
      "value": "",
      "type": "string",
      "description": "Set by Book: the hotel's service reference."
    },
    {
      "key": "bookingStatus",
      "value": "",
      "type": "string",
      "description": "Set by Book, Cancel and Refresh."
    },
    {
      "key": "catalogueId1",
      "value": "",
      "type": "string",
      "description": "Set by Accommodation list."
    },
    {
      "key": "catalogueId2",
      "value": "",
      "type": "string",
      "description": "Set by Accommodation list."
    },
    {
      "key": "catalogueIdsJson",
      "value": "",
      "type": "string",
      "description": "Set by Accommodation list: up to 20 hotel codes as a JSON array."
    },
    {
      "key": "bookingsFrom",
      "value": "",
      "type": "string",
      "description": "Set by List bookings (yyyyMMdd)."
    },
    {
      "key": "bookingsTo",
      "value": "",
      "type": "string",
      "description": "Set by List bookings (yyyyMMdd)."
    },
    {
      "key": "lastTraceId",
      "value": "",
      "type": "string",
      "description": "Set after every call that returns auditData.traceId."
    },
    {
      "key": "lastRequestId",
      "value": "",
      "type": "string",
      "description": "Set after every call: the x-request-id header."
    }
  ],
  "item": [
    {
      "name": "1. Authentication",
      "description": "Get the token every other request sends in the `auth-token` header.",
      "item": [
        {
          "name": "Authenticate",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (!v('username') || !v('password') || !v('micrositeId')) {",
                  "  stop('Select a Nava environment (top right) and fill in username, password and micrositeId.');",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  const j = res();",
                  "  pm.test('Token returned', () => pm.expect(j.token).to.be.a('string').that.is.not.empty);",
                  "  if (j.token) {",
                  "    setv('authToken', j.token);",
                  "    setv('authTokenExpiresAt', String(Date.now() + (Number(j.expirationInSeconds) || 7200) * 1000));",
                  "    setv('flowAuthToken', '');",
                  "    console.log('Token saved. It lasts ' + j.expirationInSeconds + ' seconds; one token must carry a whole booking flow.');",
                  "  }",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"username\": \"{{username}}\",\n  \"password\": \"{{password}}\",\n  \"micrositeId\": \"{{micrositeId}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/authentication/authenticate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "authentication",
                "authenticate"
              ]
            },
            "description": "Returns a token that lasts 7200 seconds. The collection sends it in the `auth-token` header (see the collection's Authorization tab), not `Authorization: Bearer`.\n\nYou don't have to run this first: any request renews a missing or expired token automatically, except Confirm, Prebook and Book, which must use the token their Quote started with.\n\nExchange your API username, password and microsite id for a token that lasts 7200 seconds. Send it in the `auth-token` header on every other call, and use the same token for a whole booking flow.\n\nReference: [Authenticate](https://nava-hotels-docs.vercel.app/docs/api/authenticate)"
          },
          "response": []
        }
      ]
    },
    {
      "name": "2. Hotel catalogue",
      "description": "Static content for your own catalogue: destinations, hotels, datasheets, facilities and meal plans. Never a source of prices or availability.",
      "item": [
        {
          "name": "Destinations",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  const list = res().destination || [];",
                  "  pm.test('Destinations returned: ' + list.length, () => pm.expect(list.length).to.be.above(0));",
                  "  const codes = list.map((d) => d.code);",
                  "  if (codes.length && codes.indexOf(v('destinationId')) === -1) {",
                  "    const next = codes.indexOf('MAD') !== -1 ? 'MAD' : codes[0];",
                  "    console.warn('destinationId \"' + v('destinationId') + '\" is not on your microsite. Using \"' + next + '\" instead.');",
                  "    setv('destinationId', next);",
                  "  }",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/destination/{{micrositeId}}?lang={{language}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "destination",
                "{{micrositeId}}"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{language}}"
                },
                {
                  "key": "countryCode",
                  "value": "ES",
                  "disabled": true,
                  "description": "Tick to list one country only. The full list can run to several megabytes."
                }
              ]
            },
            "description": "Also checks `destinationId`: if your microsite has no such destination, it switches to `MAD` or the first destination returned, so Quote searches somewhere that exists.\n\nA destination's `code` is exactly what Quote expects in `destinationId`. Lists the destinations of your microsite, optionally by country or airport.\n\nReference: [List destinations](https://nava-hotels-docs.vercel.app/docs/api/destinations)"
          },
          "response": []
        },
        {
          "name": "Destination",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/destination/{{micrositeId}}/{{destinationId}}?lang={{language}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "destination",
                "{{micrositeId}}",
                "{{destinationId}}"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{language}}"
                }
              ]
            },
            "description": "Returns one destination with the same fields as the list, plus its `description`.\n\nReference: [Get a destination](https://nava-hotels-docs.vercel.app/docs/api/destinations)"
          },
          "response": []
        },
        {
          "name": "Countries",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/destination/countries/{{micrositeId}}?lang={{language}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "destination",
                "countries",
                "{{micrositeId}}"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{language}}"
                }
              ]
            },
            "description": "Returns the countries of your microsite, each with an `id` and a `name`.\n\nReference: [List countries](https://nava-hotels-docs.vercel.app/docs/api/destinations)"
          },
          "response": []
        },
        {
          "name": "Meal plans",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/mealplan/{{micrositeId}}?lang={{language}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "mealplan",
                "{{micrositeId}}"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{language}}"
                }
              ]
            },
            "description": "Show guests the `description`, not the `type`: mixed plans collapse to the nearest type. Returns the meal plans configured on your microsite, to label the `mealPlan.id` codes from Quote.\n\nReference: [Meal plans](https://nava-hotels-docs.vercel.app/docs/api/meal-plans)"
          },
          "response": []
        },
        {
          "name": "Accommodation list",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  const list = res().accommodations || [];",
                  "  pm.test('Hotels returned: ' + list.length, () => pm.expect(list.length).to.be.above(0));",
                  "  const ids = list.map((a) => String(a.id)).filter(Boolean);",
                  "  if (ids.length) {",
                  "    setv('catalogueId1', ids[0]);",
                  "    setv('catalogueId2', ids[1] || ids[0]);",
                  "    setv('catalogueIdsJson', ids.slice(0, 20));",
                  "    console.log('Saved hotel codes ' + ids.slice(0, 2).join(', ') + ' for the datasheet requests, and the first 20 for Quote by hotel codes.');",
                  "  }",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accommodations?first=0&limit=100",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accommodations"
              ],
              "query": [
                {
                  "key": "first",
                  "value": "0"
                },
                {
                  "key": "limit",
                  "value": "100"
                }
              ]
            },
            "description": "Fetches the first 100 hotels only; a full sync pages through up to 20000 per call. Saves hotel codes for the datasheet requests and for Quote by hotel codes.\n\nDownload the list weekly and fetch a datasheet again only when its `lastUpdate` changed; never take prices from it. Pages through every hotel your microsite can sell, up to 20000 per page.\n\nReference: [Accommodation list](https://nava-hotels-docs.vercel.app/docs/api/accommodations)"
          },
          "response": []
        },
        {
          "name": "Datasheet",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (!v('catalogueId1')) {",
                  "  if (!v('accommodationId')) stop('Run Accommodation list (or Quote) first, or set catalogueId1 to a hotel code.');",
                  "  setv('catalogueId1', v('accommodationId'));",
                  "}",
                  "if (!v('catalogueId2')) setv('catalogueId2', v('catalogueId1'));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accommodations/{{catalogueId1}}/datasheet?lang={{language}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accommodations",
                "{{catalogueId1}}",
                "datasheet"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{language}}"
                }
              ]
            },
            "description": "Static content only, never prices or availability. Returns the full datasheet of one hotel: images, facilities, description, ratings and category.\n\nReference: [Datasheet](https://nava-hotels-docs.vercel.app/docs/api/datasheets)"
          },
          "response": []
        },
        {
          "name": "Datasheets (batch)",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (!v('catalogueId1')) {",
                  "  if (!v('accommodationId')) stop('Run Accommodation list (or Quote) first, or set catalogueId1 to a hotel code.');",
                  "  setv('catalogueId1', v('accommodationId'));",
                  "}",
                  "if (!v('catalogueId2')) setv('catalogueId2', v('catalogueId1'));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accommodations/datasheet?accommodationId={{catalogueId1}}&accommodationId={{catalogueId2}}&lang={{language}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accommodations",
                "datasheet"
              ],
              "query": [
                {
                  "key": "accommodationId",
                  "value": "{{catalogueId1}}"
                },
                {
                  "key": "accommodationId",
                  "value": "{{catalogueId2}}"
                },
                {
                  "key": "lang",
                  "value": "{{language}}"
                }
              ]
            },
            "description": "Up to 100 hotels per call: repeat `accommodationId` once per hotel and never comma-join the ids. Returns the datasheets of those hotels.\n\nReference: [Datasheets](https://nava-hotels-docs.vercel.app/docs/api/datasheets)"
          },
          "response": []
        },
        {
          "name": "Preferred hotels",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accommodations/preferred/{{micrositeId}}?lang={{language}}&limit=10",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accommodations",
                "preferred",
                "{{micrositeId}}"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{language}}"
                },
                {
                  "key": "limit",
                  "value": "10"
                }
              ]
            },
            "description": "Static content only: quote the hotels before you show a price. Returns the hotels your microsite promotes, as full datasheets.\n\nReference: [Preferred hotels](https://nava-hotels-docs.vercel.app/docs/api/preferred)"
          },
          "response": []
        },
        {
          "name": "Accommodation facilities",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/facilites/accommodation?lang={{language}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "facilites",
                "accommodation"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{language}}"
                }
              ]
            },
            "description": "The path is spelled `facilites` (sic) and `lang` is required. Returns the hotel facility catalogue, with icon classes, priorities and translated labels.\n\nReference: [Accommodation facilities](https://nava-hotels-docs.vercel.app/docs/api/facilities)"
          },
          "response": []
        },
        {
          "name": "Room facilities",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/facilites/room?lang={{language}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "facilites",
                "room"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{language}}"
                }
              ]
            },
            "description": "The path is spelled `facilites` (sic) and `lang` is required. Returns the room facility catalogue; each facility also has a `category`.\n\nReference: [Room facilities](https://nava-hotels-docs.vercel.app/docs/api/facilities)"
          },
          "response": []
        },
        {
          "name": "Provider configurations",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200, or 401 when your API user may not call it', () => pm.expect(pm.response.code).to.be.oneOf([200, 401]));",
                  "  if (pm.response.code === 401) console.warn('Your API user may not call Provider configurations. Ask your Nava account manager which suppliers your microsite has.');",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/providers/configurations/{{micrositeId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "providers",
                "configurations",
                "{{micrositeId}}"
              ]
            },
            "description": "Check it first when a destination or hotel search returns nothing. Lists the supplier connections on your microsite; the body is a bare array with no `auditData` and there is no `Travelc-Trace-Id` header, so log the `x-request-id` header and keep the full request and response for support.\n\nReference: [Provider configurations](https://nava-hotels-docs.vercel.app/docs/api/provider-configurations)"
          },
          "response": []
        }
      ]
    },
    {
      "name": "3. Booking flow",
      "description": "Quote > Quote single (optional) > Confirm > Prebook > Book. Run the requests in this order.\n\n- Every step returns a new `combinationKey`; the scripts always forward the newest one.\n- One token carries the whole flow. Quote renews a token with less than 60 minutes left; Confirm, Prebook and Book refuse to run with a different token.",
      "item": [
        {
          "name": "Quote",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "// checkIn defaults to 42 days from today and the stay to `nights` nights (default 4).",
                  "const today = new Date().toISOString().slice(0, 10);",
                  "const checkIn = v('checkIn') || addDays(today, 42);",
                  "setv('quoteCheckIn', checkIn);",
                  "setv('quoteCheckOut', addDays(checkIn, Number(v('nights') || 4)));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  // Prefer a combination that is not on request and is fully refundable, so a test booking is cheap to cancel.",
                  "  const pickCombination = (hotels, fallbackCode) => {",
                  "    const all = [];",
                  "    (hotels || []).forEach((h) => (h.combinations || []).forEach((c) => all.push({ code: h.code || fallbackCode, c })));",
                  "    const score = (x) => (x.c.onRequest ? 2 : 0) + (x.c.currentCancellationType && x.c.currentCancellationType.type === 'REFUNDABLE' ? 0 : 1);",
                  "    return all.sort((a, b) => score(a) - score(b))[0] || null;",
                  "  };",
                  "  const storeQuote = (hotels, fallbackCode) => {",
                  "    // Prebook must repeat the rooms and ages of the quote, so remember what this request sent.",
                  "    try {",
                  "      const sent = JSON.parse(pm.variables.replaceIn(pm.request.body.raw));",
                  "      if (Array.isArray(sent.distributions)) setv('quoteDistributions', sent.distributions);",
                  "    } catch (e) {",
                  "      console.warn('Could not read the distributions from the request body: ' + e.message);",
                  "    }",
                  "    const pick = pickCombination(hotels, fallbackCode);",
                  "    if (!pick) return null;",
                  "    setv('accommodationId', String(pick.code));",
                  "    setv('combinationKey', pick.c.combinationKey);",
                  "    setv('quotedOffer', { hotel: pick.code, price: pick.c.price, mealPlan: pick.c.mealPlan && pick.c.mealPlan.type, cancellation: pick.c.currentCancellationType });",
                  "    const price = pick.c.price ? pick.c.price.amount + ' ' + pick.c.price.currency : 'no price';",
                  "    const cancel = pick.c.currentCancellationType ? pick.c.currentCancellationType.type : 'unknown cancellation type';",
                  "    console.log('Picked hotel ' + pick.code + ': ' + price + ', ' + (pick.c.mealPlan ? pick.c.mealPlan.type : '') + ', ' + cancel + '. combinationKey saved for the next step.');",
                  "    return pick;",
                  "  };",
                  "  const sameMoney = (a, b) => !!a && !!b && Number(a.amount) === Number(b.amount) && a.currency === b.currency;",
                  "  const samePolicies = (a, b) => {",
                  "    const norm = (list) => JSON.stringify((list || []).map((p) => [p.date, p.amount && Number(p.amount.amount), p.amount && p.amount.currency]));",
                  "    return norm(a) === norm(b);",
                  "  };",
                  "",
                  "  const hotels = res().accommodations || [];",
                  "  pm.test('Hotels returned: ' + hotels.length, () =>",
                  "    pm.expect(hotels.length, 'No hotels. Check that the dates are in the future and that destinationId exists on your microsite (run Destinations). providerTraces[] in the response lists supplier errors.').to.be.above(0),",
                  "  );",
                  "  const pick = storeQuote(hotels);",
                  "  pm.test('A combination was picked for Confirm', () => pm.expect(pick, 'No combination to confirm.').to.not.equal(null));",
                  "  const single = hotels.filter((h) => h.quoteSingleNeeded).length;",
                  "  if (single) console.log(single + ' hotel(s) have quoteSingleNeeded: true. Quote single returns all their combinations.');",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"checkIn\": \"{{quoteCheckIn}}\",\n  \"checkOut\": \"{{quoteCheckOut}}\",\n  \"distributions\": [\n    { \"persons\": [{ \"age\": 30 }, { \"age\": 30 }] }\n  ],\n  \"language\": \"{{language}}\",\n  \"sourceMarket\": \"{{sourceMarket}}\",\n  \"tripType\": \"ONLY_HOTEL\",\n  \"timeout\": 8000,\n  \"filter\": { \"bestCombinations\": true, \"maxCombinations\": 4, \"includeOnRequestOptions\": false },\n  \"destinationId\": \"{{destinationId}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/booking/accommodations/quote",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "booking",
                "accommodations",
                "quote"
              ]
            },
            "description": "Saves `accommodationId` and the `combinationKey` of the best combination it finds (not on request, fully refundable when possible), and the rooms and ages you sent, which Prebook repeats.\n\nEach `distributions[]` entry is one room and each `age` is the guest's age at checkout. For the three certification bookings, edit `distributions`, run Quote to Book, and keep the booking (don't run Cancel):\n\n- One room, two adults: `[{ \"persons\": [{ \"age\": 30 }, { \"age\": 30 }] }]`\n- Two rooms: `[{ \"persons\": [{ \"age\": 30 }, { \"age\": 30 }] }, { \"persons\": [{ \"age\": 45 }, { \"age\": 42 }] }]`\n- Adults and children: `[{ \"persons\": [{ \"age\": 30 }, { \"age\": 30 }] }, { \"persons\": [{ \"age\": 40 }, { \"age\": 5 }] }]`\n\nDates: `checkIn` defaults to 42 days from today and the stay to `nights` nights. Set the `checkIn` (yyyy-MM-dd) and `nights` collection variables to change them.\n\nUse this token for the whole flow through Book. Searches up to 3000 hotel codes or one destination and returns room combinations, each with the `combinationKey` you send to Confirm. Stays are limited to 30 nights. `tripType` and `filter` are optional (defaults `ONLY_HOTEL` and the default filter). Prices, policies and remarks are informational until Confirm.\n\nReference: [Quote](https://nava-hotels-docs.vercel.app/docs/api/quote)"
          },
          "response": []
        },
        {
          "name": "Quote single",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (!v('accommodationId')) stop('Run Quote first, or set accommodationId to a hotel code.');",
                  "// Repeats the rooms and ages of the last Quote.",
                  "if (!v('quoteDistributions')) setv('quoteDistributions', [{ persons: [{ age: 30 }, { age: 30 }] }]);",
                  "",
                  "// checkIn defaults to 42 days from today and the stay to `nights` nights (default 4).",
                  "const today = new Date().toISOString().slice(0, 10);",
                  "const checkIn = v('checkIn') || addDays(today, 42);",
                  "setv('quoteCheckIn', checkIn);",
                  "setv('quoteCheckOut', addDays(checkIn, Number(v('nights') || 4)));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  // Prefer a combination that is not on request and is fully refundable, so a test booking is cheap to cancel.",
                  "  const pickCombination = (hotels, fallbackCode) => {",
                  "    const all = [];",
                  "    (hotels || []).forEach((h) => (h.combinations || []).forEach((c) => all.push({ code: h.code || fallbackCode, c })));",
                  "    const score = (x) => (x.c.onRequest ? 2 : 0) + (x.c.currentCancellationType && x.c.currentCancellationType.type === 'REFUNDABLE' ? 0 : 1);",
                  "    return all.sort((a, b) => score(a) - score(b))[0] || null;",
                  "  };",
                  "  const storeQuote = (hotels, fallbackCode) => {",
                  "    // Prebook must repeat the rooms and ages of the quote, so remember what this request sent.",
                  "    try {",
                  "      const sent = JSON.parse(pm.variables.replaceIn(pm.request.body.raw));",
                  "      if (Array.isArray(sent.distributions)) setv('quoteDistributions', sent.distributions);",
                  "    } catch (e) {",
                  "      console.warn('Could not read the distributions from the request body: ' + e.message);",
                  "    }",
                  "    const pick = pickCombination(hotels, fallbackCode);",
                  "    if (!pick) return null;",
                  "    setv('accommodationId', String(pick.code));",
                  "    setv('combinationKey', pick.c.combinationKey);",
                  "    setv('quotedOffer', { hotel: pick.code, price: pick.c.price, mealPlan: pick.c.mealPlan && pick.c.mealPlan.type, cancellation: pick.c.currentCancellationType });",
                  "    const price = pick.c.price ? pick.c.price.amount + ' ' + pick.c.price.currency : 'no price';",
                  "    const cancel = pick.c.currentCancellationType ? pick.c.currentCancellationType.type : 'unknown cancellation type';",
                  "    console.log('Picked hotel ' + pick.code + ': ' + price + ', ' + (pick.c.mealPlan ? pick.c.mealPlan.type : '') + ', ' + cancel + '. combinationKey saved for the next step.');",
                  "    return pick;",
                  "  };",
                  "  const sameMoney = (a, b) => !!a && !!b && Number(a.amount) === Number(b.amount) && a.currency === b.currency;",
                  "  const samePolicies = (a, b) => {",
                  "    const norm = (list) => JSON.stringify((list || []).map((p) => [p.date, p.amount && Number(p.amount.amount), p.amount && p.amount.currency]));",
                  "    return norm(a) === norm(b);",
                  "  };",
                  "",
                  "  const acc = res().accommodation;",
                  "  const hotels = acc ? [acc] : [];",
                  "  const count = acc && acc.combinations ? acc.combinations.length : 0;",
                  "  pm.test('Combinations returned: ' + count, () => pm.expect(count).to.be.above(0));",
                  "  storeQuote(hotels, v('accommodationId'));",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"checkIn\": \"{{quoteCheckIn}}\",\n  \"checkOut\": \"{{quoteCheckOut}}\",\n  \"distributions\": {{quoteDistributions}},\n  \"language\": \"{{language}}\",\n  \"sourceMarket\": \"{{sourceMarket}}\",\n  \"tripType\": \"ONLY_HOTEL\",\n  \"timeout\": 8000,\n  \"filter\": { \"includeOnRequestOptions\": false }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/booking/accommodations/{{accommodationId}}/quote",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "booking",
                "accommodations",
                "{{accommodationId}}",
                "quote"
              ]
            },
            "description": "Optional. Returns every combination of the hotel Quote picked, for the same rooms and ages (`{{quoteDistributions}}`), and replaces the saved `combinationKey` with the best one. Skip it to confirm the combination from Quote.\n\nNever call it in parallel for many hotels: send one Quote with a code list instead. Returns every room combination of one hotel, for a hotel page or when Quote marked the hotel `quoteSingleNeeded`.\n\nReference: [Quote single](https://nava-hotels-docs.vercel.app/docs/api/quote-single)"
          },
          "response": []
        },
        {
          "name": "Confirm",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  const j = res();",
                  "  const acc = j.accommodation || {};",
                  "  const comb = acc.combination || {};",
                  "  pm.test('New combinationKey returned', () => pm.expect(comb.combinationKey).to.be.a('string').that.is.not.empty);",
                  "  if (comb.combinationKey) setv('combinationKey', comb.combinationKey);",
                  "  setv('confirmedOffer', { price: comb.price, mealPlan: comb.mealPlan && comb.mealPlan.type, cancellationPolicies: comb.cancellationPolicies || [] });",
                  "  setv('requiredField', j.requiredField || {});",
                  "  const warnings = (j.warnings || []).map((w) => w.type);",
                  "  // A warning is not an error, but a real integration must show it to the guest and get consent again.",
                  "  pm.test(warnings.length ? 'Warnings: ' + warnings.join(', ') + '. Show them to the guest and get consent again' : 'No price or cancellation policy warnings', () => {});",
                  "  console.log('Confirmed ' + (acc.name || acc.code || '') + ': ' + (comb.price ? comb.price.amount + ' ' + comb.price.currency : 'no price') + '. Required guest data: ' + JSON.stringify(j.requiredField || {}));",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"accommodation\": { \"combinationKey\": \"{{combinationKey}}\" }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/booking/accommodations/{{accommodationId}}/confirm",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "booking",
                "accommodations",
                "{{accommodationId}}",
                "confirm"
              ]
            },
            "description": "Saves the new `combinationKey`, the authoritative price and cancellation policies, and `requiredField`, which Prebook uses to decide which guest fields to send. Warnings appear as a test result.\n\nSend the key from Quote or Quote single and use the **new** key in the response for Prebook. Confirm returns the authoritative price, cancellation policies and remarks, the guest fields Prebook needs, and any `warnings[]`: show those to the guest and get fresh consent.\n\nReference: [Confirm](https://nava-hotels-docs.vercel.app/docs/api/confirm)"
          },
          "response": []
        },
        {
          "name": "Prebook",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "// Builds the guests from the rooms and ages the Quote sent and the fields Confirm's requiredField asks for.",
                  "// To send your own guests, replace {{prebookDistributions}} in the body with your own JSON.",
                  "const rooms = getJson('quoteDistributions', null);",
                  "if (!rooms) stop('Run Quote first: Prebook repeats the rooms and ages of the quote.');",
                  "const required = getJson('requiredField', {});",
                  "const checkOut = v('quoteCheckOut');",
                  "// Born 60 days before checkout, so the age is the same at check-in and at checkout.",
                  "const born = (age) => {",
                  "  const d = new Date(checkOut + 'T00:00:00Z');",
                  "  d.setUTCFullYear(d.getUTCFullYear() - age);",
                  "  d.setUTCDate(d.getUTCDate() - 60);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const ADULTS = [['Ana', 'MRS'], ['Luis', 'MISTER'], ['Sara', 'MS'], ['Omar', 'MISTER'], ['Lina', 'MRS'], ['Karim', 'MISTER']];",
                  "const CHILDREN = [['Leo', 'MISTER'], ['Mia', 'MS'], ['Adam', 'MISTER'], ['Noor', 'MS'], ['Yara', 'MS']];",
                  "const SURNAMES = ['Ruiz', 'Haddad', 'Nasser', 'Silva'];",
                  "let n = 0;",
                  "const distributions = rooms.map((room, r) => {",
                  "  let adults = 0;",
                  "  let children = 0;",
                  "  const persons = (room.persons || room.person || []).map((p, i) => {",
                  "    n += 1;",
                  "    const age = Number(p.age);",
                  "    const [name, title] = age >= 18 ? ADULTS[adults++ % ADULTS.length] : CHILDREN[children++ % CHILDREN.length];",
                  "    const lastName = SURNAMES[r % SURNAMES.length];",
                  "    // requestedAge must equal the age sent in Quote: the age at checkout.",
                  "    const g = { name, lastName, requestedAge: age, courtesyTitle: title, birthDate: born(age) };",
                  "    const role = r === 0 && i === 0 ? 'contactPerson' : i === 0 ? 'roomHolders' : 'otherPersons';",
                  "    const fields = (required[role] || []).slice();",
                  "    if (role === 'contactPerson') fields.push('EMAIL', 'PHONE');",
                  "    fields.forEach((f) => {",
                  "      switch (f) {",
                  "        case 'FIRST_NAME':",
                  "        case 'LAST_NAME':",
                  "        case 'TITLE':",
                  "        case 'BIRTH_DATE':",
                  "          break; // always sent",
                  "        case 'EMAIL':",
                  "          g.email = (name + '.' + lastName + n).toLowerCase() + '@example.com';",
                  "          break;",
                  "        case 'PHONE':",
                  "          g.phoneCountryCode = '+971';",
                  "          g.phone = '501234567';",
                  "          break;",
                  "        case 'COUNTRY':",
                  "          g.countryId = 'AE';",
                  "          break;",
                  "        case 'DOCUMENT':",
                  "        case 'PASSPORT':",
                  "          g.documentType = 'PASSPORT';",
                  "          g.documentNumber = 'P' + (1000000 + n);",
                  "          break;",
                  "        case 'DOCUMENT_EXPIRY_DATE':",
                  "          g.passportExpirationDate = addDays(checkOut, 3650);",
                  "          break;",
                  "        case 'ADDRESS':",
                  "          g.address = { street: 'Sheikh Zayed Road 1', city: 'Dubai', postCode: '00000' };",
                  "          break;",
                  "        case 'SOCIAL_INSURANCE_NUMBER':",
                  "          g.socialInsuranceNumber = '000000000';",
                  "          break;",
                  "        case 'BILLING_DOCUMENT':",
                  "          g.billingNumber = 'B0000000';",
                  "          break;",
                  "        case 'ACADEMY_TITLE':",
                  "          g.academyTitle = 'Dr';",
                  "          break;",
                  "        default:",
                  "          console.warn('requiredField asks for ' + f + ' (' + role + '), which has no documented hotel field. Ask your Nava account manager.');",
                  "      }",
                  "    });",
                  "    return g;",
                  "  });",
                  "  return { persons };",
                  "});",
                  "setv('prebookDistributions', JSON.stringify(distributions, null, 2));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  // Prefer a combination that is not on request and is fully refundable, so a test booking is cheap to cancel.",
                  "  const pickCombination = (hotels, fallbackCode) => {",
                  "    const all = [];",
                  "    (hotels || []).forEach((h) => (h.combinations || []).forEach((c) => all.push({ code: h.code || fallbackCode, c })));",
                  "    const score = (x) => (x.c.onRequest ? 2 : 0) + (x.c.currentCancellationType && x.c.currentCancellationType.type === 'REFUNDABLE' ? 0 : 1);",
                  "    return all.sort((a, b) => score(a) - score(b))[0] || null;",
                  "  };",
                  "  const storeQuote = (hotels, fallbackCode) => {",
                  "    // Prebook must repeat the rooms and ages of the quote, so remember what this request sent.",
                  "    try {",
                  "      const sent = JSON.parse(pm.variables.replaceIn(pm.request.body.raw));",
                  "      if (Array.isArray(sent.distributions)) setv('quoteDistributions', sent.distributions);",
                  "    } catch (e) {",
                  "      console.warn('Could not read the distributions from the request body: ' + e.message);",
                  "    }",
                  "    const pick = pickCombination(hotels, fallbackCode);",
                  "    if (!pick) return null;",
                  "    setv('accommodationId', String(pick.code));",
                  "    setv('combinationKey', pick.c.combinationKey);",
                  "    setv('quotedOffer', { hotel: pick.code, price: pick.c.price, mealPlan: pick.c.mealPlan && pick.c.mealPlan.type, cancellation: pick.c.currentCancellationType });",
                  "    const price = pick.c.price ? pick.c.price.amount + ' ' + pick.c.price.currency : 'no price';",
                  "    const cancel = pick.c.currentCancellationType ? pick.c.currentCancellationType.type : 'unknown cancellation type';",
                  "    console.log('Picked hotel ' + pick.code + ': ' + price + ', ' + (pick.c.mealPlan ? pick.c.mealPlan.type : '') + ', ' + cancel + '. combinationKey saved for the next step.');",
                  "    return pick;",
                  "  };",
                  "  const sameMoney = (a, b) => !!a && !!b && Number(a.amount) === Number(b.amount) && a.currency === b.currency;",
                  "  const samePolicies = (a, b) => {",
                  "    const norm = (list) => JSON.stringify((list || []).map((p) => [p.date, p.amount && Number(p.amount.amount), p.amount && p.amount.currency]));",
                  "    return norm(a) === norm(b);",
                  "  };",
                  "",
                  "  const j = res();",
                  "  const comb = (j.accommodation || {}).combination || {};",
                  "  const before = getJson('confirmedOffer', {});",
                  "  pm.test('New combinationKey returned', () => pm.expect(comb.combinationKey).to.be.a('string').that.is.not.empty);",
                  "  // Prebook re-checks with the supplier. Book only when nothing changed since Confirm, or after new consent.",
                  "  pm.test('Price unchanged since Confirm', () => pm.expect(sameMoney(comb.price, before.price), JSON.stringify(comb.price) + ' vs ' + JSON.stringify(before.price)).to.equal(true));",
                  "  pm.test('Meal plan unchanged since Confirm', () => pm.expect(comb.mealPlan && comb.mealPlan.type).to.eql(before.mealPlan));",
                  "  pm.test('Cancellation policies unchanged since Confirm', () => pm.expect(samePolicies(comb.cancellationPolicies, before.cancellationPolicies)).to.equal(true));",
                  "  const warnings = (j.warnings || []).map((w) => w.type);",
                  "  if (warnings.length) console.warn('Prebook warnings: ' + warnings.join(', ') + '. Show them to the guest and get consent again.');",
                  "  if (comb.combinationKey) setv('combinationKey', comb.combinationKey);",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"accommodation\": {\n    \"combinationKey\": \"{{combinationKey}}\",\n    \"commentToAccommodation\": \"Test booking from the Nava Postman collection\"\n  },\n  \"distributions\": {{prebookDistributions}}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/booking/accommodations/{{accommodationId}}/prebook",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "booking",
                "accommodations",
                "{{accommodationId}}",
                "prebook"
              ]
            },
            "description": "The pre-request script fills `{{prebookDistributions}}` with test guests: the same rooms, order and ages as the Quote, Latin-script names, a birth date for everyone, an email and phone for the contact person, and any other field Confirm's `requiredField` lists. Open the Console to see the guests sent. To send your own guests, replace `{{prebookDistributions}}` with your JSON.\n\nThe tests compare price, meal plan and cancellation policies with Confirm. If one fails, don't book without the guest's new consent.\n\nSend the rooms in the Quote's order, with the same people and every guest's `requestedAge` equal to the quoted age. Prebook takes the key from Confirm, re-validates the offer with the supplier and returns a new key for Book. Compare price, meal plan and cancellation policies with Confirm before you book.\n\nReference: [Prebook](https://nava-hotels-docs.vercel.app/docs/api/prebook)"
          },
          "response": []
        },
        {
          "name": "Book",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "// Safety guard: this request books or cancels. It runs only in the test environment, and never against the",
                  "// production base URL, unless allowBookingWrites is true.",
                  "const navaEnv = String(v('navaEnv') || '').toLowerCase();",
                  "const trim = (u) => String(u || '').toLowerCase().replace('://www.', '://').replace(/\\/+$/, '');",
                  "const onProduction = trim(v('baseUrl')) === trim(v('productionBaseUrl'));",
                  "if ((navaEnv !== 'test' || onProduction) && String(v('allowBookingWrites')).toLowerCase() !== 'true') {",
                  "  stop(",
                  "    navaEnv === 'test'",
                  "      ? 'Blocked: navaEnv is \"test\" but baseUrl is the production base URL. Use the sandbox base URL with your test credentials.'",
                  "      : 'Blocked: navaEnv is \"' + (navaEnv || 'not set') + '\". This request books or cancels for real. To send it, set allowBookingWrites to true in your environment, then set it back to false.',",
                  "  );",
                  "}",
                  "",
                  "// Your own order id. A real integration saves it BEFORE calling Book, to reconcile a timeout.",
                  "setv('externalReference', 'POSTMAN-' + Date.now());",
                  "const fake = v('fakeBookingStatus');",
                  "if (fake) {",
                  "  // fakeBooking forces the outcome. A fake booking is not saved and never reaches a supplier.",
                  "  const b = JSON.parse(pm.variables.replaceIn(pm.request.body.raw));",
                  "  b.fakeBooking = { status: fake };",
                  "  pm.request.body.update(JSON.stringify(b, null, 2));",
                  "  console.log('fakeBooking: ' + fake + '. Nothing is booked; post-booking calls answer 404 for a fake booking.');",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  const j = res();",
                  "  const acc = j.accommodation || {};",
                  "  setv('bookingReference', j.bookingReference || '');",
                  "  setv('accommodationBookingReference', acc.bookingReference || '');",
                  "  setv('bookingStatus', j.status || '');",
                  "  const expected = v('fakeBookingStatus') || 'BOOKED';",
                  "  pm.test('Status ' + j.status + (j.status === expected ? '' : ' (expected ' + expected + ')'), () =>",
                  "    pm.expect(j.status, 'Branch on status: BOOKED is confirmed; RQ waits for the supplier (poll Booking detail); PENDING_BOOK is still in progress; PRICE_ERROR, BOOK_ERROR and NOT_BOOKED did not book.').to.eql(expected),",
                  "  );",
                  "  pm.test('Trip and hotel references returned', () => {",
                  "    pm.expect(j.bookingReference).to.be.a('string').that.is.not.empty;",
                  "    pm.expect(acc.bookingReference).to.be.a('string').that.is.not.empty;",
                  "  });",
                  "  console.log('Book: ' + j.status + ', bookingReference ' + j.bookingReference + ', accommodationBookingReference ' + acc.bookingReference + ', externalReference ' + v('externalReference') + '. Keep all three and the traceId.');",
                  "  // The key is used up and the flow is over: a new booking starts with a new Quote.",
                  "  setv('combinationKey', '');",
                  "  setv('flowAuthToken', '');",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"accommodation\": { \"combinationKey\": \"{{combinationKey}}\" },\n  \"externalReference\": \"{{externalReference}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/booking/accommodations/{{accommodationId}}/book",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "booking",
                "accommodations",
                "{{accommodationId}}",
                "book"
              ]
            },
            "description": "Sets `externalReference` to a new `POSTMAN-<timestamp>` and saves `bookingReference` and `accommodationBookingReference` for the post-booking requests.\n\n**Never re-send Book because it timed out.** It has no idempotency key: read the booking first.\n\nTo test your failure path, set the `fakeBookingStatus` collection variable to `BOOK_ERROR` (or `BOOKED`) and run the flow again. A fake booking is not saved and never reaches a supplier. Clear the variable afterwards.\n\nBlocked unless the environment's `navaEnv` is `test` and its `baseUrl` is not production, or `allowBookingWrites` is `true`.\n\nNever retry Book automatically: it has no idempotency key, and a call that timed out may still have booked. Send the key from Prebook, persist both references from the response at once, and branch on `status`.\n\nReference: [Book](https://nava-hotels-docs.vercel.app/docs/api/book)"
          },
          "response": []
        }
      ]
    },
    {
      "name": "4. After booking",
      "description": "Read, price and cancel the booking from Book, using `bookingReference` and `accommodationBookingReference`.",
      "item": [
        {
          "name": "Booking detail",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "const needBooking = () => {",
                  "  if (!v('bookingReference') || !v('accommodationBookingReference')) {",
                  "    stop('No booking yet. Run Book first, or set bookingReference and accommodationBookingReference to a booking in your test account.');",
                  "  }",
                  "};",
                  "",
                  "needBooking();"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  const j = res();",
                  "  pm.test('Status ' + j.status, () => pm.expect(j.status).to.be.a('string'));",
                  "  pm.test('externalReference is stored on the booking', () => pm.expect(j.externalReference).to.eql(v('externalReference')));",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/booking/{{bookingReference}}/accommodations/{{accommodationBookingReference}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "booking",
                "{{bookingReference}}",
                "accommodations",
                "{{accommodationBookingReference}}"
              ]
            },
            "description": "The stored booking, without calling the supplier. Poll this while a booking is `RQ`.\n\nReads stored data only, with no supplier call, so use it for routine polling of `RQ` and `PENDING_BOOK` bookings. It returns your `externalReference` under that name. A `fakeBooking` booking is never saved, so it returns 404 here.\n\nReference: [Booking detail](https://nava-hotels-docs.vercel.app/docs/api/booking-detail)"
          },
          "response": []
        },
        {
          "name": "Get booking (whole trip)",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "const needBooking = () => {",
                  "  if (!v('bookingReference') || !v('accommodationBookingReference')) {",
                  "    stop('No booking yet. Run Book first, or set bookingReference and accommodationBookingReference to a booking in your test account.');",
                  "  }",
                  "};",
                  "",
                  "needBooking();"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  const j = res();",
                  "  const hotels = j.hotelservice || [];",
                  "  pm.test('hotelservice[] has the hotel', () => pm.expect(hotels.length).to.be.above(0));",
                  "  pm.test('externalReference comes back as agencyBookingReference', () => pm.expect(j.agencyBookingReference).to.eql(v('externalReference')));",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/booking/getBookings/{{micrositeId}}/{{bookingReference}}?lang={{language}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "booking",
                "getBookings",
                "{{micrositeId}}",
                "{{bookingReference}}"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{language}}"
                }
              ]
            },
            "description": "The webhook is a signal and this is the truth: read the whole trip here after every webhook. The hotel is in `hotelservice[]`, and a 404 means the reference does not exist in this microsite.\n\nReference: [Get booking](https://nava-hotels-docs.vercel.app/docs/api/get-booking)"
          },
          "response": []
        },
        {
          "name": "List bookings",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "// from and to are CREATION dates, yyyyMMdd. A window around today covers bookings made in this session.",
                  "const ymd = (n) => addDays(new Date().toISOString().slice(0, 10), n).replace(/-/g, '');",
                  "setv('bookingsFrom', ymd(-1));",
                  "setv('bookingsTo', ymd(1));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  const list = res().bookings || [];",
                  "  pm.test('Bookings returned: ' + list.length, () => pm.expect(list).to.be.an('array'));",
                  "  if (v('bookingReference') && list.length < 100) {",
                  "    pm.test('The booking from Book is listed', () => pm.expect(list.map((b) => b.bookingReference)).to.include(v('bookingReference')));",
                  "  }",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/booking/bookings?microsite={{micrositeId}}&from={{bookingsFrom}}&to={{bookingsTo}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "booking",
                "bookings"
              ],
              "query": [
                {
                  "key": "microsite",
                  "value": "{{micrositeId}}"
                },
                {
                  "key": "from",
                  "value": "{{bookingsFrom}}"
                },
                {
                  "key": "to",
                  "value": "{{bookingsTo}}"
                }
              ]
            },
            "description": "Lists the bookings created on your microsite from yesterday to tomorrow (`bookingsFrom` and `bookingsTo` are set by the pre-request script).\n\nStrictly scoped to one microsite and bounded by **creation** date (`from` and `to` are required). Returns each booking's reference, status, creation date and microsite, 100 per page, with no status or channel filter. The older listing variant of `getBookings` is deprecated.\n\nReference: [List bookings](https://nava-hotels-docs.vercel.app/docs/api/list-bookings)"
          },
          "response": []
        },
        {
          "name": "Cancellation fee",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "const needBooking = () => {",
                  "  if (!v('bookingReference') || !v('accommodationBookingReference')) {",
                  "    stop('No booking yet. Run Book first, or set bookingReference and accommodationBookingReference to a booking in your test account.');",
                  "  }",
                  "};",
                  "",
                  "needBooking();"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  const fee = res().cancellationFee;",
                  "  pm.test('cancellationFee returned', () => pm.expect(fee).to.be.an('object'));",
                  "  if (fee) console.log('Cancelling now would cost ' + fee.amount + ' ' + fee.currency + '. This is a quote; nothing was cancelled.');",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/booking/{{bookingReference}}/accommodations/{{accommodationBookingReference}}/cancellation-fee",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "booking",
                "{{bookingReference}}",
                "accommodations",
                "{{accommodationBookingReference}}",
                "cancellation-fee"
              ]
            },
            "description": "What cancelling now would cost. A quote: it cancels nothing.\n\nA read-only quote: it cancels nothing and refunds nothing. Returns the penalty if you cancelled the booked hotel today.\n\nReference: [Cancellation fee](https://nava-hotels-docs.vercel.app/docs/api/cancellation-fee)"
          },
          "response": []
        },
        {
          "name": "Cancel",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "// Safety guard: this request books or cancels. It runs only in the test environment, and never against the",
                  "// production base URL, unless allowBookingWrites is true.",
                  "const navaEnv = String(v('navaEnv') || '').toLowerCase();",
                  "const trim = (u) => String(u || '').toLowerCase().replace('://www.', '://').replace(/\\/+$/, '');",
                  "const onProduction = trim(v('baseUrl')) === trim(v('productionBaseUrl'));",
                  "if ((navaEnv !== 'test' || onProduction) && String(v('allowBookingWrites')).toLowerCase() !== 'true') {",
                  "  stop(",
                  "    navaEnv === 'test'",
                  "      ? 'Blocked: navaEnv is \"test\" but baseUrl is the production base URL. Use the sandbox base URL with your test credentials.'",
                  "      : 'Blocked: navaEnv is \"' + (navaEnv || 'not set') + '\". This request books or cancels for real. To send it, set allowBookingWrites to true in your environment, then set it back to false.',",
                  "  );",
                  "}",
                  "",
                  "const needBooking = () => {",
                  "  if (!v('bookingReference') || !v('accommodationBookingReference')) {",
                  "    stop('No booking yet. Run Book first, or set bookingReference and accommodationBookingReference to a booking in your test account.');",
                  "  }",
                  "};",
                  "",
                  "needBooking();"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  const j = res();",
                  "  setv('bookingStatus', j.status || '');",
                  "  pm.test('Booking CANCELED', () => pm.expect(j.status).to.eql('CANCELED'));",
                  "  pm.test('Hotel CANCELED', () => pm.expect(j.accommodation && j.accommodation.status).to.eql('CANCELED'));",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/booking/{{bookingReference}}/accommodations/{{accommodationBookingReference}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "booking",
                "{{bookingReference}}",
                "accommodations",
                "{{accommodationBookingReference}}"
              ]
            },
            "description": "Cancels at the supplier. **Never re-send it because it timed out**: read the booking first.\n\nBlocked unless the environment's `navaEnv` is `test` and its `baseUrl` is not production, or `allowBookingWrites` is `true`.\n\nNever retry Cancel automatically: it is not safely retryable, so after a timeout read Booking detail first. Cancels the hotel at the supplier and returns the booking with `status: CANCELED`; it does not refund the guest.\n\nReference: [Cancel](https://nava-hotels-docs.vercel.app/docs/api/cancel)"
          },
          "response": []
        }
      ]
    },
    {
      "name": "5. More examples",
      "description": "Variations you can run on their own.",
      "item": [
        {
          "name": "Quote by hotel codes",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (!v('catalogueIdsJson')) {",
                  "  if (!v('accommodationId')) stop('Run Accommodation list first: this request quotes the hotel codes it saved.');",
                  "  setv('catalogueIdsJson', [v('accommodationId')]);",
                  "}",
                  "",
                  "// checkIn defaults to 42 days from today and the stay to `nights` nights (default 4).",
                  "const today = new Date().toISOString().slice(0, 10);",
                  "const checkIn = v('checkIn') || addDays(today, 42);",
                  "setv('quoteCheckIn', checkIn);",
                  "setv('quoteCheckOut', addDays(checkIn, Number(v('nights') || 4)));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "",
                  "  // Prefer a combination that is not on request and is fully refundable, so a test booking is cheap to cancel.",
                  "  const pickCombination = (hotels, fallbackCode) => {",
                  "    const all = [];",
                  "    (hotels || []).forEach((h) => (h.combinations || []).forEach((c) => all.push({ code: h.code || fallbackCode, c })));",
                  "    const score = (x) => (x.c.onRequest ? 2 : 0) + (x.c.currentCancellationType && x.c.currentCancellationType.type === 'REFUNDABLE' ? 0 : 1);",
                  "    return all.sort((a, b) => score(a) - score(b))[0] || null;",
                  "  };",
                  "  const storeQuote = (hotels, fallbackCode) => {",
                  "    // Prebook must repeat the rooms and ages of the quote, so remember what this request sent.",
                  "    try {",
                  "      const sent = JSON.parse(pm.variables.replaceIn(pm.request.body.raw));",
                  "      if (Array.isArray(sent.distributions)) setv('quoteDistributions', sent.distributions);",
                  "    } catch (e) {",
                  "      console.warn('Could not read the distributions from the request body: ' + e.message);",
                  "    }",
                  "    const pick = pickCombination(hotels, fallbackCode);",
                  "    if (!pick) return null;",
                  "    setv('accommodationId', String(pick.code));",
                  "    setv('combinationKey', pick.c.combinationKey);",
                  "    setv('quotedOffer', { hotel: pick.code, price: pick.c.price, mealPlan: pick.c.mealPlan && pick.c.mealPlan.type, cancellation: pick.c.currentCancellationType });",
                  "    const price = pick.c.price ? pick.c.price.amount + ' ' + pick.c.price.currency : 'no price';",
                  "    const cancel = pick.c.currentCancellationType ? pick.c.currentCancellationType.type : 'unknown cancellation type';",
                  "    console.log('Picked hotel ' + pick.code + ': ' + price + ', ' + (pick.c.mealPlan ? pick.c.mealPlan.type : '') + ', ' + cancel + '. combinationKey saved for the next step.');",
                  "    return pick;",
                  "  };",
                  "  const sameMoney = (a, b) => !!a && !!b && Number(a.amount) === Number(b.amount) && a.currency === b.currency;",
                  "  const samePolicies = (a, b) => {",
                  "    const norm = (list) => JSON.stringify((list || []).map((p) => [p.date, p.amount && Number(p.amount.amount), p.amount && p.amount.currency]));",
                  "    return norm(a) === norm(b);",
                  "  };",
                  "",
                  "  const hotels = res().accommodations || [];",
                  "  pm.test('Hotels returned: ' + hotels.length, () =>",
                  "    pm.expect(hotels.length, 'No hotels. Check that the dates are in the future and that destinationId exists on your microsite (run Destinations). providerTraces[] in the response lists supplier errors.').to.be.above(0),",
                  "  );",
                  "  const pick = storeQuote(hotels);",
                  "  pm.test('A combination was picked for Confirm', () => pm.expect(pick, 'No combination to confirm.').to.not.equal(null));",
                  "  const single = hotels.filter((h) => h.quoteSingleNeeded).length;",
                  "  if (single) console.log(single + ' hotel(s) have quoteSingleNeeded: true. Quote single returns all their combinations.');",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"checkIn\": \"{{quoteCheckIn}}\",\n  \"checkOut\": \"{{quoteCheckOut}}\",\n  \"distributions\": [\n    { \"persons\": [{ \"age\": 30 }, { \"age\": 30 }] }\n  ],\n  \"language\": \"{{language}}\",\n  \"sourceMarket\": \"{{sourceMarket}}\",\n  \"tripType\": \"ONLY_HOTEL\",\n  \"timeout\": 8000,\n  \"filter\": { \"bestCombinations\": true, \"maxCombinations\": 4, \"includeOnRequestOptions\": false },\n  \"accommodations\": {{catalogueIdsJson}}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/booking/accommodations/quote",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "booking",
                "accommodations",
                "quote"
              ]
            },
            "description": "Searches up to 20 hotel codes saved by Accommodation list instead of a destination (a real search can send up to 3000). Saves a `combinationKey` like Quote, so you can continue with Confirm.\n\nUse this token for the whole flow through Book. Searches up to 3000 hotel codes or one destination and returns room combinations, each with the `combinationKey` you send to Confirm. Stays are limited to 30 nights. `tripType` and `filter` are optional (defaults `ONLY_HOTEL` and the default filter). Prices, policies and remarks are informational until Confirm.\n\nReference: [Quote](https://nava-hotels-docs.vercel.app/docs/api/quote)"
          },
          "response": []
        },
        {
          "name": "Refresh",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "// Safety guard: this request books or cancels. It runs only in the test environment, and never against the",
                  "// production base URL, unless allowBookingWrites is true.",
                  "const navaEnv = String(v('navaEnv') || '').toLowerCase();",
                  "const trim = (u) => String(u || '').toLowerCase().replace('://www.', '://').replace(/\\/+$/, '');",
                  "const onProduction = trim(v('baseUrl')) === trim(v('productionBaseUrl'));",
                  "if ((navaEnv !== 'test' || onProduction) && String(v('allowBookingWrites')).toLowerCase() !== 'true') {",
                  "  stop(",
                  "    navaEnv === 'test'",
                  "      ? 'Blocked: navaEnv is \"test\" but baseUrl is the production base URL. Use the sandbox base URL with your test credentials.'",
                  "      : 'Blocked: navaEnv is \"' + (navaEnv || 'not set') + '\". This request books or cancels for real. To send it, set allowBookingWrites to true in your environment, then set it back to false.',",
                  "  );",
                  "}",
                  "",
                  "const needBooking = () => {",
                  "  if (!v('bookingReference') || !v('accommodationBookingReference')) {",
                  "    stop('No booking yet. Run Book first, or set bookingReference and accommodationBookingReference to a booking in your test account.');",
                  "  }",
                  "};",
                  "",
                  "needBooking();"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Shared helpers (the same block is copied into every script of this collection).",
                  "const v = (k) => pm.variables.get(k);",
                  "const setv = (k, val) => pm.collectionVariables.set(k, typeof val === 'string' ? val : JSON.stringify(val));",
                  "const getJson = (k, fallback) => {",
                  "  try {",
                  "    const s = pm.variables.get(k);",
                  "    return s ? JSON.parse(s) : fallback;",
                  "  } catch (e) {",
                  "    return fallback;",
                  "  }",
                  "};",
                  "const addDays = (iso, n) => {",
                  "  const d = new Date(iso + 'T00:00:00Z');",
                  "  d.setUTCDate(d.getUTCDate() + n);",
                  "  return d.toISOString().slice(0, 10);",
                  "};",
                  "const res = () => {",
                  "  try {",
                  "    return pm.response.json() || {};",
                  "  } catch (e) {",
                  "    return {};",
                  "  }",
                  "};",
                  "// Newman sends a request even when its pre-request script fails, so a request that must not go out is first",
                  "// pointed at an address that can never resolve.",
                  "const stop = (message) => {",
                  "  pm.request.url.update('https://blocked.invalid/not-sent');",
                  "  throw new Error(message);",
                  "};",
                  "",
                  "if (pm.response && pm.response.code) {",
                  "  pm.test('HTTP 200, or 406 when the supplier does not support Refresh', () => pm.expect(pm.response.code).to.be.oneOf([200, 406]));",
                  "  const j = res();",
                  "  if (j.status) {",
                  "    setv('bookingStatus', j.status);",
                  "    console.log('Refresh: booking ' + j.status + ', hotel ' + (j.accommodation && j.accommodation.status) + '. Some test suppliers answer every Refresh with CANCELED.');",
                  "  }",
                  "} else {",
                  "  pm.test('Sent', () => {",
                  "    throw new Error('Not sent: see the pre-request script error.');",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept-Encoding",
                "value": "gzip",
                "description": "Mandatory on every call."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/booking/{{bookingReference}}/accommodations/{{accommodationBookingReference}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "booking",
                "{{bookingReference}}",
                "accommodations",
                "{{accommodationBookingReference}}"
              ]
            },
            "description": "Re-reads the booking from the supplier. Use Booking detail for routine polling. **Some test suppliers answer every Refresh with `CANCELED`**, so don't refresh a test booking you still need.\n\nBlocked unless the environment's `navaEnv` is `test` and its `baseUrl` is not production, or `allowBookingWrites` is `true`.\n\nRefresh calls the supplier and returns the status it reports, for example to resolve `RQ`: unchanged if nothing changed at the supplier, and 406 if the supplier doesn't support refreshing. Use Booking detail for routine polling. Some test suppliers answer every Refresh with `CANCELED`; real suppliers don't.\n\nReference: [Refresh](https://nava-hotels-docs.vercel.app/docs/api/refresh)"
          },
          "response": []
        }
      ]
    }
  ]
}
