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

The following is a representative Duffel response where the supplier prices in USD and the agency settles in DZD. exchangeRate appears at the top level when currencies differ. For Amadeus Algeria (same-currency DZD→DZD), exchangeRate is absent and pricing may include an acd block.

{
    "success": true,
    "environment": "sandbox",
    "requestId": "a1b2c3d4",
    "offerId": "fx_offer_a1b2c3d4",
    "supplierCurrency": "USD",
    "agencyCurrency": "DZD",
    "exchangeRate": 250.0,
    "processingTime": 2.1,
    "supplier": {
        "id": "duffel_1",
        "name": "Duffel"
    },
    "booking": {
        "reference": "ord_0000B9UietU1vZwsABCD",
        "status": "confirmed",
        "pnr": "EDJP6K",
        "type": "instant",
        "createdAt": "2026-08-18T09:14:00Z",
        "cancelledAt": null,
        "voidableUntil": null
    },
    "pricing": {
        "supplierCurrency": "USD",
        "agencyCurrency": "DZD",
        "basePrice": 6170.0,
        "taxesAndFees": 4926.8,
        "agencyCommission": 11.1,
        "finalPrice": 11107.9,
        "b2bPrice": 11096.8,
        "additionalServices": []
    },
    "travelerPricing": [
        {
            "travelerId": "1",
            "travelerType": "ADULT",
            "fareOption": "STANDARD",
            "price": {
                "supplierCurrency": "USD",
                "agencyCurrency": "DZD",
                "base": 4800.0,
                "total": 11107.9,
                "b2bPrice": 11096.8
            }
        }
    ],
    "bookingReferences": [
        {
            "pnr": "EDJP6K",
            "carrier": {
                "iataCode": "AF",
                "name": "Air France",
                "logoUrl": "https://api.fx-port.com/media/airlines/AF.svg"
            }
        }
    ],
    "documents": [
        {
            "type": "electronic_ticket",
            "documentType": "ETICKET",
            "uniqueIdentifier": "057-1234567890",
            "status": "ISSUED",
            "passengerIds": ["1"]
        }
    ],
    "passengers": [
        {
            "id": "1",
            "type": "ADULT",
            "givenName": "JAMES",
            "familyName": "SMITH",
            "ticketNumber": "057-1234567890"
        }
    ],
    "payment": {
        "status": "paid",
        "awaitingPayment": false
    },
    "meta": {
        "supportHold": false,
        "supportVoid": false,
        "voidDeadline": null
    },
    "postProcessing": {
        "bucketLink": null,
        "emailSent": false
    }
}

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.voidableUntilVoid cutoff (midnight in airline timezone, UTC). null when voiding is not supported.
documents[n].uniqueIdentifierE-ticket number
postProcessing.bucketLinkPDF URL — null until generated. Poll get_booking to get the link.
exchangeRatePresent at top level when supplierCurrency ≠ agencyCurrency. Never inside 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