FX-Port API
Flights

POST /api/v1/flights/book_flight

Create a booking — instant issue or hold reservation. Requires Read+Write permission.

Create a booking. Set booking_type to "issue" for instant ticket issuance, or "hold" to create an unpaid reservation when the selected offer supports holds.

Permission: Read+Write

Call price_flight before booking to ensure the offer has been re-priced and the cache is warm. Booking without pricing may result in a stale price or offer rejection.

Hold is capability-based. Send "booking_type": "hold" only when the selected offer returns supportHold: true after price_flight. Otherwise use "issue" or choose another offer. A hold is not ticketed and can be released by the airline before the displayed payment deadline.

Request body

FieldTypeRequiredDescription
search_idstringYessearchId from the supplier entry in the search response
offer_idstringYesOffer ID from search results
booking_typestringYes"issue" or "hold"
passengersarrayYesPassenger details (see below)
contact_detailsobjectYes{ email, phone_number }
send_emailbooleanNofalse to suppress confirmation email. Default true.

Passenger object

FieldTypeRequiredDescription
typestringYes"adult", "child", "infant", "seated_infant", "senior", "young_adult"
genderstringYes"MALE" or "FEMALE"
first_namestringYesGiven name as on passport — Latin/ASCII only
last_namestringYesFamily name as on passport — Latin/ASCII only
date_of_birthstringYesYYYY-MM-DD — age evaluated at departure date
identity_documentsarrayYesArray with one passport object

Name encoding: Use plain Latin (ASCII) letters only. No diacritics (Jose not José), no non-Latin scripts. Allowed: A–Z, a–z, spaces, hyphens. Airlines and GDS systems reject anything outside this range. See Flight passengers & ages for full rules.

Identity document object

FieldTypeRequiredDescription
typestringYes"passport"
numberstringYesDocument number
issuing_countrystringYesISO 3166-1 alpha-2 (e.g. "DZ")
expiry_datestringYesYYYY-MM-DD
nationalitystringNoISO 3166-1 alpha-2

Request examples

curl --request POST \
  --url https://api.fx-port.com/api/v1/flights/book_flight \
  --header 'Authorization: Bearer fxp_live_YOUR_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "search_id": "fs_9fa52bad-a7be-42aa-9ad6-d3f9fa642dc3",
    "offer_id": "fx_offer_a1b2c3d4",
    "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": "[email protected]",
      "phone_number": "+213554657687"
    }
  }'

Response example — instant issue

A Duffel instant-issue booking, where the supplier prices in USD and the agency settles in DZD. exchangeRate appears at the top level when currencies differ.

{
  "environment": "sandbox",
  "success": true,
  "requestId": "e4efec38",
  "searchId": "fs_dfx_14923c6e-48be-4668-afe8-95446a99c8ec",
  "offerId": "fx_offer_56e6a88c",
  "processingTime": 0.51,
  "exchangeRate": 250.0,
  "booking": {
    "reference": "ord_0000B9o1uvvKVjUSYYrDhk",
    "status": "confirmed",
    "pnr": "4CJ5SB",
    "type": "instant",
    "createdAt": "2026-08-27T18:19:01.367831Z",
    "cancelledAt": null,
    "availableActions": ["cancel", "change", "update"],
    "voidableUntil": null
  },
  "supplier": {
    "id": "duffel_1",
    "technology": "duffel",
    "name": "Duffel"
  },
  "agency": {
    "agencyId": "2e800fb6-0bbf-4fc1-b0bd-0f3be3b5bcc4",
    "currency": "DZD"
  },
  "pricing": {
    "supplierCurrency": "USD",
    "agencyCurrency": "DZD",
    "basePrice": 16440.0,
    "taxesAndFees": 5238.8,
    "agencyCommission": 21.68,
    "finalPrice": 21700.48,
    "b2bPrice": 21678.8,
    "additionalServices": []
  },
  "payment": {
    "status": "paid",
    "paidAt": "2026-08-27T18:19:01Z",
    "awaitingPayment": false
  },
  "documents": [
    {
      "type": "electronic_ticket",
      "uniqueIdentifier": "1",
      "passengerIds": ["pas_0000B9o1tnN9Lys2jFYxi1"]
    }
  ],
  "bookingReferences": [
    {
      "pnr": "4CJ5SB",
      "carrier": { "iataCode": "BA", "name": "British Airways" }
    }
  ],
  "passengers": [
    {
      "id": "pas_0000B9o1tnN9Lys2jFYxi1",
      "type": "ADULT",
      "givenName": "Noah",
      "familyName": "Andersson",
      "ticketNumber": "1"
    }
  ],
  "meta": {
    "supportHold": false,
    "supportVoid": false,
    "voidDeadline": null
  },
  "postProcessing": {
    "bookingDbId": "8649a997-b426-4735-aa38-28c0e00e0932",
    "bucketLink": null,
    "emailSent": false
  }
}

documents[n].uniqueIdentifier is supplier-dependent — some suppliers (like Duffel in sandbox) return a short internal ticket identifier rather than a full IATA-format ticket number. Treat it as an opaque string; don't parse or validate its format.

Response example — hold booking

The same request as above but with booking_type: "hold" against an Amadeus Algeria offer that supports holds — same currency both sides (DZD), so there's no top-level exchangeRate. The order is unpaid: booking.status is "pending" and payment.status/awaitingPayment (surfaced via get_order_quote) show it still needs pay_order.

{
  "environment": "sandbox",
  "success": true,
  "requestId": "7053291b",
  "searchId": "fs_df598ac9-ce7d-47a6-95ee-e62a15141a99",
  "offerId": "fx_offer_605e2089",
  "processingTime": 2.79,
  "booking": {
    "reference": "eJzTd9ePMo_0NXMEAAtYAlQ",
    "status": "pending",
    "pnr": "Z7YM6A",
    "type": "hold",
    "createdAt": "2026-08-27T18:14:00.000",
    "cancelledAt": null,
    "availableActions": ["confirm", "cancel"],
    "voidableUntil": null
  },
  "supplier": {
    "id": "amadeus_aqc_dz_1",
    "technology": "amadeus_aqc",
    "name": "Amadeus AQC"
  },
  "agency": {
    "agencyId": "2e800fb6-0bbf-4fc1-b0bd-0f3be3b5bcc4",
    "currency": "DZD"
  },
  "pricing": {
    "supplierCurrency": "DZD",
    "agencyCurrency": "DZD",
    "basePrice": 3160.0,
    "taxesAndFees": 1921.44,
    "agencyCommission": 5.09,
    "finalPrice": 5086.53,
    "b2bPrice": 5081.44,
    "additionalServices": [],
    "acd": { "applied": false }
  },
  "payment": {
    "status": "awaiting_payment",
    "paidAt": null,
    "paymentRequiredBy": null,
    "priceGuaranteeExpiresAt": null,
    "awaitingPayment": true
  },
  "documents": [],
  "bookingReferences": [
    {
      "pnr": "Z7YM6A",
      "carrier": { "iataCode": "AH", "name": "Air Algerie" }
    }
  ],
  "passengers": [
    {
      "id": "1",
      "type": "ADULT",
      "givenName": "MARCO",
      "familyName": "ROSSI"
    }
  ]
}

Save booking.reference as order_id and continue with Get order quote and Pay order to complete the purchase, or Cancel order to release the hold.

Key fields

FieldDescription
booking.referenceSupplier order ID — use this as order_id in all subsequent calls
booking.pnrAirline record locator — not globally unique, use reference as primary ID
booking.status"confirmed" for instant issue; "pending" for an unpaid hold
booking.voidableUntilVoid cutoff (midnight in airline timezone, UTC). null when voiding is not supported.
documents[n].uniqueIdentifierE-ticket / document identifier — format varies by supplier, treat as opaque
postProcessing.bookingDbIdInternal booking UUID — same value later returned as booking.id by get_booking
postProcessing.bucketLinkPDF URL — null until generated. Poll get_booking to get the link.
exchangeRatePresent at top level when supplierCurrency ≠ agencyCurrency. Never inside pricing.
pricing.acdAlgerian Currency Declaration flag, Amadeus Algeria only — see Flight pricing

Getting the confirmation PDF

After issuance, the PDF is generated asynchronously (typically 10–30 seconds). Poll POST /api/v1/flights/get_booking until booking.bucket_link is populated, or use POST /api/v1/flights/get_ticket.

On this page