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
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
| 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.status | "confirmed" for instant issue; "pending" for an unpaid hold |
booking.voidableUntil | Void cutoff (midnight in airline timezone, UTC). null when voiding is not supported. |
documents[n].uniqueIdentifier | E-ticket / document identifier — format varies by supplier, treat as opaque |
postProcessing.bookingDbId | Internal booking UUID — same value later returned as booking.id by get_booking |
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. |
pricing.acd | Algerian 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.