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
| Field | Type | Required | Description |
|---|---|---|---|
search_id | string | Yes | searchId from the supplier entry in the search response |
offer_id | string | Yes | Offer ID from search results |
booking_type | string | Yes | "issue" or "hold" |
passengers | array | Yes | Passenger details (see below) |
contact_details | object | Yes | { email, phone_number } |
send_email | boolean | No | false to suppress confirmation email. Default true. |
Passenger object
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | "adult", "child", "infant", "seated_infant", "senior", "young_adult" |
gender | string | Yes | "MALE" or "FEMALE" |
first_name | string | Yes | Given name as on passport — Latin/ASCII only |
last_name | string | Yes | Family name as on passport — Latin/ASCII only |
date_of_birth | string | Yes | YYYY-MM-DD — age evaluated at departure date |
identity_documents | array | Yes | Array 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
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | "passport" |
number | string | Yes | Document number |
issuing_country | string | Yes | ISO 3166-1 alpha-2 (e.g. "DZ") |
expiry_date | string | Yes | YYYY-MM-DD |
nationality | string | No | ISO 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
| Field | Description |
|---|---|
booking.reference | Supplier order ID — use this as order_id in all subsequent calls |
booking.pnr | Airline record locator — not globally unique, use reference as primary ID |
booking.voidableUntil | Void cutoff (midnight in airline timezone, UTC). null when voiding is not supported. |
documents[n].uniqueIdentifier | E-ticket number |
postProcessing.bucketLink | PDF URL — null until generated. Poll get_booking to get the link. |
exchangeRate | Present 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.