# Passengers and ages

> Passenger types, age rules evaluated at departure, seated and lap infants, required age arrays and capacity limits.

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

> [!NOTE]
> These rules are **specific to flights**. Hotels and eSIMs have their own guest and traveler rules, in their own sections.

> [!CAUTION]
> **Ages are evaluated at the departure date of the first flight segment, not at the booking date.** A passenger who turns 12 on or before departure must be a young adult or an adult; a lap infant who turns 2 on or before departure must be a child and have a seat. The wrong type is rejected at check-in and can mean a rebooking fee.


## Passenger types

| Count in a search | `type` when booking | Type in the response | Age at departure | Seat | Ages list (search) |
|---|---|---|---|---|---|
| `adults` | `adult` | `ADULT` | 18 and over | own | none |
| `seniors` | `senior` | `SENIOR` | 60 and over | own | none |
| `young_adults` | `young_adult` | `YOUNG` | 12 to 17 | own | `young_adult_ages` |
| `children` | `child` | `CHILD` | 2 to 11 | own | `child_ages` |
| `seated_infants` | `seated_infant` | `SEATED_INFANT` | under 2 | **own seat** | `seated_infant_ages` |
| `infants` | `infant` | `HELD_INFANT` | under 2 | **lap** of an adult | `infant_ages` |

- A **lap infant** (`infants`) sits on an adult's lap: no seat and, in most markets, no separate fare. Send it when the child stays under 2 for the whole trip.
- A **seated infant** (`seated_infants`) has a seat and a ticket. Use it when the family wants a seat for a child under 2.
- A **child aged 2 to 11** (`children`) is a regular seated passenger. An adult can travel with several children, and with a child of 2 and a lap infant at the same time.

## Search: the `passengers` object

Counts, and an ages list for every type that has one. Every field is optional: **the default is one adult**, and a search with no passenger at all (`passengers` missing, `[]` or an empty text) is a search for one adult. Counts are JSON integers. `"2"`, `"2.0"` and `2.0` are read as 2 with a `coerced_value` warning, and an empty text or `null` means not sent. A negative count, a fraction, words, a boolean or a number above 99 is `400 invalid_passenger_count`. What limits a search in practice are the [capacity constraints](#capacity-constraints) below.

~~~json
{
  "adults": 2,
  "seniors": 0,
  "young_adults": 0,
  "children": 2,
  "child_ages": [10, 6],
  "infants": 1,
  "infant_ages": [1],
  "seated_infants": 1,
  "seated_infant_ages": [1]
}
~~~

| Ages list | Values | If you leave it out |
|---|---|---|
| `child_ages` | 2 to 11 | Every child is searched at age 11 |
| `young_adult_ages` | 12 to 17 | Every young adult at age 17 |
| `seated_infant_ages` | 0 to 1 | Every seated infant at age 1 |
| `infant_ages` | 0 to 1 | Every lap infant at age 0 (no warning: 0 is the right default) |

An ages list is a JSON array of integers. A text list (`"7,9"`), one bare number, and numeric text or an integral float for an age (`"7"`, `7.0`) are read with a `coerced_value` warning; a fractional age (`0.5`) counts as the years completed (0). An empty text or `null` means not sent. An age above 17, a negative one, or one that is not a number is `400 invalid_passenger_age`.

**Ages are not required, and they win over the counts.** The API does not reject a count that disagrees with its ages: it changes the search and says so in a `coerced_value` warning. A missing age of a child, a young adult or a seated infant is reported with a warning that names the age assumed; a missing lap-infant age is not. Measured on the sandbox:

| You send | The search prices |
|---|---|
| `"children": 2, "child_ages": [8]` | One adult and **one** child: the count is cut to the number of ages |
| `"children": 1, "child_ages": [1]` | One adult only: **no child**, because an age under 2 is not a child |
| `"children": 1, "child_ages": [15]` | A `YOUNG` traveler, not a child |
| `"infants": 1, "infant_ages": [3]` | A `CHILD`, not an infant |
| `"children": 2` and no ages | Two children aged 11 |

Always check `travelerPricing` of the first offer against the travelers you meant, and send the real ages.


> [!NOTE]
> **Passenger limits can depend on the supplier.** Some suppliers require one distinct adult or senior for every passenger under two, seated and lap infants together. In an automatic or multi-supplier search FX-Port skips a supplier that cannot serve the mix and returns the others. `400 supplier_passenger_combination_unsupported` is returned only when **no** selected supplier can serve it. The rule does not apply to children aged 2 to 11. See [the example](https://fx-port.com/docs/flights/search/passenger-and-cabin.md#one-adult-with-two-infants).


## Capacity constraints

| Rule | Limit | Violation code |
|---|---|---|
| Seated passengers (adults + seniors + young adults + children + seated infants) | At most **9** | `MAX_SEATS_EXCEEDED` |
| Lap infants (`infants`) | Not more than adults + seniors | `LAP_INFANT_LIMIT` |
| Seated infants under 2 (`seated_infants`) | Not more than adults + seniors | `SEATED_INFANT_LIMIT` |
| Count of one type | A whole number from 0 to 99 | `400 invalid_passenger_count` |
| An age | A whole number from 0 to 17 | `400 invalid_passenger_age` |
| A traveler to look after the others | A normal online booking has at least one adult or senior. Unaccompanied minors depend on the airline | none |

A broken rule is answered with `400`. The body keeps its long-standing flat shape (`error: "TRAVELER_RULE_VIOLATION"`, in `errors[0].code` as `traveler_rule_violation`), and `violations[]` lists every broken rule:

~~~json
{
  "environment": "sandbox",
  "success": false,
  "requestId": "2b773b87",
  "error": "TRAVELER_RULE_VIOLATION",
  "message": "Total seated passengers cannot exceed 9",
  "violations": [
    { "code": "MAX_SEATS_EXCEEDED", "message": "Total seated passengers cannot exceed 9", "max_seats": 9, "seated_passengers": 10 }
  ]
}
~~~

The violation codes are `MAX_SEATS_EXCEEDED` (`max_seats`, `seated_passengers`), `LAP_INFANT_LIMIT` (`lap_infants`, `adult_senior_count`) and `SEATED_INFANT_LIMIT` (`seated_infants`, `adult_senior_count`). With no supplier left, the body is `SUPPLIER_PASSENGER_COMBINATION_UNSUPPORTED` with a `limitation` object (`UNDER_TWO_HOST_RATIO`, `underTwoPassengers`, `adultSeniorHosts`).

## Common mistakes

~~~jsonc
// Children without ages: not an error, but the ages are guessed (11)
// and the price may be wrong.
{ "adults": 1, "children": 2 }

// Better: the real ages.
{ "adults": 1, "children": 2, "child_ages": [8, 5] }

// A child who turns 2 before departure sent as a lap infant:
// the airline rejects it at check-in, it needs its own seat.
{ "adults": 1, "infants": 1, "infant_ages": [1] }

// Better: a traveler who is 2 at departure is a child.
{ "adults": 1, "children": 1, "child_ages": [2] }

// More lap infants than adults: 400 traveler_rule_violation (LAP_INFANT_LIMIT).
{ "adults": 1, "infants": 2, "infant_ages": [0, 1] }

// 10 seated passengers: 400 traveler_rule_violation (MAX_SEATS_EXCEEDED).
{ "adults": 5, "children": 5, "child_ages": [4, 5, 6, 7, 8] }
// Split the trip into two bookings of at most 9 seats.
~~~

## Booking: the passenger object

`book_flight` takes a `passengers` array with **one object per traveler you priced** (lap infants included). The list must match the passengers of the search and the offer's `travelerPricing`: a mismatch is refused by the supplier.

| Field | Type | Required | Description |
|---|---|---|---|
| `type` | string | Yes | `adult`, `senior`, `young_adult`, `child`, `seated_infant` or `infant` (a lap infant). Case-insensitive. Also accepted: `young`, `young_adults`, `infant_with_seat`, `held_infant`, `infant_without_seat`. Any other value is rejected with `400 invalid_passenger_type`. |
| `gender` | string | Yes | `MALE` or `FEMALE` (`M` and `F` too), in any letter case. `X`, `U` or `UNSPECIFIED` for a passport marked X: FX-Port passes `UNSPECIFIED` on and the supplier decides whether it can issue the ticket. |
| `first_name` | string | Yes | Given name as in the passport. Spaces, line breaks and tabs around it (and zero-width marks left by copy-paste) are removed before the checks. It needs a letter, at most 64 characters and no control character: otherwise `400 invalid_name`. |
| `last_name` | string | Yes | Family name as in the passport, with the same rules. |
| `date_of_birth` | string | Yes | `YYYY-MM-DD`. A real date that is not in the future: tomorrow is still accepted (time-zone slack), later is `400 invalid_date`. The age is checked against the departure date by the supplier. |
| `identity_documents` | array | No | The passport (up to 5 documents). Send it for every traveler: airlines can require it, and international trips need it. |
| `id` | string | No | An id of yours for the passenger. |


> [!WARNING]
> **Names.** FX-Port keeps every letter of any alphabet, spaces, hyphens, commas, periods and apostrophes, and replaces digits and symbols by a space; it does **not** reject accents or non-Latin scripts. But **airlines and GDS systems need Latin letters**: write the name the way the machine-readable zone of the passport does (`Jose`, `Muller`, `Mohammed`, `Ivanov`), because a ticket with a different name is refused at the airport. Amadeus writes the names in upper case on the booking (`AUDIT TESTER`).


### Identity document object

| Field | Type | Required | Description |
|---|---|---|---|
| `type` | string | No | `passport` (the default). |
| `number` | string | No | The document number: 1 to 40 letters, digits, spaces and `- . / _`. Spaces around it are removed. A JSON number is read as text with a `coerced_value` warning. Symbols or markup are `400 invalid_field_type`. |
| `issuing_country` | string | No | The country that issued it: ISO 3166-1 alpha-2 (`DZ`, `EG`, `FR`). Any code of 2 to 4 letters, optionally followed by `-` and 1 to 3 letters or digits, is also accepted and passed on in upper case (`GB-SCT`, `USPR`, `GP-MG`: the values of FX-Port's own website); FX-Port does not convert it, so the supplier decides whether it knows the code. Anything else is `400 invalid_country_code`. |
| `expiry_date` | string | No | `YYYY-MM-DD`. |
| `nationality` | string | No | Accepted and **ignored**: suppliers receive the issuing country as the nationality. |

Legacy names for every field are listed in [Book a flight](https://fx-port.com/docs/flights/book.md#legacy-names).
