# Search flights

> Search for flights across one, several or every enabled supplier. Start here, then expand the examples by route, passenger, filter, supplier or itinerary type.

Endpoint: `POST /api/v1/get_flights`

Source: https://fx-port.com/docs/flights/search

Search available flight offers across every compatible supplier. Results are grouped under `suppliers[]`: each supplier has its own `searchId` and its own list of offers.

**Permission:** Read (a read key is enough; so is a read+write key). **Rate group:** search, 6,000 requests per minute per key.


> [!WARNING]
> **Save the supplier-level `searchId`.** Pass `suppliers[n].searchId` to `price_flight` and `book_flight`, never the top-level `requestId`. A request that fans out to two suppliers returns two `searchId` values, and an offer can only be priced with the `searchId` of the supplier entry it is in.


## What to expect

- **Size.** A search returns 0.7 MB (one-way, two suppliers) to 14 MB of JSON (a busy round trip with 2,000 offers). **Send `Accept-Encoding: gzip`**: the same 695 KB response is 26 KB on the wire. There is **no pagination**: `results.totalOffers` always equals the length of `results.offers`.
- **Time.** Two to eight seconds, because the response waits for the slowest supplier. To show the first fares after about two seconds, read the results as a [stream](https://fx-port.com/docs/streaming.md).
- **Partial results are normal.** If at least one supplier answers, the status is `200` and the others are listed in `failures[]`. Only when no supplier answers is the status `503`.
- **Values are read generously, garbage is refused.** A value a real client can send (`"2"` for a count, `premium economy` for a cabin, a space between airline codes) is read, with a `coerced_value` warning when its meaning had to be guessed ([Conventions](https://fx-port.com/docs/conventions.md)). Only a value that makes no sense is a `400` with a stable code, before any supplier is called.
- **Identical searches are merged.** Searches with the same parameters that arrive at the same moment from the same agency, environment and currency are merged into **one** supplier fan-out. Nothing is kept afterwards, and every caller still gets its own `requestId` and headers and a `searchId` that works with `price_flight`.
- **Sandbox suppliers.** `amadeus_aqc_dz_1` (Amadeus Algeria, only for routes that touch Algeria; supports hold, void and flexible dates) and `duffel_1` (Duffel, worldwide; instant ticketing only). See [Supplier selection](https://fx-port.com/docs/flights/search/suppliers.md).

## Request fields

### Route

| Field | Type | Required | Description |
|---|---|---|---|
| `origin` | string | Yes¹ | Departure IATA code: three ASCII letters, an airport (`ALG`, `CDG`) or a city (`PAR`, `LON`). Lower case is accepted and read as upper case. |
| `destination` | string | Yes¹ | Arrival IATA code. Must differ from `origin`. |
| `departure_date` | string | Yes¹ | `YYYY-MM-DD`, a real calendar date from yesterday (time-zone slack) up to 365 days ahead (`date_too_far` beyond). |
| `return_date` | string | No | Makes the search a round trip. On or after `departure_date`, and within the same 365 days. |
| `segments` | array | No² | 1 to 6 flights `{ origin, destination, departure_date }` instead of the four fields above. Dates must not go backwards. See [Open-jaw and multi-city](https://fx-port.com/docs/flights/search/complex-itineraries.md). |
| `url` | string | No | A path-style search used by FX-Port's own site, for example `flights/ALG-CDG/2026-12-15/economy/1adults`. It wins over every route field. Prefer the fields above: they are validated field by field. |

_¹ Required unless `segments` or `url` is sent. ² A list of segments replaces `origin`, `destination`, `departure_date` and `return_date`._

The type of the search comes from the route: one segment is `ONE_WAY`; two segments where the second is the exact reverse of the first (or `return_date`) is `ROUND_TRIP`; two segments that are not reverses is `OPEN_JAW`; three or more is `MULTI_CITY`.

Several airports can be written `"ALG,ORN"` or as a list `["ALG","ORN"]` (up to five, in `origin` and `destination` only: a segment takes one code): the first one is searched and the others are kept as alternatives that a supplier may ignore (Duffel does). Search one airport per request when you need a predictable result.

### Passengers and cabin

| Field | Type | Required | Description |
|---|---|---|---|
| `passengers` | object | No | Counts and age lists. **Optional: the default is one adult.** See [Flight passengers & ages](https://fx-port.com/docs/flights/passengers.md). |
| `cabin_class` | string | No | `economy` (default), `premium_economy`, `business`, `first` or `any` (no cabin restriction). Case, spaces, dashes and underscores do not matter: `Premium Economy`, `premium-economy` and `PREMIUM_ECONOMY` are the same cabin. `premium`, `economy_class`, `business_class`, `first_class` and `premium_economy_class` are read as their cabin with a `coerced_value` warning, and an empty text means the default cabin. Any other value (`coach`, `Y`) is rejected with `400 invalid_cabin_class`. |

The cabin you send is lower case; the cabin of each segment in the response is upper case (`ECONOMY`, `PREMIUM_ECONOMY`, `BUSINESS`, `FIRST`).

### Filters

| Field | Type | Description |
|---|---|---|
| `nonstop` | boolean | `true` returns non-stop offers only. |
| `refundable` | boolean | `true` asks the supplier for refundable fares. A hint, not a guarantee: see the table below. |
| `checked_bags` | boolean | `true` asks for fares with a checked bag included. |
| `max_price` | number | A price ceiling. **Not enforced in the sandbox**; see below. `0`, an empty text and `null` mean no ceiling: the field is ignored (`0` and an empty text add a `coerced_value` warning). Numeric text (`"1500"`) is read as a number with a warning. A negative or non-numeric value (`"cheap"`, `"150000 DZD"`) is `400 invalid_field_type`. |
| `included_airlines` | string[] or text | Up to 20 airline codes: `["AF","AH"]`, or a text with the codes separated by commas, semicolons, bars or spaces (`"AF,AH"`, `"AF AH"`). Only these carriers. |
| `excluded_airlines` | string[] or text | Up to 20 airline codes to remove, written like `included_airlines`. |
| `preferred_airlines` | string[] or text | Behaves exactly like `included_airlines`: a strict allow-list, **not** a ranking. |
| `flexible` | boolean | Also returns the date matrix (`flexPrices`) from suppliers that support it, next to the offers. See [Flexible-date search](https://fx-port.com/docs/flights/flexible-search.md). |
| `flexible_only` | boolean | The matrix without offers. `POST /flights/flexible_search` sends it for you. |
| `from_cc`, `to_cc` | string | ISO country codes of the origin and the destination: **exactly two letters** (`DZ`; a three-letter value is `400 invalid_country_code`). Used to choose suppliers; see [Supplier selection](https://fx-port.com/docs/flights/search/suppliers.md). |
| `countries` | string[] | Up to 10 country codes of the route, when you want to override the countries FX-Port infers from the airports. |
| `filters` | object | Only the three airline lists above are read from it. Any other key (`{"max_stops": 0}`) is ignored. |

**What each supplier enforces.** `filtersApplied` in the response repeats what you **asked for**, for every supplier, not what the supplier honoured:

| Filter | Amadeus Algeria (`amadeus_aqc_dz_1`) | Duffel (`duffel_1`) |
|---|---|---|
| `nonstop` | Enforced. | Enforced. |
| `refundable` | Sent to the supplier; the result still mixes refundable and non-refundable fares (70 offers became 53, still mixed). | Ignored. |
| `checked_bags` | Enforced (included checked bags only). | **Ignored**: offers with no checked bag are returned. |
| `max_price` | Sent in the **supplier's** currency (DZD), and **not enforced in the sandbox**: with `max_price: 40000` offers up to 242,844 DZD came back. | Never sent; offers up to 280,279 DZD came back. |
| `included_airlines`, `preferred_airlines` | Native carrier restriction: the validating carrier is in the list. | A filter on the returned offers: an offer stays when **any** marketing, operating or validating carrier matches, so `validatingAirline` can be a carrier you did not list. |
| `excluded_airlines` | Native. | A filter on the returned offers. |

Always read the offer itself (`pricing`, `travelerPricing[].fareDetailsBySegment[].includedCheckedBags`, `conditions`) and filter again on your side when a filter is a hard requirement. You can send `included_airlines` and `excluded_airlines` together: the excluded codes are taken out of the included list.

### Supplier selection

| Field | Type | Description |
|---|---|---|
| *(none)* | | **Recommended.** FX-Port selects the compatible suppliers. |
| `supplier_id` | string | Restrict the search to one supplier id (a comma separated list is accepted). Strict: an unknown or disabled id is `400 invalid_supplier_selection`. |
| `supplier_ids` | string[] | Restrict the search to this set. Strict, up to 10. Merged with `supplier_id`. |
| `preferred_suppliers` | string[] | A hint: ids that are not usable are ignored and the automatic selection applies. |
| `max_suppliers` | integer | Keep only the first N selected suppliers, in priority order. `"3"` and `3.0` are read as 3 with a `coerced_value` warning; an empty text means no limit. |

### Other fields

| Field | Type | Description |
|---|---|---|
| `loyalty_program_accounts` | array | Up to 10 frequent-flyer accounts `{ airline_iata_code, account_number, first_name, last_name }`. They are stored with the search and matched by passenger name at booking. |
| `acd_exempt_company_id` | string | For agencies that settle in DZD and have an ACD-exempt company: its id, to price the fares without ACD. A company that is not linked to your agency is `403 acd_exemption_invalid`, a non-DZD agency `403 acd_exemption_currency`. See [ACD passport uploads](https://fx-port.com/docs/flights/acd-documents.md). |
| `agent_id` | string | Your own id of the agent who searches; stored with the search and the booking. |

### Legacy names

These spellings still work. The response says so in `warnings` (`deprecated_field`) and in the headers `Deprecation` and `X-FX-Deprecated-Fields`. New code should send the name in the second column. If a request holds a legacy name **and** its replacement with different values (`direct` and `nonstop`), the replacement wins and an `ignored_field` warning names the dropped one.

| Legacy name | Use instead |
|---|---|
| `departureDate`, `returnDate`, `cabinClass` | `departure_date`, `return_date`, `cabin_class` |
| `travelers` | `passengers` |
| `direct` | `nonstop` |
| `checkedBags`, `maxPrice`, `flexibleOnly` | `checked_bags`, `max_price`, `flexible_only` |
| `fromCc`, `toCc` | `from_cc`, `to_cc` |
| `supplierId`, `supplierIds`, `preferredSuppliers`, `maxSuppliers` | `supplier_id`, `supplier_ids`, `preferred_suppliers`, `max_suppliers` |
| `includedAirlines`, `include_airlines`, `includeAirlines`, `included_airline`, `include_airline`, `airline_include`, `airlineInclude` | `included_airlines` |
| `excludedAirlines`, `exclude_airlines`, `excludeAirlines`, `excluded_airline`, `exclude_airline`, `airline_exclude`, `airlineExclude` | `excluded_airlines` |
| `preferredAirlines`, `airlines` | `preferred_airlines` |
| `passengers.young`, `passengers.youngAdults` | `passengers.young_adults` |
| `passengers.toddlers`, `passengers.seatedInfants` | `passengers.seated_infants` |
| `passengers.childAges`, `youngAdultAges`, `seatedInfantAges`, `infantAges` | `passengers.child_ages`, `young_adult_ages`, `seated_infant_ages`, `infant_ages` |
| `segments[].date`, `segments[].departureDate`, `segments[].departure` | `segments[].departure_date` |
| `agentId`, `acdExemptCompanyId`, `loyaltyProgramAccounts` | `agent_id`, `acd_exempt_company_id`, `loyalty_program_accounts` |

A key the endpoint does not read at all (a typo, the flat `adults`, `max_stops` inside `filters`) never fails the request: it is ignored and reported as an `ignored_field` warning.

## Examples by category


- [Basic routes](https://fx-port.com/docs/flights/search/basic-routes.md): One-way, round trip, non-stop and every-supplier searches.
- [Passengers & cabins](https://fx-port.com/docs/flights/search/passenger-and-cabin.md): Family mix, lap and seated infants, senior and young adult, business, premium economy, all cabins.
- [Filters](https://fx-port.com/docs/flights/search/filters.md): Refundable, checked bags, price cap, airline include and exclude, flexible dates.
- [Supplier selection](https://fx-port.com/docs/flights/search/suppliers.md): Automatic selection, exact supplier ids and the routing rules.
- [Open-jaw and multi-city](https://fx-port.com/docs/flights/search/complex-itineraries.md): Two or more segments.


## Minimal request


**Request**

```bash
curl https://api.fx-port.com/api/v1/get_flights \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "origin": "ALG",
  "destination": "CDG",
  "departure_date": "2026-12-15",
  "cabin_class": "economy",
  "passengers": {
    "adults": 1
  }
}'
```


## Response essentials

The example is a trimmed **real sandbox response** to the request above: the Duffel entry with one non-stop offer (the sandbox airline `ZZ` is Duffel's test carrier). The other supplier's entry has the same shape. Arrays are cut and the `amenities` list is shortened; the field tables below describe everything that is not shown.

~~~json
{
  "environment": "sandbox",
  "success": true,
  "requestId": "54139f32",
  "searchType": "ONE_WAY",
  "flexible": false,
  "processingTime": 2.243,
  "suppliers": [
    {
      "success": true,
      "searchId": "fs_dfx_a3aeaa36-74ad-424b-b88d-e6bdc9a4ba94",
      "supportHold": false,
      "supportVoid": false,
      "voidDeadline": null,
      "filtersApplied": {},
      "warnings": [],
      "meta": {
        "supplierId": "duffel_1",
        "technologyType": "duffel",
        "supplierName": "Duffel",
        "processingTime": 1.34,
        "supplierCurrency": "USD",
        "agencyCurrency": "DZD"
      },
      "results": {
        "totalOffers": 92,
        "supplierCurrency": "USD",
        "agencyCurrency": "DZD",
        "offers": [
          {
            "id": "fx_offer_9ab6d940",
            "type": "flight_offer",
            "supplier": { "id": "duffel_1", "name": "Duffel", "originalId": "off_034q6JKdiQDZ8HIDMFus64" },
            "bookableSeats": null,
            "supportHold": false,
            "supportVoid": false,
            "voidDeadline": null,
            "filtersApplied": {},
            "warnings": [],
            "instantTicketingRequired": false,
            "lastTicketingDate": "2026-11-01",
            "lastTicketingDateTime": "2026-11-01T15:52:40.830072Z",
            "pricing": {
              "supplierCurrency": "USD",
              "agencyCurrency": "DZD",
              "basePrice": 16385.0,
              "taxesAndFees": 5226.2,
              "agencyCommission": 21.62,
              "finalPrice": 21632.82,
              "b2bPrice": 21611.2,
              "additionalServices": []
            },
            "itinerary": [
              {
                "direction": "outbound",
                "duration": "PT2H24M",
                "stops": 0,
                "segments": [
                  {
                    "id": "seg_0000BC0a5iUChSV6Qrz0Co",
                    "departure": {
                      "airportCode": "ALG",
                      "airportName": "Houari Boumediene Airport",
                      "cityName": "Algiers",
                      "countryCode": "DZ",
                      "terminal": "2",
                      "datetime": "2026-12-15T10:50:00",
                      "timeZone": "Africa/Algiers"
                    },
                    "arrival": {
                      "airportCode": "CDG",
                      "airportName": "Paris Charles de Gaulle Airport",
                      "cityName": "Paris",
                      "countryCode": "FR",
                      "terminal": "1",
                      "datetime": "2026-12-15T13:14:00",
                      "timeZone": "Europe/Paris"
                    },
                    "flight": {
                      "carrierCode": "ZZ",
                      "carrierName": "Duffel Airways",
                      "carrierLogoUrl": "https://assets.duffel.com/img/airlines/for-light-background/full-color-logo/ZZ.svg",
                      "flightNumber": "7426",
                      "aircraftCode": null,
                      "aircraftName": null,
                      "operatingCarrier": "ZZ",
                      "operatingCarrierName": "Duffel Airways"
                    },
                    "duration": "PT2H24M",
                    "cabin": "ECONOMY",
                    "bookingClass": null,
                    "fareBasis": "Y20LGTN2",
                    "brandedFare": "BASIC",
                    "brandedFareLabel": "Basic"
                  }
                ],
                "conditions": {
                  "changeBeforeDeparture": { "allowed": false },
                  "refundBeforeDeparture": { "allowed": false }
                }
              }
            ],
            "travelerPricing": [
              {
                "travelerId": "pas_0000BC0a5iH5UE0bmBqWkl",
                "travelerType": "ADULT",
                "fareOption": "STANDARD",
                "price": {
                  "supplierCurrency": "USD",
                  "agencyCurrency": "DZD",
                  "base": 16385.0,
                  "total": 21632.82,
                  "b2bPrice": 21611.2
                },
                "fareDetailsBySegment": [
                  {
                    "segmentId": "seg_0000BC0a5iUChSV6Qrz0Co",
                    "cabin": "ECONOMY",
                    "fareBasis": "Y20LGTN2",
                    "brandedFare": "BASIC",
                    "brandedFareLabel": "Basic",
                    "bookingClass": null,
                    "includedCheckedBags": { "quantity": 1 },
                    "includedCabinBags": { "quantity": 1 },
                    "amenities": [
                      { "description": "WIFI", "isChargeable": true, "amenityType": "WIFI", "amenityProvider": { "name": "BrandedFare" } }
                    ]
                  }
                ]
              }
            ],
            "conditions": {
              "refundBeforeDeparture": { "allowed": false },
              "changeBeforeDeparture": { "allowed": false }
            },
            "fareRules": {
              "exchange": { "allowed": false },
              "refund": { "allowed": false },
              "revalidation": { "allowed": false },
              "supplierCurrency": "USD",
              "agencyCurrency": "DZD"
            },
            "validatingAirline": [{ "code": "ZZ", "name": "Duffel Airways" }]
          }
        ]
      },
      "exchangeRate": 250.0
    }
  ],
  "summary": { "totalSuppliers": 2, "successfulSuppliers": 2, "failedSuppliers": 0, "totalOffers": 158 }
}
~~~

Names: next to the keys you feed back into the next calls the response adds **snake_case twins** with the same value. At the top level: `request_id`, `processing_time`, `search_type`. In each supplier entry: `search_id`, `support_hold`, `support_void`, `void_deadline`, `exchange_rate`, `filters_applied`, and `meta.supplier_id`, `meta.supplier_name`, `meta.processing_time`. In each offer: `offer_id` (next to `id`), `support_hold`, `support_void`, `void_deadline`, `pricing.base_price`, `pricing.taxes_and_fees`, `pricing.agency_commission`, `pricing.final_price`, `pricing.b2b_price`, and `passenger_id` and `passenger_type` in each `travelerPricing` entry. Add `?naming=snake` to get every key in snake_case. See [Conventions and naming](https://fx-port.com/docs/conventions.md).

### Top-level response fields

| Field | Type | Meaning |
|---|---|---|
| `environment` | string | `sandbox` or `live`, from your key. |
| `success` | boolean | `true` on every `200`, even if some suppliers failed: read `suppliers[n].success` and `failures`. |
| `requestId` | string | The id of this search, for support. |
| `searchType` | string | `ONE_WAY`, `ROUND_TRIP`, `OPEN_JAW` or `MULTI_CITY`, derived from your route. |
| `flexible` | boolean | Whether the date matrix was requested. |
| `flexibleOnly` | boolean | Only present (and `true`) for [flexible search](https://fx-port.com/docs/flights/flexible-search.md). |
| `processingTime` | number | Seconds spent by the whole search. |
| `suppliers` | array | One entry per supplier that answered; see below. |
| `summary` | object | `totalSuppliers` and `successfulSuppliers` (both count the suppliers that **answered**), `failedSuppliers` (the ones listed in `failures`) and `totalOffers`. |
| `failures` | array | Only when a supplier failed: `{ supplierId, error, cause }` (twin: `supplier_id`). `cause` is a stable code: `supplier_timeout`, `supplier_api_error`, `supplier_auth_error`, `supplier_error`, `supplier_temporarily_unavailable` (the supplier is skipped for a short while), `supplier_busy` (FX-Port's capacity towards that supplier is full for a moment: the supplier was not called, retry in a few seconds) or `service_overloaded` (FX-Port was too busy to prepare that supplier's results: retry in a few seconds). `error` is a fixed sentence about the cause, never the supplier's own text. |
| `warnings` | array | Only when the request used a legacy name, an ignored field or a value that was adjusted. See [Conventions and naming](https://fx-port.com/docs/conventions.md). |

### `suppliers[n]` fields

| Field | Type | Meaning |
|---|---|---|
| `success` | boolean | Whether this supplier returned usable results. |
| `searchId` | string | **Save this.** `fs_<uuid>` (Amadeus Algeria) or `fs_dfx_<uuid>` (Duffel). Required as `search_id` by [price_flight](https://fx-port.com/docs/flights/price.md) and [book_flight](https://fx-port.com/docs/flights/book.md). It is valid for 24 hours, but a supplier withdraws offers earlier: price soon after you show an offer. |
| `supportHold`, `supportVoid` | boolean | What the supplier can do in general; re-read them on the priced offer before you choose between hold and issue. |
| `voidDeadline` | string or null | ISO 8601 UTC: the end of the same-day void window (midnight in the supplier's time zone, `Africa/Algiers` for Amadeus Algeria). `null` when the supplier cannot void. |
| `filtersApplied` | object | The filters you asked for, echoed (see above). |
| `warnings` | array | Notes of the supplier; empty in every sample. |
| `meta` | object | `supplierId`, `supplierName`, `technologyType` (`amadeus_aqc`, `duffel`), `processingTime`, `supplierCurrency`, `agencyCurrency`. |
| `results` | object | `totalOffers`, `supplierCurrency`, `agencyCurrency`, `offers`. |
| `exchangeRate` | number | Only when the supplier's currency is not your agency's (Duffel, USD to DZD: 250.0). |
| `flexPrices` | object | Only with `flexible: true` from a supplier that supports it. |

### `offers[n]` fields

| Field | Type | Meaning |
|---|---|---|
| `id` | string | **Save this.** `fx_offer_` and 8 hex characters. Required as `offer_id` by price and book. |
| `type` | string | Always `flight_offer`. |
| `supplier` | object | `id`, `name` and `originalId` (the supplier's own id of the offer: for support only). |
| `bookableSeats` | integer or null | Seats left at this fare, when the supplier says (Amadeus: up to 9). |
| `supportHold`, `supportVoid`, `voidDeadline` | | As on the supplier, for this offer. |
| `instantTicketingRequired` | boolean | `true`: the fare cannot be held; book it with `booking_type: "issue"`. |
| `lastTicketingDate`, `lastTicketingDateTime` | string | Duffel only: when the offer stops being sellable. Typically 15 to 60 minutes after the search. |
| `pricing` | object | Every field is explained in [Flight pricing](https://fx-port.com/docs/flights/pricing.md). Amadeus Algeria adds `acd`. |
| `itinerary` | array | One entry per direction; see below. |
| `travelerPricing` | array | One entry per priced traveler; see below. |
| `conditions` | object | `refundBeforeDeparture.allowed` and `changeBeforeDeparture.allowed`: indicative, not a promise. |
| `fareRules` | object | `exchange`, `refund`, `revalidation` (each `{ allowed }`) and the two currencies. |
| `validatingAirline` | array | `{ code, name }` of the airline whose rules and ticket stock govern the fare. |
| `additionalServices` | array | Amadeus only: paid extras the supplier lists. |
| `requiresACD` | boolean | Amadeus Algeria only, present only when `true`: after booking, passports must be uploaded. See [ACD passport uploads](https://fx-port.com/docs/flights/acd-documents.md). |
| `filtersApplied`, `warnings` | | As on the supplier. |

### `itinerary[n]` and `segments[n]` fields

| Field | Type | Meaning |
|---|---|---|
| `direction` | string | `outbound`, `return`, and `segment_3`, `segment_4` ... for the third and later legs of a multi-city search. |
| `duration` | string | ISO 8601 duration of the direction, for example `PT2H24M`. |
| `stops` | integer | Number of stops (`0` is non-stop). |
| `segments[n].id` | string | Id of the segment; `fareDetailsBySegment[].segmentId` points to it. |
| `segments[n].departure`, `.arrival` | object | `airportCode`, `airportName`, `cityName`, `countryCode`, `terminal` (can be null), `datetime` (local time, no zone), `timeZone` (IANA). |
| `segments[n].flight` | object | `carrierCode`, `carrierName`, `carrierLogoUrl`, `flightNumber` (without the carrier code: `7426`), `aircraftCode`, `aircraftName` (can be null), `operatingCarrier`, `operatingCarrierName`. Different carriers mean a codeshare. |
| `segments[n].duration` | string | ISO 8601 duration of the flight. |
| `segments[n].cabin` | string | `ECONOMY`, `PREMIUM_ECONOMY`, `BUSINESS` or `FIRST` for this segment; it can differ from the cabin you asked for. |
| `segments[n].bookingClass` | string or null | Booking class letter (Amadeus: `Q`, `O`; Duffel: null). |
| `segments[n].fareBasis`, `.brandedFare`, `.brandedFareLabel` | string | Fare basis code, fare family code and its display label. |
| `conditions` | object | Refund and change rules of this direction. |

### `travelerPricing[n]` fields

| Field | Type | Meaning |
|---|---|---|
| `travelerId` | string | `"1"`, `"2"` ... (Amadeus) or `pas_...` (Duffel). |
| `travelerType` | string | `ADULT`, `SENIOR`, `YOUNG`, `CHILD`, `SEATED_INFANT` or `HELD_INFANT` (a lap infant). |
| `fareOption` | string | The fare option, for example `STANDARD`. |
| `price` | object | The traveler's share: `supplierCurrency`, `agencyCurrency`, `base`, `total`, `b2bPrice`. |
| `fareDetailsBySegment[n]` | array | `segmentId`, `cabin`, `fareBasis`, `brandedFare`, `brandedFareLabel`, `bookingClass`, `includedCheckedBags.quantity`, `includedCabinBags.quantity` and `amenities[]` (`description`, `isChargeable`, `amenityType`, `amenityProvider`). |

The entries are grouped **by type** (adults, then seniors, children ...), not in the order you would list the travelers: a request has no passenger order. Each traveler's `price.total` adds up to the offer's `pricing.finalPrice`.

## Save these values

| Value | Where | Used by |
|---|---|---|
| `searchId` | `suppliers[n].searchId` | `price_flight.search_id`, then `book_flight.search_id` |
| offer `id` | `suppliers[n].results.offers[m].id` | `price_flight.offer_id`, then `book_flight.offer_id` |
| `supportHold` | supplier and priced offer | Choose hold only when it is still `true` after pricing |
| `requiresACD` | offer | Plan the [ACD passport upload](https://fx-port.com/docs/flights/acd-documents.md) |

## Streaming

Add `?stream=true` (or send `Accept: text/event-stream`) to receive the same search as server-sent events: a first event after about half a second, the first offers after about two seconds, and the rest as each supplier answers. See [Streaming search results](https://fx-port.com/docs/streaming.md) for the events, the merge rules, the client code in nine languages and the Cloudflare notes. Keep at most 4 streams open at the same time for one API key.

## Errors

The error body is described in [Errors](https://fx-port.com/docs/errors.md). Branch on `errors[0].code`.

| Status | Code | When | What to do |
|---|---|---|---|
| `400` | `invalid_json`, `invalid_body` | The body is not JSON, or not a JSON object, or is nested more than 24 levels deep, or holds a text longer than 256 characters. | Send one JSON object with `Content-Type: application/json`. |
| `400` | `missing_search_params` | Neither a `url`, nor `segments`, nor the three fields `origin`, `destination` and `departure_date`. The body has `example_params` and `example_url`. | Send a route. |
| `400` | `missing_field` | A segment lacks `origin`, `destination` or `departure_date`. `errors[0].field` is the path, for example `segments[1].departure_date`. | Send the field it names. |
| `400` | `invalid_airport_code` | An IATA code that is not three ASCII letters (up to five codes in `"ALG,ORN"`), or an origin equal to the destination. | Use three letters; `PAR` and `LON` city codes are valid. |
| `400` | `invalid_date`, `date_in_past`, `date_too_far`, `return_before_departure` | A date that is not a real `YYYY-MM-DD`, is earlier than yesterday, is more than 365 days ahead, or a return (or a later segment) before the departure. | Fix the date. |
| `400` | `invalid_cabin_class` | `cabin_class` is not one of the five values, once case, spaces, dashes and underscores are ignored. `errors[0].details.allowed` lists them. | Send one of the five values. |
| `400` | `invalid_passenger_count`, `invalid_passenger_age` | A count that is not a whole number from 0 to 99, or an age that is not a whole number from 0 to 17. Numeric text and integral floats (`"2"`, `2.0`) are **read** with a `coerced_value` warning; words, fractions, booleans, negatives and bigger numbers are refused. | Fix the number. |
| `400` | `traveler_rule_violation` | The mix breaks a rule: more than 9 seated passengers, more lap infants than adults and seniors, or more seated infants under 2 than adults and seniors. This answer keeps its older flat body: `error: "TRAVELER_RULE_VIOLATION"` and `violations[]` with one entry per broken rule. | See [Flight passengers & ages](https://fx-port.com/docs/flights/passengers.md#capacity-constraints). |
| `400` | `invalid_supplier_selection` | An unknown or disabled supplier, or a supplier that cannot do flexible search. `availableSuppliers` lists the usable ids. | Remove the selector or use an available id. |
| `400` | `supplier_passenger_combination_unsupported` | No selected supplier can serve the passenger mix (for example one adult with a seated and a lap infant, with only Duffel available). | Change the mix, or select another supplier. |
| `400` | `invalid_airline_filter`, `invalid_field_type`, `invalid_segments`, `invalid_country_code`, `invalid_currency`, `invalid_url` | A filter or a field has the wrong type or shape; `errors[0].field` names it. | Fix the field. |
| `403` | `acd_exemption_invalid`, `acd_exemption_currency` | `acd_exempt_company_id` is not usable by this agency. | Remove it, or use a company linked to your agency. |
| `413` | `request_too_large` | The body is larger than 64 KB, or holds more than 5,000 values. | Send a smaller body. |
| `422` | `no_supplier_supports_flexible_search` | `flexible_only` (or `POST /flights/flexible_search`) on a route that no flexible-capable supplier serves. | Use `get_flights` for fixed dates. |
| `503` | `all_suppliers_failed` | No supplier returned results. `supplierErrors[]` lists `{ supplierId, cause, message }` with `cause` one of `supplier_timeout`, `supplier_api_error`, `supplier_auth_error`, `supplier_error`, `supplier_temporarily_unavailable`, `supplier_busy`, `service_overloaded` and a fixed sentence: the supplier's own text is never returned. | A well-formed airport code that does not exist, a date the supplier has no schedule for, or a supplier outage all end here. Wait, then retry with backoff, or search another route. |
| `503` | `supplier_busy` | **Every** supplier refused the search because FX-Port's capacity towards it is full for a moment. The suppliers were not called, nothing was booked and nothing was charged. The answer carries `Retry-After` (seconds). | Wait for `Retry-After`, then send the same request again: it is safe. |
| `503` | `service_overloaded` | FX-Port was busy preparing other large search answers and no worker became free within a few seconds, so this search could not be completed. Nothing was booked and nothing was charged. The answer carries `Retry-After` (seconds). | Retry with backoff: wait for `Retry-After`, then send the same request again. |

Invalid input is refused **before any supplier is called**, so it never costs a supplier request and never counts against a supplier. A supplier that keeps failing is skipped for a short while and listed in `failures[]` with the cause `supplier_temporarily_unavailable`; the others still answer. A supplier whose capacity is full for a moment is listed the same way with the cause `supplier_busy` while the others answer; only when every supplier is busy is the whole search refused with `503 supplier_busy` and `Retry-After`. `service_overloaded` works like `supplier_busy` when FX-Port itself was too busy to prepare a supplier's results (one supplier: `failures[]`; all of them: `503`). Neither makes FX-Port skip the supplier for later searches.


> [!WARNING]
> **Multi-city searches** (three or more segments) are not supported by Amadeus Algeria: it fails and is listed in `failures`, while Duffel answers. Add `"supplier_id": "duffel_1"` to a multi-city search that touches Algeria to avoid the failing call.


## Test it in the sandbox

| You want | Send |
|---|---|
| Both suppliers | `ALG` to `CDG`, one adult, a date 30 days ahead |
| A fare you can hold | A route that touches Algeria; read `supportHold` on the offer and the supplier `amadeus_aqc_dz_1` |
| An instant-only fare | `"supplier_id": "duffel_1"` |
| An offer that needs ACD passports | `CDG` to `ALG` with `"supplier_id": "amadeus_aqc_dz_1"`: every offer has `pricing.acd.applied: true` |
| Round trip, open-jaw, multi-city | The examples of [Basic routes](https://fx-port.com/docs/flights/search/basic-routes.md) and [Open-jaw and multi-city](https://fx-port.com/docs/flights/search/complex-itineraries.md) |
| An error | `"cabin_class": "luxury"` returns `400 invalid_cabin_class` before any supplier is called |
| A value that is read, with a warning | `"passengers": { "adults": "2" }` is searched as 2 adults and the answer carries a `coerced_value` warning; `"adults": "two"` is `400 invalid_passenger_count` |

## Related

- [Price a flight offer](https://fx-port.com/docs/flights/price.md): the required next step.
- [Streaming search results](https://fx-port.com/docs/streaming.md) and [Flexible-date search](https://fx-port.com/docs/flights/flexible-search.md).
- [API reference: Search flights](https://fx-port.com/docs/api/get-flights.md) and [the Flight offer object](https://fx-port.com/docs/api/objects/flight-offer.md).
