# Book a flight

> Create a booking, either an instant issue or a hold reservation. Requires Read+Write permission.

Endpoint: `POST /api/v1/flights/book_flight`

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

Create a booking. `booking_type` is `"issue"` (the default) for instant ticketing, or `"hold"` for an unpaid reservation when the offer supports it.

**Permission:** Read+Write. **Rate group:** booking (1,200 requests per minute per key). In the sandbox nothing is charged: the response says `balanceDebited: false`.


> [!NOTE]
> **Price first.** The offer must have been priced with [`price_flight`](https://fx-port.com/docs/flights/price.md) within the last 12 hours, with the same `search_id` and `offer_id`. An offer that was never priced is refused with `404 offer_not_priced`.

> [!WARNING]
> **Hold depends on the priced offer.** Send `"booking_type": "hold"` only when `supportHold` is `true` **after** `price_flight`. Otherwise the answer is `400 hold_not_supported`. A hold is not ticketed and the airline can release it before the displayed deadline.

> [!TIP]
> Send `"send_email": false` while you test: otherwise a confirmation e-mail goes to the address you typed.


## Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `search_id` | string | Yes | The `searchId` of the supplier entry the offer came from (the same search that was priced). |
| `offer_id` | string | Yes | The `id` of the priced offer. |
| `booking_type` | string | No | `issue` (default) or `hold`, in any letter case. Any other value is rejected with `400 invalid_booking_type`. |
| `passengers` | array | Yes | One object per traveler, lap infants included: see below. At most 18. |
| `contact_details` | object | Yes | `{ "email", "phone_number" }`: **both** are required. Use an international phone number (`+213...`): Duffel validates the format. FX-Port accepts digits with spaces, dots, dashes, slashes and brackets, one leading `+` (also `(+213)`), 5 to 24 digits and 32 characters at most. A dial code alone (`+213`) is `400 invalid_phone_number`. |
| `send_email` | boolean | No | `false` suppresses the confirmation e-mail. Default `true`. |
| `commission_override` | object | No | Replaces your agency commission for this booking: `{ "type": "percentage" or "fixed", "value": number >= 0 }` (a numeric text such as `"10"` is read as a number with a `coerced_value` warning). `percentage` is a share of `b2bPrice`; `fixed` is `value` times the number of travelers. `pricing.agencyCommission` and `pricing.finalPrice` change, `b2bPrice` does not, in the response **and** in the stored booking. Example: 10 % on a `b2bPrice` of 28679.0 gave a commission of 2867.9 and a `finalPrice` of 31546.9. |
| `agent_id` | string | No | Your own id of the agent who books; stored on the booking. |

### Passenger object

| Field | Type | Required | Description |
|---|---|---|---|
| `type` | string | Yes | `adult`, `senior`, `young_adult`, `child`, `seated_infant` or `infant` (a lap infant), in any letter case. More spellings are accepted: see [Flight passengers & ages](https://fx-port.com/docs/flights/passengers.md#booking-the-passenger-object). An unknown value is `400 invalid_passenger_type`. |
| `gender` | string | Yes | `MALE` or `FEMALE` (`M` and `F` too); `X`, `U` or `UNSPECIFIED` for a passport marked X. |
| `first_name` | string | Yes | Given name as in the passport. Spaces and line breaks around it are removed before the checks. |
| `last_name` | string | Yes | Family name as in the passport. |
| `date_of_birth` | string | Yes | `YYYY-MM-DD`; tomorrow is still accepted (time-zone slack), later is `400 invalid_date`. |
| `identity_documents` | array | No | The passport. Send it for every traveler: airlines can require it. |
| `id` | string | No | An id of yours for the passenger. |

`passengers` must match the passengers of the search and the offer's `travelerPricing` (count and type). FX-Port does not compare them for you: a mismatch is refused by the supplier. Names: any letter of any alphabet is accepted by FX-Port, but airlines need **Latin letters**; see the warning in [Flight passengers & ages](https://fx-port.com/docs/flights/passengers.md).

### Identity document object

| Field | Type | Required | Description |
|---|---|---|---|
| `type` | string | No | `passport` (default). |
| `number` | string | No | The document number: 1 to 40 letters, digits, spaces and `- . / _`. |
| `issuing_country` | string | No | ISO 3166-1 alpha-2 (`DZ`). A website value such as `GB-SCT` or `USPR` (2 to 4 letters, optionally `-` and 1 to 3 letters or digits) is passed on in upper case: see [Flight passengers & ages](https://fx-port.com/docs/flights/passengers.md#identity-document-object). |
| `expiry_date` | string | No | `YYYY-MM-DD`. |
| `nationality` | string | No | Accepted and ignored: suppliers use the issuing country. |

### Legacy names

Every field below has a previous spelling that still works and is answered with a `deprecated_field` warning. Send the name in the second column. If a body holds a previous spelling **and** its replacement with different values, the replacement wins and an `ignored_field` warning names the dropped one.

| Legacy name | Use instead |
|---|---|
| `searchId`, `offerId` | `search_id`, `offer_id` |
| `bookingType`, `sendEmail`, `commissionOverride`, `agentId` | `booking_type`, `send_email`, `commission_override`, `agent_id` |
| `contact`, `contacts`, `contactDetails` | `contact_details` |
| `contact_details.phone`, `.phoneNumber`, `.mobile` | `contact_details.phone_number` |
| `contact_details.emailAddress` | `contact_details.email` |
| `passengers[].firstName`, `.givenName`, `.given_name` | `passengers[].first_name` |
| `passengers[].lastName`, `.familyName`, `.family_name`, `.surname` | `passengers[].last_name` |
| `passengers[].dateOfBirth`, `.bornOn`, `.born_on`, `.dob` | `passengers[].date_of_birth` |
| `passengers[].identityDocuments`, `.documents` | `passengers[].identity_documents` |
| `passengers[].passengerType` | `passengers[].type` |
| `identity_documents[].issuingCountry`, `.issuing_country_code`, `.issuingCountryCode` | `identity_documents[].issuing_country` |
| `identity_documents[].expiryDate`, `.expires_on` | `identity_documents[].expiry_date` |
| `identity_documents[].documentNumber`, `.unique_identifier` | `identity_documents[].number` |

`phone` and `phoneNumber` at the **top level** of the body are not a spelling of `contact_details`: the contact is an object.

## Request examples


**Instant issue**

**Request**

```bash
curl https://api.fx-port.com/api/v1/flights/book_flight \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "search_id": "fs_dfx_88eb64c9-6c36-47e6-bdaa-9ad3c1377ac4",
  "offer_id": "fx_offer_b7a95439",
  "booking_type": "issue",
  "send_email": false,
  "passengers": [
    {
      "type": "adult",
      "gender": "MALE",
      "first_name": "James",
      "last_name": "Smith",
      "date_of_birth": "1988-06-15",
      "identity_documents": [
        {
          "type": "passport",
          "number": "A12345678",
          "issuing_country": "DZ",
          "expiry_date": "2031-04-18",
          "nationality": "DZ"
        }
      ]
    }
  ],
  "contact_details": {
    "email": "traveler@example.com",
    "phone_number": "+15555550123"
  }
}'
```

**Hold**

**Request**

```bash
curl https://api.fx-port.com/api/v1/flights/book_flight \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "search_id": "fs_85ac5f12-7456-4edf-a2fb-1d51239d278b",
  "offer_id": "fx_offer_9de60e2a",
  "booking_type": "hold",
  "send_email": false,
  "passengers": [
    {
      "type": "adult",
      "gender": "FEMALE",
      "first_name": "Maria",
      "last_name": "Garcia",
      "date_of_birth": "1991-03-22",
      "identity_documents": [
        {
          "type": "passport",
          "number": "B87654321",
          "issuing_country": "DZ",
          "expiry_date": "2030-11-15"
        }
      ]
    }
  ],
  "contact_details": {
    "email": "traveler@example.com",
    "phone_number": "+15555550123"
  }
}'
```

**2 adults and a child**

**Request**

```bash
curl https://api.fx-port.com/api/v1/flights/book_flight \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "search_id": "fs_dfx_88eb64c9-6c36-47e6-bdaa-9ad3c1377ac4",
  "offer_id": "fx_offer_b7a95439",
  "booking_type": "issue",
  "send_email": false,
  "passengers": [
    {
      "type": "adult",
      "gender": "MALE",
      "first_name": "Omar",
      "last_name": "Nguyen",
      "date_of_birth": "1985-04-10",
      "identity_documents": [
        {
          "type": "passport",
          "number": "P11223344",
          "issuing_country": "DZ",
          "expiry_date": "2031-04-18"
        }
      ]
    },
    {
      "type": "adult",
      "gender": "FEMALE",
      "first_name": "Priya",
      "last_name": "Nguyen",
      "date_of_birth": "1987-08-20",
      "identity_documents": [
        {
          "type": "passport",
          "number": "P44332211",
          "issuing_country": "DZ",
          "expiry_date": "2030-12-01"
        }
      ]
    },
    {
      "type": "child",
      "gender": "MALE",
      "first_name": "Leo",
      "last_name": "Nguyen",
      "date_of_birth": "2018-05-14",
      "identity_documents": [
        {
          "type": "passport",
          "number": "P99887766",
          "issuing_country": "DZ",
          "expiry_date": "2029-05-14"
        }
      ]
    }
  ],
  "contact_details": {
    "email": "traveler@example.com",
    "phone_number": "+15555550123"
  }
}'
```


## Response example: instant issue

A Duffel instant issue: the supplier prices in USD and the agency settles in DZD, so `exchangeRate` is at the top level. The itinerary, the traveler pricing and the owner are shortened here; they have the shape described in [Get a flight booking](https://fx-port.com/docs/flights/get-booking.md).

~~~json
{
  "environment": "sandbox",
  "success": true,
  "requestId": "7b166b31",
  "searchId": "fs_dfx_88eb64c9-6c36-47e6-bdaa-9ad3c1377ac4",
  "offerId": "fx_offer_b7a95439",
  "processingTime": 0.408,
  "exchangeRate": 250.0,
  "booking": {
    "reference": "ord_0000BC0UuqWJKkrDO331Xc",
    "status": "confirmed",
    "pnr": "BOKT52",
    "type": "instant",
    "createdAt": "2026-11-01T14:24:41.453265Z",
    "cancelledAt": null,
    "content": "managed",
    "syncedAt": "2026-11-01T14:24:41Z",
    "availableActions": ["cancel", "change"],
    "voidableUntil": null,
    "cancellation": null
  },
  "supplier": { "id": "duffel_1", "technology": "duffel", "name": "Duffel", "offerId": "off_034q4rP6NMvbRSHMkUPbTE" },
  "agency": { "currency": "DZD" },
  "pricing": {
    "supplierCurrency": "USD",
    "agencyCurrency": "DZD",
    "basePrice": 16342.51,
    "taxesAndFees": 5216.69,
    "agencyCommission": 21.56,
    "finalPrice": 21580.76,
    "b2bPrice": 21559.2,
    "additionalServices": []
  },
  "payment": { "status": "paid", "paidAt": "2026-11-01T14:24:41Z", "paymentRequiredBy": null, "priceGuaranteeExpiresAt": null, "awaitingPayment": false },
  "documents": [{ "type": "electronic_ticket", "uniqueIdentifier": "1", "passengerIds": ["pas_0000BC0UogJGYlTqCNHhye"] }],
  "bookingReferences": [
    { "pnr": "BOKT52", "carrier": { "iataCode": "ZZ", "name": "Duffel Airways", "logoUrl": "https://assets.duffel.com/img/airlines/for-light-background/full-color-logo/ZZ.svg", "logoLockupUrl": null } }
  ],
  "passengers": [
    {
      "id": "pas_0000BC0UogJGYlTqCNHhye",
      "type": "ADULT",
      "title": "mr",
      "givenName": "Audit",
      "familyName": "Tester",
      "gender": "m",
      "bornOn": "1990-01-15",
      "email": "traveler@example.com",
      "phoneNumber": "+15555550123",
      "infantPassengerId": null,
      "loyaltyProgrammeAccounts": [],
      "ticketNumber": "1"
    }
  ],
  "travelerPricing": ["..."],
  "itinerary": ["..."],
  "conditions": { "refundBeforeDeparture": { "allowed": false }, "changeBeforeDeparture": { "allowed": true } },
  "owner": { "id": "arl_00009VME7D6ivUu8dn35WK", "iataCode": "ZZ", "name": "Duffel Airways", "conditionsOfCarriageUrl": "https://duffelairways.com/dummy-url/conditions-of-carriage", "...": "..." },
  "services": [],
  "meta": { "supportHold": false, "supportVoid": false, "voidDeadline": null, "voidWindowEndsAt": null, "airlineInitiatedChanges": [], "changes": [], "users": [], "orderMetadata": null },
  "postProcessing": { "bookingDbId": "8f1e6b52-3c1d-4d62-9d8a-0c6a1f6e5a11", "bucketLink": null, "emailSent": false },
  "balanceDebited": false,
  "transactionCreated": false,
  "payoutCreated": false
}
~~~

## Response example: hold

The same request with `"booking_type": "hold"` on an Amadeus Algeria offer. The agency and the supplier price in DZD, so there is no `exchangeRate`. The order is unpaid: `booking.status` is `pending` and `payment.awaitingPayment` is `true`. Real response of 2026-11-01, trimmed, with the agency id and the contact data replaced by fake values (the shortened parts are marked):

~~~json
{
  "environment": "sandbox",
  "success": true,
  "requestId": "66e4b1a8",
  "searchId": "fs_85ac5f12-7456-4edf-a2fb-1d51239d278b",
  "offerId": "fx_offer_9de60e2a",
  "processingTime": 2.624,
  "booking": {
    "reference": "eJzTd9ePNA9wdA0FAAs8AmE",
    "status": "pending",
    "pnr": "Y7PAEU",
    "type": "hold",
    "createdAt": "2026-11-01T15:26:00.000",
    "cancelledAt": null,
    "content": "managed",
    "syncedAt": "2026-11-01T15:26:00.000",
    "availableActions": ["confirm", "cancel"],
    "voidableUntil": null,
    "cancellation": null
  },
  "supplier": { "id": "amadeus_aqc_dz_1", "technology": "amadeus_aqc", "name": "Amadeus AQC", "offerId": "3" },
  "agency": { "currency": "DZD" },
  "pricing": {
    "supplierCurrency": "DZD",
    "agencyCurrency": "DZD",
    "basePrice": 3160.0,
    "taxesAndFees": 1765.44,
    "agencyCommission": 4.93,
    "finalPrice": 4930.37,
    "b2bPrice": 4925.44,
    "additionalServices": [],
    "acd": { "applied": false }
  },
  "payment": { "status": "awaiting_payment", "paidAt": null, "paymentRequiredBy": null, "priceGuaranteeExpiresAt": null, "awaitingPayment": true },
  "documents": [],
  "bookingReferences": [{ "pnr": "Y7PAEU", "carrier": { "iataCode": "AH", "name": "Air Algerie", "logoUrl": "https://assets.duffel.com/img/airlines/for-light-background/full-color-logo/AH.svg", "logoLockupUrl": "https://assets.duffel.com/img/airlines/for-light-background/full-color-lockup/AH.svg" } }],
  "passengers": [
    {
      "id": "1",
      "type": "ADULT",
      "title": "mr",
      "givenName": "JAMES",
      "familyName": "SMITH",
      "gender": "male",
      "bornOn": "1988-06-15",
      "email": "traveler@example.com",
      "phoneNumber": "15555550123",
      "infantPassengerId": null,
      "loyaltyProgrammeAccounts": [],
      "ticketNumber": null
    }
  ],
  "travelerPricing": ["..."],
  "itinerary": ["..."],
  "conditions": { "refundBeforeDeparture": { "allowed": true }, "changeBeforeDeparture": { "allowed": true } },
  "owner": { "id": null, "iataCode": "AH", "name": "Air Algerie", "...": "..." },
  "services": [],
  "meta": { "supportHold": true, "supportVoid": true, "voidDeadline": "2026-11-01T23:00:00Z", "voidWindowEndsAt": null, "airlineInitiatedChanges": [], "changes": [], "users": [], "orderMetadata": null },
  "fareRules": { "refund": { "allowed": true }, "exchange": { "allowed": true }, "agencyCurrency": "DZD" },
  "postProcessing": { "bookingDbId": "d6a05380-ccab-4a2f-bc13-f65ae9fdad7e", "bucketLink": null, "emailSent": false },
  "balanceDebited": false,
  "transactionCreated": false,
  "payoutCreated": false
}
~~~

Keep `booking.reference` as the `order_id` of the next calls and continue with [Quote a held booking](https://fx-port.com/docs/flights/get-order-quote.md) and [Pay a held booking](https://fx-port.com/docs/flights/pay-order.md), or release the hold with [Cancel a held booking](https://fx-port.com/docs/flights/cancel-order.md).

## Key fields

| Field | Description |
|---|---|
| `booking.reference` | The supplier's **order id**. Use it as `order_id` in the later calls (twin: `booking.order_id`). In `cancel_order` the same key holds the PNR: read `orderId` there. |
| `booking.pnr` | The airline record locator. **Not unique across suppliers**: use `reference` as the key. |
| `booking.status` | `confirmed` for an instant issue, `pending` for an unpaid hold (later calls say `awaiting_payment`). |
| `booking.type` | `instant` or `hold`; `pay_order` turns a `hold` into `instant`. |
| `booking.availableActions` | What the supplier lets you do next: `confirm`, `cancel`, `change`. |
| `booking.voidableUntil` | The end of the same-day void window, in UTC: midnight in the supplier's time zone of the day the PNR was **paid** (not created). `null` for an unpaid hold or when voiding is not supported. |
| `booking.createdAt` | A timestamp whose format depends on the supplier: with `Z` (Duffel) or without a zone (Amadeus). |
| `supplier` | `id`, `technology`, `name` and `offerId` (the supplier's own offer id). `name` is `Amadeus AQC` here and `Amadeus Algeria` in a search: use `supplier.id`. |
| `payment` | `status` (`paid` or `awaiting_payment`), `paidAt`, `paymentRequiredBy`, `priceGuaranteeExpiresAt`, `awaitingPayment`. |
| `documents[n]` | The e-ticket: `uniqueIdentifier` is the ticket number (Amadeus `124-2446977396`), or a short opaque id in the Duffel sandbox (`"1"`). Treat it as opaque. Empty for a hold. |
| `passengers[n]` | The travelers as the supplier stored them. Values differ by supplier: `gender` is `m` (Duffel) or `male` (Amadeus), names are upper case on Amadeus. |
| `exchangeRate` | At the top level, when `supplierCurrency` and `agencyCurrency` differ; see [Flight pricing](https://fx-port.com/docs/flights/pricing.md). |
| `postProcessing.bookingDbId` | The FX-Port id of the booking: the `id` that [`get_booking`](https://fx-port.com/docs/flights/get-booking.md) returns. |
| `postProcessing.bucketLink` | The PDF link. `null` right after a booking: read it with `get_booking` or `get_ticket`. |
| `requiresACD` | Present and `true` only for an ACD booking: upload passports with [ACD passport uploads](https://fx-port.com/docs/flights/acd-documents.md). |
| `balanceDebited`, `transactionCreated`, `payoutCreated` | Sandbox only, always `false`: nothing moved. |

Next to these keys the response has the snake_case twins `request_id`, `search_id`, `offer_id`, `processing_time`, `exchange_rate`, `booking.order_id`, `pricing.base_price` ... `pricing.b2b_price`, `meta.support_hold`, `support_void`, `void_deadline`, `supplier.supplier_id`, `supplier_name`, `supplier_offer_id`, and in each passenger `first_name`, `last_name`, `date_of_birth`, `phone_number`, `passenger_id`, `passenger_type`.

## Getting the confirmation PDF

The PDF is generated after the booking, usually within 10 to 30 seconds. Poll [`get_booking`](https://fx-port.com/docs/flights/get-booking.md) until `booking.bucket_link` has a value, or call [`get_ticket`](https://fx-port.com/docs/flights/get-ticket.md) until `ticket_ready` is `true`.

## Behaviour to know

- **The checks run in this order**: the required fields, the booking lock of the offer, the search, the offer in that search, the priced offer, the ACD exemption, the hold capability, and (on live) the agency status and balance. Only then the supplier is called.
- **One booking per offer.** The offer is locked while it is booked and after it succeeded: booking it again is `409 duplicate_booking` (with `pnr`). A booking that fails releases the offer.
- **Duffel books one offer per search.** A second Duffel booking from the same `search_id` fails with `409 offer_request_already_booked`: search again.
- **A failed booking leaves a trace.** A booking that the supplier refuses is stored with a reference that starts with `FAILED-` and the status `failed`; it shows in your history. Ignore it.

## Errors

| Status | Code | When | What to do |
|---|---|---|---|
| `400` | `bad_request` | `search_id` or `offer_id` is missing: `Missing required parameters: search_id and offer_id are required`. | Send both. |
| `400` | `missing_field` | `passengers` or one of `contact_details.email` and `contact_details.phone_number` is missing; or a passenger lacks `gender`, `first_name`, `last_name` or `date_of_birth`. | Send what `errors[0].field` names (for example `passengers[0].first_name`). |
| `400` | `invalid_booking_type`, `invalid_passenger_type`, `invalid_gender`, `invalid_name`, `invalid_email`, `invalid_phone_number`, `invalid_date`, `invalid_country_code`, `invalid_commission_override`, `invalid_identifier`, `invalid_field_type` | A value is not accepted. `errors[0].field` names it. An unknown `booking_type` is refused here and never becomes an order. | Fix it. |
| `400` | `too_many_passengers` | More than 18 passengers. | Split the booking. |
| `400` | `hold_not_supported` | `hold` on an offer that cannot be held. | Book it with `issue`, or choose another offer. |
| `402` | `insufficient_balance` | Live only: the balance is lower than `b2bPrice`. `details` has `available`, `required`, `currency`. | Top up, then retry. |
| `403` | `read_write_permission_required` | A read-only key. | Use a read+write key. |
| `403` | `agency_not_active` | Live only: the agency is not approved. | Contact support. |
| `404` | `search_not_found` | The `search_id` is unknown, older than 24 hours, or belongs to another agency (the answer is the same). | Search again. |
| `404` | `offer_not_found` | The offer is not in that search. | Check the pair `search_id` and `offer_id`. |
| `404` | `offer_not_priced` | The offer was never priced (or the priced offer is older than 12 hours). | Call `price_flight`. |
| `409` | `duplicate_booking` | The offer was already booked; `pnr` is the existing booking. | Read that booking. |
| `409` | `offer_request_already_booked`, `offer_already_booked`, `order_already_exists` | The supplier says this offer or search was already booked (Duffel allows one booking per search). | Search again, or read the booking before you retry. |
| `409` | `booking_in_progress`, `booking_lock_failed` | Another request is booking this offer. | Wait, then read the booking before you retry. |
| `409` | `acd_exemption_invalid` | The ACD exemption of the search is not valid any more. | Search again without it. |
| `422` | `validation_type`, `born_on_does_not_match` and other supplier codes | The supplier refused a detail of the booking: the body has `error.code`, `error.type` (`validation_error`) and `error.details[]` (with `source.field`). | Fix the detail. |
| `503` | The supplier's own code, or `supplier_error` | The supplier itself failed: it answered with a status of 500 or more (`meta.supplierStatusCode`). | Read the booking before you retry: a booking can exist at the airline. |
| `503` | `balance_lock_timeout` | Live only, for a booking that is paid when it is made (`issue`): another operation of your agency holds the balance in the same currency for a moment. **Nothing was booked and nothing was charged**, and the offer is released. The message is always the fixed sentence `The agency balance is busy with another operation. Retry in a few seconds.`; `errors[0].details.currency` is the currency of your balance. No `Retry-After` header is sent. | Send the same request again after a few seconds. |
| `500` | `post_booking_failed` | The booking was created at the supplier but FX-Port could not finish storing it. The message is generic and the body carries the `requestId`. | Do **not** book again: look for the booking in your history or contact support with the `request_id`. |

A failed booking is always a JSON body: `409` when the offer or search was already booked, `503` when the supplier failed with a 5xx status (or your balance was busy, see above), `422` for every other refusal. You never receive an HTML `502` page from this endpoint. The refusal of the supplier is in `error.code`, `error.type` and `error.message`.

See [Errors](https://fx-port.com/docs/errors.md) for the body of an error.

## Related

- [Price a flight offer](https://fx-port.com/docs/flights/price.md), [Flight passengers & ages](https://fx-port.com/docs/flights/passengers.md) and [Booking flows](https://fx-port.com/docs/flights/booking-flows.md).
- [Get a flight booking](https://fx-port.com/docs/flights/get-booking.md) and [Get the ticket PDF](https://fx-port.com/docs/flights/get-ticket.md).
