{
  "info": {
    "_postman_id": "fxport-api-flights-sandbox-v1",
    "name": "FX-Port API - Flights (Sandbox)",
    "description": "FX-Port Flights API — sandbox/API-key collection.\n\nAuth is set once at collection root as Bearer {{api_key}}; all requests inherit it. Never save a real key or webhook secret in the collection.\n\nHold bookings are capability-based: run the hold flow only when the selected repriced offer returns supportHold=true. A PNR can expire before its displayed deadline, and only a successful pay_order response confirms issuance.\n\nPassenger age arrays must match their counts. Passenger limits can be supplier-specific. During automatic or multi-supplier searches, incompatible suppliers are skipped silently and compatible results are returned. HTTP 400 is returned only when no selected supplier can serve the passenger mix. Some suppliers require one adult or senior per under-2 passenger; children aged 2-11 are not subject to that limit.\n\nPost-booking void, exchange, re-issue, and refund requests for issued tickets are currently handled manually by the FX-Port team. Contact dev@fx-port.com.\n\nCovered endpoints:\n- Flight search (one-way, round-trip, family, cabins, flexible dates, filters, single/multiple supplier selection)\n- Flight pricing\n- Flight booking (hold and instant issue)\n- Held-order flows (quote, pay, cancel)\n- Booking and ticket reads\n- Airport autocomplete",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{api_key}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "base_url",
      "value": "http://127.0.0.1:8000"
    },
    {
      "key": "production_base_url",
      "value": "https://api.fx-port.com"
    },
    {
      "key": "api_key",
      "value": "fxp_test_YOUR_KEY"
    },
    {
      "key": "webhook_secret",
      "value": "whsec_YOUR_SECRET"
    },
    {
      "key": "dep",
      "value": ""
    },
    {
      "key": "ret",
      "value": ""
    },
    {
      "key": "fallback_dep",
      "value": ""
    },
    {
      "key": "search_id",
      "value": ""
    },
    {
      "key": "offer_id",
      "value": ""
    },
    {
      "key": "supplier_id",
      "value": ""
    },
    {
      "key": "order_id",
      "value": ""
    },
    {
      "key": "booking_id",
      "value": "V64R4V"
    },
    {
      "key": "male_first_1",
      "value": "Adam"
    },
    {
      "key": "male_last_1",
      "value": "Martin"
    },
    {
      "key": "female_first_1",
      "value": "Sarah"
    },
    {
      "key": "female_last_1",
      "value": "Martin"
    },
    {
      "key": "child_first_1",
      "value": "Noah"
    },
    {
      "key": "child_last_1",
      "value": "Martin"
    },
    {
      "key": "infant_first_1",
      "value": "Lina"
    },
    {
      "key": "infant_last_1",
      "value": "Martin"
    },
    {
      "key": "adult1_dob",
      "value": "1989-03-12"
    },
    {
      "key": "adult2_dob",
      "value": "1991-08-27"
    },
    {
      "key": "child1_age",
      "value": "8"
    },
    {
      "key": "child1_dob",
      "value": "2018-04-19"
    },
    {
      "key": "infant1_dob",
      "value": "2025-02-06"
    },
    {
      "key": "adult1_passport",
      "value": "A12345678"
    },
    {
      "key": "adult2_passport",
      "value": "B87654321"
    },
    {
      "key": "child1_passport",
      "value": "C11223344"
    },
    {
      "key": "infant1_passport",
      "value": "D55667788"
    },
    {
      "key": "adult1_pexp",
      "value": "2031-07-14"
    },
    {
      "key": "adult2_pexp",
      "value": "2032-05-09"
    },
    {
      "key": "child1_pexp",
      "value": "2030-11-22"
    },
    {
      "key": "infant1_pexp",
      "value": "2033-01-30"
    }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "const now = new Date();",
          "const fmt = d => d.toISOString().slice(0, 10);",
          "const rnd = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;",
          "const pick = arr => arr[Math.floor(Math.random() * arr.length)];",
          "const depDate = new Date(now.getTime() + 30 * 86400000);",
          "const dobForAge = age => fmt(new Date(now.getFullYear() - age, rnd(0, 11), rnd(1, 28)));",
          "const futureDate = (minY, maxY) => fmt(new Date(now.getFullYear() + rnd(minY, maxY), rnd(0, 11), rnd(1, 28)));",
          "const passport = () => 'ABCDEFGHJKLMNPRSTUVWXYZ'[rnd(0, 21)] + String(rnd(10000000, 99999999));",
          "",
          "// Travel dates are always refreshed so examples stay in the near future.",
          "pm.collectionVariables.set('dep', fmt(depDate));",
          "pm.collectionVariables.set('fallback_dep', fmt(new Date(now.getTime() + 40 * 86400000)));",
          "pm.collectionVariables.set('ret', fmt(new Date(now.getTime() + 37 * 86400000)));",
          "",
          "// Seed a randomized, internally-consistent passenger identity. Re-seeded on every",
          "// Search so booking DOBs/ages cascade from the search that produced the offer.",
          "const requestName = (pm.info && pm.info.requestName) || '';",
          "const isSearch = /search/i.test(requestName);",
          "if (isSearch || !pm.collectionVariables.get('child1_dob')) {",
          "  // Diverse, real-world first/last names from many origins. ASCII/Latin ONLY —",
          "  // no accents or diacritics (write 'Jose' not 'José') and no non-Latin scripts.",
          "  const maleFirst = ['Adam', 'Liam', 'Mehdi', 'Karim', 'Sofiane', 'Diego', 'Mateo', 'Lucas', 'Omar', 'Youssef', 'Ivan', 'Dmitri', 'Kenji', 'Hiroshi', 'Wei', 'Chen', 'Arjun', 'Rohan', 'Kwame', 'Chidi', 'Sven', 'Lars', 'Marco', 'Paolo', 'Andres', 'Rafael', 'Noah', 'Ethan', 'Hassan', 'Bilal', 'Tariq', 'Sasha', 'Nikola', 'Emeka', 'Thabo', 'Santiago', 'Gabriel', 'Henrik', 'Matteo', 'Ahmad'];",
          "  const femaleFirst = ['Sarah', 'Emma', 'Lina', 'Amel', 'Yasmine', 'Sofia', 'Maria', 'Valentina', 'Aisha', 'Fatima', 'Anya', 'Olga', 'Yuki', 'Sakura', 'Mei', 'Ling', 'Priya', 'Ananya', 'Amara', 'Zainab', 'Ingrid', 'Freya', 'Giulia', 'Chiara', 'Camila', 'Isabela', 'Olivia', 'Hana', 'Noor', 'Layla', 'Nadia', 'Mira', 'Zoe', 'Adaeze', 'Naledi', 'Lucia', 'Elena', 'Astrid', 'Bianca', 'Salma'];",
          "  const childFirst = ['Noah', 'Rayan', 'Sami', 'Aya', 'Maya', 'Nour', 'Leo', 'Mia', 'Kai', 'Aria', 'Ravi', 'Ana', 'Nina', 'Theo', 'Ivy', 'Luca', 'Zara', 'Finn'];",
          "  const infantFirst = ['Lina', 'Sami', 'Nour', 'Malak', 'Leo', 'Mia', 'Kai', 'Ivy', 'Ana', 'Theo', 'Zoe', 'Ravi'];",
          "  const surnames = ['Benali', 'Haddad', 'Smith', 'Johnson', 'Garcia', 'Martinez', 'Rossi', 'Ferrari', 'Kim', 'Park', 'Nguyen', 'Tran', 'Kumar', 'Patel', 'Okafor', 'Mensah', 'Andersson', 'Nielsen', 'Muller', 'Weber', 'Silva', 'Santos', 'Ivanov', 'Petrov', 'Suzuki', 'Tanaka', 'Wang', 'Li', 'Cohen', 'Khan'];",
          "  const family = pick(surnames);",
          "  const childAge = rnd(2, 11);",
          "",
          "  pm.collectionVariables.set('male_first_1', pick(maleFirst));",
          "  pm.collectionVariables.set('male_last_1', family);",
          "  pm.collectionVariables.set('female_first_1', pick(femaleFirst));",
          "  pm.collectionVariables.set('female_last_1', family);",
          "  pm.collectionVariables.set('child_first_1', pick(childFirst));",
          "  pm.collectionVariables.set('child_last_1', family);",
          "  pm.collectionVariables.set('infant_first_1', pick(infantFirst));",
          "  pm.collectionVariables.set('infant_last_1', family);",
          "",
          "  pm.collectionVariables.set('adult1_dob', dobForAge(rnd(28, 55)));",
          "  pm.collectionVariables.set('adult2_dob', dobForAge(rnd(28, 55)));",
          "  pm.collectionVariables.set('child1_age', String(childAge));",
          "  // Anchor child/infant DOBs to the departure date so age-at-travel matches the searched band.",
          "  pm.collectionVariables.set('child1_dob', fmt(new Date(depDate.getFullYear() - childAge, depDate.getMonth() - rnd(1, 6), rnd(1, 28))));",
          "  pm.collectionVariables.set('infant1_dob', fmt(new Date(depDate.getFullYear(), depDate.getMonth() - rnd(2, 20), rnd(1, 28))));",
          "",
          "  pm.collectionVariables.set('adult1_passport', passport());",
          "  pm.collectionVariables.set('adult2_passport', passport());",
          "  pm.collectionVariables.set('child1_passport', passport());",
          "  pm.collectionVariables.set('infant1_passport', passport());",
          "",
          "  pm.collectionVariables.set('adult1_pexp', futureDate(3, 9));",
          "  pm.collectionVariables.set('adult2_pexp', futureDate(3, 9));",
          "  pm.collectionVariables.set('child1_pexp', futureDate(3, 9));",
          "  pm.collectionVariables.set('infant1_pexp', futureDate(3, 9));",
          "}"
        ]
      }
    },
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "try {",
          "  const body = pm.response.json();",
          "  for (const supplier of (body.suppliers || [])) {",
          "    const searchId = supplier.searchId || supplier.search_id;",
          "    const supplierId = supplier.supplierId || supplier.supplier_id || (supplier.meta && (supplier.meta.supplierId || supplier.meta.supplier_id));",
          "    const offers = (supplier.results && supplier.results.offers) || supplier.offers || [];",
          "    if (supplier.success && searchId) {",
          "      pm.collectionVariables.set('search_id', searchId);",
          "      if (supplierId) pm.collectionVariables.set('supplier_id', supplierId);",
          "      if (offers.length && offers[0].id) pm.collectionVariables.set('offer_id', offers[0].id);",
          "      break;",
          "    }",
          "  }",
          "} catch (error) {}"
        ]
      }
    }
  ],
  "item": [
    {
      "name": "01 - Flight Search",
      "description": "Search endpoints require a read or read+write key.\n\nPassenger age-array lengths must match their counts. Passenger limits can be supplier-specific. Incompatible suppliers are skipped silently when another selected supplier can return results; HTTP 400 is returned only when no selected supplier remains compatible. Some suppliers require seated_infants + infants <= adults + seniors, while children aged 2-11 are not subject to that limit.\n\nSupplier selection: omit selectors to call all enabled suppliers; use supplier_id for one supplier, supplier_ids for an exact enabled subset, preferred_suppliers for a lenient subset, and max_suppliers to cap automatic selection by priority.",
      "item": [
        {
          "name": "Search - One Way Economy",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"ORY\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  }\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Basic one-way search. Test script saves search_id, supplier_id, and the first offer_id for downstream requests."
          }
        },
        {
          "name": "Search - Round Trip Economy",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"return_date\": \"{{ret}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  }\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Basic round-trip search without market country filters."
          }
        },
        {
          "name": "Search - Family",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"return_date\": \"{{ret}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 2,\n    \"children\": 1,\n    \"child_ages\": [{{child1_age}}],\n    \"infants\": 1,\n    \"infant_ages\": [1]\n  }\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Round-trip search for 2 adults, 1 child, and 1 lap infant. child_ages and infant_ages are both present and match their counts. The generated child age is reused as the child's date_of_birth in 'Book Flight - Family Example', so search and booking remain consistent. Run this before the family booking."
          }
        },
        {
          "name": "Search - Duffel - 1 Adult + 3 Children",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Duffel returns the full family', () => {",
                  "  pm.response.to.have.status(200);",
                  "  const supplier = (pm.response.json().suppliers || [])[0] || {};",
                  "  const offers = ((supplier.results || {}).offers || []);",
                  "  const types = (((offers[0] || {}).travelerPricing) || []).map(item => item.travelerType);",
                  "  pm.expect(offers.length).to.be.above(0);",
                  "  pm.expect(types).to.eql(['ADULT', 'CHILD', 'CHILD', 'CHILD']);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"CDG\",\n  \"destination\": \"LHR\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1,\n    \"children\": 3,\n    \"child_ages\": [8, 6, 4]\n  },\n  \"supplier_id\": \"duffel_1\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "get_flights"]
            },
            "description": "One adult may travel with multiple normal children. Verified through Duffel sandbox search, price, and issue."
          }
        },
        {
          "name": "Search - Duffel - Age 2 Child + Lap Infant",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Duffel returns child and lap infant', () => {",
                  "  pm.response.to.have.status(200);",
                  "  const supplier = (pm.response.json().suppliers || [])[0] || {};",
                  "  const offers = ((supplier.results || {}).offers || []);",
                  "  const types = (((offers[0] || {}).travelerPricing) || []).map(item => item.travelerType);",
                  "  pm.expect(offers.length).to.be.above(0);",
                  "  pm.expect(types).to.eql(['ADULT', 'CHILD', 'HELD_INFANT']);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"CDG\",\n  \"destination\": \"LHR\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1,\n    \"children\": 1,\n    \"child_ages\": [2],\n    \"infants\": 1,\n    \"infant_ages\": [0]\n  },\n  \"supplier_id\": \"duffel_1\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "get_flights"]
            },
            "description": "A traveler who is truly age 2 is a normal child, not a seated infant. Verified through Duffel sandbox search, price, and issue with one lap infant."
          }
        },
        {
          "name": "Search - Complete Passenger Mix - Seated Infant",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "const suppliers = (body.suppliers || []).filter(s => s.success && (((s.results || {}).offers || []).length > 0));",
                  "const supplierIds = suppliers.map(s => ((s.meta || {}).supplierId || s.supplierId));",
                  "pm.test('At least one requested supplier returned offers', () => {",
                  "  pm.expect(body.success).to.eql(true);",
                  "  pm.expect(supplierIds.length).to.be.above(0);",
                  "});",
                  "pm.test('Every supplier preserves all six traveler types', () => {",
                  "  suppliers.forEach(s => {",
                  "    const travelerPricing = s.results.offers[0].travelerPricing || [];",
                  "    const travelerTypes = travelerPricing.map(item => item.travelerType);",
                  "    pm.expect(travelerPricing.length).to.eql(6);",
                  "    pm.expect(travelerTypes).to.include.members(['ADULT', 'CHILD', 'HELD_INFANT', 'SEATED_INFANT']);",
                  "  });",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 2,\n    \"seniors\": 0,\n    \"young_adults\": 0,\n    \"children\": 2,\n    \"child_ages\": [10, 6],\n    \"infants\": 1,\n    \"infant_ages\": [1],\n    \"seated_infants\": 1,\n    \"seated_infant_ages\": [1]\n  },\n  \"supplier_ids\": [\"amadeus_aqc_dz_1\", \"duffel_1\"]\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Validated complete passenger mix against multiple suppliers: 2 adults, 2 children, 1 lap infant, and 1 seated infant. Two adult hosts satisfy suppliers that require one adult/senior per under-2 passenger. Incompatible suppliers are skipped silently when another selected supplier returns results."
          }
        },
        {
          "name": "Search - Supplier Fallback - 1 Adult + Seated and Lap Infant",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const body = pm.response.json();",
                  "if (pm.response.code === 200) {",
                  "  pm.test('Compatible results do not include a compatibility warning', () => {",
                  "    const offers = (body.suppliers || []).flatMap(s => ((s.results || {}).offers || []));",
                  "    pm.expect(offers.length).to.be.above(0);",
                  "    pm.expect(body.error).not.to.eql('SUPPLIER_PASSENGER_COMBINATION_UNSUPPORTED');",
                  "  });",
                  "}",
                  "pm.test('Response follows supplier availability contract', () => {",
                  "  pm.expect([200, 503]).to.include(pm.response.code);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{fallback_dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1,\n    \"seated_infants\": 1,\n    \"seated_infant_ages\": [0],\n    \"infants\": 1,\n    \"infant_ages\": [0]\n  },\n  \"flexible\": true\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "get_flights"]
            },
            "description": "Supplier-specific passenger limits are resolved automatically on the stable ALG-CDG test market. Incompatible suppliers are skipped silently. If any compatible supplier returns offers, the response is HTTP 200 without a compatibility warning; HTTP 400 is used only when none remain compatible."
          }
        },
        {
          "name": "Search - Adult Senior Young - Duffel",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "const supplier = (body.suppliers || [])[0] || {};",
                  "const offers = (supplier.results && supplier.results.offers) || [];",
                  "const travelerPricing = (offers[0] && offers[0].travelerPricing) || [];",
                  "pm.test('Supplier returned a three-traveler offer', () => {",
                  "  pm.expect(body.success).to.eql(true);",
                  "  pm.expect(supplier.success).to.eql(true);",
                  "  pm.expect(offers.length).to.be.above(0);",
                  "  pm.expect(travelerPricing.length).to.eql(3);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1,\n    \"seniors\": 1,\n    \"young_adults\": 1,\n    \"young_adult_ages\": [16]\n  },\n  \"supplier_id\": \"duffel_1\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Validated mixed-category search for one adult, one senior, and one 16-year-old young traveler. Duffel prices seniors as adults internally; FX-Port preserves the requested logical traveler types in unified responses."
          }
        },
        {
          "name": "Search - Business Class",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"business\",\n  \"passengers\": {\n    \"adults\": 1\n  }\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "One-way search restricted to the business cabin (cabin_class=business)."
          }
        },
        {
          "name": "Search - Premium Economy",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"premium_economy\",\n  \"passengers\": {\n    \"adults\": 1\n  }\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "One-way search restricted to the premium economy cabin (cabin_class=premium_economy)."
          }
        },
        {
          "name": "Search - All Cabin Classes",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"any\",\n  \"passengers\": {\n    \"adults\": 1\n  }\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Search across every cabin class. Set cabin_class=any to send no cabin restriction to the suppliers, returning offers in all available cabins."
          }
        },
        {
          "name": "Search - All Suppliers Flexible",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"return_date\": \"{{ret}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"flexible\": true\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Search all enabled suppliers at once (no supplier_id) with flexible=true. Regular offers may come from every compatible supplier; suppliers with flexible-date support may additionally return a +/-3-day flexPrices matrix. Always check for matrix presence."
          }
        },
        {
          "name": "Search - One Way All Suppliers Flexible",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"flexible\": true\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "One-way search across all enabled suppliers with flexible=true. Suppliers without flexible-date support can still return regular offers; capable suppliers may also return a flexPrices calendar. Do not assume every supplier populates the matrix."
          }
        },
        {
          "name": "Search - Round Trip All Suppliers Include Airline",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"return_date\": \"{{ret}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"included_airlines\": [\"AH\"]\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Round-trip search across all enabled suppliers filtered to a specific airline. included_airlines restricts results to offers operated or marketed by the listed IATA codes. Suppliers that do not carry that airline will return empty offers."
          }
        },
        {
          "name": "Search - Round Trip All Suppliers Exclude Airline",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"return_date\": \"{{ret}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"excluded_airlines\": [\"AH\"]\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Round-trip search across all enabled suppliers excluding a specific airline. excluded_airlines removes offers marketed or operated by the listed IATA codes from all suppliers."
          }
        },
        {
          "name": "Search - Direct Only",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"ORY\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"direct\": true\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Direct-only search. The backend maps direct=true to stops=0."
          }
        },
        {
          "name": "Search - Refundable Only",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"refundable\": true\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Search filtered to refundable offers when supported by the supplier."
          }
        },
        {
          "name": "Search - Checked Bags Only",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"return_date\": \"{{ret}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"checked_bags\": true\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Search filtered to offers with checked baggage included when supported by the supplier."
          }
        },
        {
          "name": "Search - Open Jaw",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"segments\": [\n    {\n      \"origin\": \"ALG\",\n      \"destination\": \"CDG\",\n      \"date\": \"{{dep}}\"\n    },\n    {\n      \"origin\": \"ORY\",\n      \"destination\": \"ALG\",\n      \"date\": \"{{ret}}\"\n    }\n  ],\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  }\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Open-jaw search using exactly two non-reversed segments. Multi-destination search is not currently listed in this public collection."
          }
        },
        {
          "name": "Search - Price Cap Market and Preferred Airlines",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"max_price\": 50000,\n  \"preferred_airlines\": [\n    \"AF\",\n    \"AH\"\n  ],\n  \"from_cc\": \"DZ\",\n  \"to_cc\": \"FR\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Combines a maximum total-price cap, market country hints, and preferred airline ordering. max_price is expressed in the agency currency. Market hints help FX-Port select suitable supplier offices; preferred_airlines prioritizes but does not strictly whitelist carriers."
          },
          "description": "Combines a maximum total-price cap, market country hints, and preferred airline ordering. max_price is expressed in the agency currency (DZD for this example). Market hints help FX-Port select suitable supplier offices; preferred_airlines prioritizes but does not strictly whitelist carriers."
        },
        {
          "name": "Search - Selected Suppliers Array (Exact Subset)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"supplier_ids\": [\n    \"amadeus_aqc_dz_1\",\n    \"duffel_1\"\n  ]\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Calls the enabled suppliers listed in supplier_ids. Unknown, disabled, or flexible-incompatible IDs return HTTP 400. Traveler limits may be supplier-specific: incompatible suppliers are skipped silently, and HTTP 400 is returned only when none remain compatible."
          }
        },
        {
          "name": "Search - Supplier Duffel Only",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Duffel search returned offers', () => {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  const supplier = (body.suppliers || [])[0] || {};",
                  "  const offers = (supplier.results && supplier.results.offers) || [];",
                  "  pm.expect(body.success).to.eql(true);",
                  "  pm.expect(supplier.success).to.eql(true);",
                  "  pm.expect(offers.length).to.be.above(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"ORY\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"supplier_id\": \"duffel_1\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Searches only Duffel by passing supplier_id=duffel_1. No other suppliers are called."
          }
        },
        {
          "name": "Search - Supplier Amadeus Algeria Only",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"ORN\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"supplier_id\": \"amadeus_aqc_dz_1\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Searches only Amadeus Algeria on the sandbox-friendly ALG-ORN route by passing supplier_id=amadeus_aqc_dz_1. No other suppliers are called."
          }
        },
        {
          "name": "Search - Include Airlines Amadeus Algeria",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"supplier_id\": \"amadeus_aqc_dz_1\",\n  \"included_airlines\": [\"AF\", \"AH\"]\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Whitelist search: include means only these airline IATA codes are allowed. For Amadeus Algeria, FX-Port sends includedCarrierCodes only because Amadeus rejects combining includedCarrierCodes and excludedCarrierCodes."
          }
        },
        {
          "name": "Search - Exclude Airline Amadeus Algeria",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"supplier_id\": \"amadeus_aqc_dz_1\",\n  \"excluded_airlines\": [\"TK\"]\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Blacklist search: excludes offers involving the requested airline IATA code. Amadeus Algeria also keeps its default BA exclusion when no include whitelist is present."
          }
        },
        {
          "name": "Search - Include Airline Duffel",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"supplier_id\": \"duffel_1\",\n  \"included_airlines\": [\"AF\"]\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Whitelist search on ALG-CDG: only offers involving AF are kept. Duffel v2 offer-request docs do not expose a native airline include request field, so FX-Port applies this to returned offers while preserving the normal response shape."
          }
        },
        {
          "name": "Search - Exclude Airline Duffel",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"ORY\",\n  \"departure_date\": \"{{dep}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"supplier_id\": \"duffel_1\",\n  \"excluded_airlines\": [\"BA\"]\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/get_flights",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "get_flights"
              ]
            },
            "description": "Blacklist search: excludes offers involving the requested airline IATA code. Duffel v2 offer-request docs do not expose a native airline exclude request field, so FX-Port applies this to returned offers while preserving the normal response shape."
          }
        },
        {
          "name": "Flexible Search - Amadeus Algeria",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin\": \"ALG\",\n  \"destination\": \"CDG\",\n  \"departure_date\": \"{{dep}}\",\n  \"return_date\": \"{{ret}}\",\n  \"cabin_class\": \"economy\",\n  \"passengers\": {\n    \"adults\": 1\n  },\n  \"supplier_id\": \"amadeus_aqc_dz_1\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/flights/flexible_search",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "flights",
                "flexible_search"
              ]
            },
            "description": "Runs the flexible-search endpoint, which forces flexible=true and flexible_only=true. This request targets a currently capable supplier; integrations should treat flexible-date support as a supplier capability and always check flexPrices."
          }
        }
      ]
    },
    {
      "name": "02 - Pricing",
      "description": "Pricing example. Depends on search_id/offer_id from a successful search. Baggage inclusion (include_bags) is not yet public — it will be released later and announced by email.",
      "item": [
        {
          "name": "Price Flight",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Pricing succeeded', () => {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.success).to.eql(true);",
                  "  pm.expect(((body.results || {}).offers || []).length).to.eql(1);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"search_id\": \"{{search_id}}\",\n  \"offer_id\": \"{{offer_id}}\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/flights/price_flight",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "flights",
                "price_flight"
              ]
            },
            "description": "Refreshes pricing for the selected offer. search_id must be the supplier-level searchId returned in the search response."
          }
        }
      ]
    },
    {
      "name": "03 - Booking",
      "description": "Create sandbox bookings with a read+write key. Price immediately before booking. Use booking_type=hold only when the selected repriced offer returns supportHold=true; otherwise issue immediately or choose another offer.",
      "item": [
        {
          "name": "Book Flight - Hold",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "try {",
                  "  const body = pm.response.json();",
                  "  const booking = body.booking || (body.data && body.data.booking) || {};",
                  "  const orderId = booking.reference || booking.orderId || body.orderId || body.order_id;",
                  "  const pnr = booking.pnr || booking.bookingId || body.bookingId || body.booking_id;",
                  "  if (orderId) pm.collectionVariables.set('order_id', orderId);",
                  "  if (pnr) pm.collectionVariables.set('booking_id', pnr);",
                  "  pm.test('Hold booking succeeded', () => {",
                  "    pm.response.to.have.status(200);",
                  "    pm.expect(body.success).to.eql(true);",
                  "    pm.expect(orderId).to.be.ok;",
                  "  });",
                  "} catch (error) {}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"search_id\": \"{{search_id}}\",\n  \"offer_id\": \"{{offer_id}}\",\n  \"booking_type\": \"hold\",\n  \"send_email\": false,\n  \"passengers\": [\n    {\n      \"type\": \"adult\",\n      \"gender\": \"MALE\",\n      \"first_name\": \"{{male_first_1}}\",\n      \"last_name\": \"{{male_last_1}}\",\n      \"date_of_birth\": \"{{adult1_dob}}\",\n      \"identity_documents\": [\n        {\n          \"type\": \"passport\",\n          \"number\": \"{{adult1_passport}}\",\n          \"issuing_country\": \"DZ\",\n          \"expiry_date\": \"{{adult1_pexp}}\"\n        }\n      ]\n    }\n  ],\n  \"contact_details\": {\n    \"email\": \"sandbox.flight@example.com\",\n    \"phone_number\": \"+213554657687\"\n  }\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/flights/book_flight",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "flights",
                "book_flight"
              ]
            },
            "description": "Creates an unpaid reservation only when the selected repriced offer returns supportHold=true. The response can use status=pending before storage normalizes it to awaiting_payment. No ticket is issued and no balance is charged. Test script saves order_id and booking_id when present."
          }
        },
        {
          "name": "Book Flight - Issue",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "try {",
                  "  const body = pm.response.json();",
                  "  const booking = body.booking || (body.data && body.data.booking) || {};",
                  "  const orderId = booking.reference || booking.orderId || body.orderId || body.order_id;",
                  "  const pnr = booking.pnr || booking.bookingId || body.bookingId || body.booking_id;",
                  "  if (orderId) pm.collectionVariables.set('order_id', orderId);",
                  "  if (pnr) pm.collectionVariables.set('booking_id', pnr);",
                  "  pm.test('Instant issue succeeded', () => {",
                  "    pm.response.to.have.status(200);",
                  "    pm.expect(body.success).to.eql(true);",
                  "    pm.expect(booking.status).to.eql('confirmed');",
                  "    pm.expect(orderId).to.be.ok;",
                  "  });",
                  "} catch (error) {}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"search_id\": \"{{search_id}}\",\n  \"offer_id\": \"{{offer_id}}\",\n  \"booking_type\": \"issue\",\n  \"send_email\": false,\n  \"passengers\": [\n    {\n      \"type\": \"adult\",\n      \"gender\": \"MALE\",\n      \"first_name\": \"{{male_first_1}}\",\n      \"last_name\": \"{{male_last_1}}\",\n      \"date_of_birth\": \"{{adult1_dob}}\",\n      \"identity_documents\": [\n        {\n          \"type\": \"passport\",\n          \"number\": \"{{adult1_passport}}\",\n          \"issuing_country\": \"DZ\",\n          \"expiry_date\": \"{{adult1_pexp}}\"\n        }\n      ]\n    }\n  ],\n  \"contact_details\": {\n    \"email\": \"sandbox.flight@example.com\",\n    \"phone_number\": \"+213554657687\"\n  }\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/flights/book_flight",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "flights",
                "book_flight"
              ]
            },
            "description": "Creates an instant/issued booking in sandbox. send_email=false keeps examples quiet."
          }
        },
        {
          "name": "Book Flight - Family Example",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "try {",
                  "  const body = pm.response.json();",
                  "  const booking = body.booking || (body.data && body.data.booking) || {};",
                  "  const orderId = booking.reference || booking.orderId || body.orderId || body.order_id;",
                  "  const pnr = booking.pnr || booking.bookingId || body.bookingId || body.booking_id;",
                  "  if (orderId) pm.collectionVariables.set('order_id', orderId);",
                  "  if (pnr) pm.collectionVariables.set('booking_id', pnr);",
                  "} catch (error) {}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"search_id\": \"{{search_id}}\",\n  \"offer_id\": \"{{offer_id}}\",\n  \"booking_type\": \"issue\",\n  \"send_email\": false,\n  \"passengers\": [\n    {\n      \"type\": \"adult\",\n      \"gender\": \"MALE\",\n      \"first_name\": \"{{male_first_1}}\",\n      \"last_name\": \"{{male_last_1}}\",\n      \"date_of_birth\": \"{{adult1_dob}}\",\n      \"identity_documents\": [{ \"type\": \"passport\", \"number\": \"{{adult1_passport}}\", \"issuing_country\": \"DZ\", \"expiry_date\": \"{{adult1_pexp}}\" }]\n    },\n    {\n      \"type\": \"adult\",\n      \"gender\": \"FEMALE\",\n      \"first_name\": \"{{female_first_1}}\",\n      \"last_name\": \"{{female_last_1}}\",\n      \"date_of_birth\": \"{{adult2_dob}}\",\n      \"identity_documents\": [{ \"type\": \"passport\", \"number\": \"{{adult2_passport}}\", \"issuing_country\": \"DZ\", \"expiry_date\": \"{{adult2_pexp}}\" }]\n    },\n    {\n      \"type\": \"child\",\n      \"gender\": \"MALE\",\n      \"first_name\": \"{{child_first_1}}\",\n      \"last_name\": \"{{child_last_1}}\",\n      \"date_of_birth\": \"{{child1_dob}}\",\n      \"identity_documents\": [{ \"type\": \"passport\", \"number\": \"{{child1_passport}}\", \"issuing_country\": \"DZ\", \"expiry_date\": \"{{child1_pexp}}\" }]\n    },\n    {\n      \"type\": \"infant\",\n      \"gender\": \"FEMALE\",\n      \"first_name\": \"{{infant_first_1}}\",\n      \"last_name\": \"{{infant_last_1}}\",\n      \"date_of_birth\": \"{{infant1_dob}}\",\n      \"identity_documents\": [{ \"type\": \"passport\", \"number\": \"{{infant1_passport}}\", \"issuing_country\": \"DZ\", \"expiry_date\": \"{{infant1_pexp}}\" }]\n    }\n  ],\n  \"contact_details\": {\n    \"email\": \"sandbox.flight@example.com\",\n    \"phone_number\": \"+213554657687\"\n  }\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/flights/book_flight",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "flights",
                "book_flight"
              ]
            },
            "description": "Family booking payload (2 adults, 1 child, 1 infant). Run 'Search - Family' first so the child age and passenger counts match. Names, dates of birth, passport numbers, and expiry dates are randomized by the collection pre-request script."
          }
        }
      ]
    },
    {
      "name": "04 - Held Orders",
      "description": "Quote, pay, or cancel held reservations by order_id. A displayed deadline and canPay=true are not guarantees: the PNR may already be expired, cancelled, past its payment deadline, or released early by the airline.",
      "item": [
        {
          "name": "Get Order Quote",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const orderId = pm.collectionVariables.get('order_id') || pm.environment.get('order_id');",
                  "if (!orderId) {",
                  "  throw new Error('order_id is empty. Run Hold Booking or Book Flight first to populate it.');",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "  const body = pm.response.json();",
                  "  const quote = body.orderQuote || (body.data && body.data.orderQuote) || body;",
                  "  const orderId = quote.orderId || quote.order_id || body.order_id;",
                  "  if (orderId) pm.collectionVariables.set('order_id', orderId);",
                  "  const paymentStatus = quote.paymentStatus || {};",
                  "  pm.test('Held-order quote succeeded', () => {",
                  "    pm.response.to.have.status(200);",
                  "    pm.expect(body.success).to.eql(true);",
                  "    pm.expect(paymentStatus.awaitingPayment).to.eql(true);",
                  "  });"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"order_id\": \"{{order_id}}\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/flights/get_order_quote",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "flights",
                "get_order_quote"
              ]
            },
            "description": "Returns the current order price and payment state for a held booking. Accepts order_id (preferred), booking_id, or pnr. PNRs are not globally unique, so prefer order_id. b2bPrice/totalAmount is the agency debit (= finalPrice - agencyCommission). canPay=true is not a guarantee: payment can still fail if the PNR expired, was cancelled, passed its deadline, or was released early by the airline."
          }
        },
        {
          "name": "Pay Order",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const orderId = pm.collectionVariables.get('order_id') || pm.environment.get('order_id');",
                  "if (!orderId) {",
                  "  throw new Error('order_id is empty. Run Hold Booking or Get Order Quote first to populate it.');",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Held-order payment succeeded', () => {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.success).to.eql(true);",
                  "  pm.expect(body.paymentSucceeded).to.eql(true);",
                  "  pm.expect((body.paymentDetails || {}).status).to.eql('succeeded');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"order_id\": \"{{order_id}}\",\n  \"payment_type\": \"balance\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/flights/pay_order",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "flights",
                "pay_order"
              ]
            },
            "description": "Pays and issues a held order. Prefer order_id; amount and currency are resolved server-side. Payment cannot revive an expired or cancelled PNR, a hold past its deadline, or a reservation released early by the airline. Treat paymentSucceeded and paymentDetails.status as authoritative. Get Order Quote is optional and canPay is not a guarantee."
          }
        },
        {
          "name": "Cancel Held Reservation",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const orderId = pm.collectionVariables.get('order_id') || pm.environment.get('order_id');",
                  "if (!orderId) {",
                  "  throw new Error('order_id is empty. Run Hold Booking or Book Flight first to populate it.');",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"order_id\": \"{{order_id}}\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/flights/cancel_order",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "flights",
                "cancel_order"
              ]
            },
            "description": "Cancels an unpaid held reservation before ticket issuance when that booking's supplier supports hold cancellation. Accepts order_id (preferred), booking_id, or pnr. This releases the PNR; it is not a refund."
          }
        }
      ]
    },
    {
      "name": "05 - Booking and Ticket Reads",
      "description": "Read-only flight booking and ticket lookup endpoints. Defaults use known sandbox booking V64R4V.",
      "item": [
        {
          "name": "Get Booking",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const bookingId = pm.collectionVariables.get('booking_id') || pm.environment.get('booking_id');",
                  "if (!bookingId) {",
                  "  throw new Error('booking_id is empty. Run Book Flight, Hold Booking, or a flight booking read that populates it first.');",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"booking_id\": \"{{booking_id}}\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/flights/get_booking",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "flights",
                "get_booking"
              ]
            },
            "description": "Fetch by PNR, supplier order id, or internal UUID. Environment and agency are resolved from the API key."
          }
        },
        {
          "name": "Get Ticket PDF",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const bookingId = pm.collectionVariables.get('booking_id') || pm.environment.get('booking_id');",
                  "if (!bookingId) {",
                  "  throw new Error('booking_id is empty. Run Book Flight or a flight booking read that populates it first.');",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"booking_id\": \"{{booking_id}}\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/v1/flights/get_ticket",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "flights",
                "get_ticket"
              ]
            },
            "description": "Returns the ticket PDF download URL when the PDF is ready. If ticket_ready=false, retry later."
          }
        }
      ]
    },
    {
      "name": "06 - Airport Utilities",
      "description": "API-facing airport autocomplete and fallback ranking utilities. Internal airport metrics are excluded.",
      "item": [
        {
          "name": "Search Airports",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/flights/airports?query=ALG&locale=en",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "flights",
                "airports"
              ],
              "query": [
                {
                  "key": "query",
                  "value": "ALG"
                },
                {
                  "key": "locale",
                  "value": "en"
                }
              ]
            },
            "description": "Airport autocomplete. Query can be an IATA code, city, or airport name."
          }
        }
      ]
    }
  ]
}
