Skip to content
FX-Port/Docs
GuidesAPI referenceChangelog
Get API keys
  • Introduction
  • Quickstart
  • Security
  • Authentication
  • Environments and Postman
  • Sandbox
  • Conventions and naming
  • Legacy names and twins
  • Errors
  • Rate limits and fair use
  • Streaming search results

Concepts

  • Overview
  • Booking flows
  • Passengers and ages
  • Pricing
  • Post-booking operations

Search and price

  • Airport autocomplete
  • Search flights
  • Basic routes
  • Passengers and cabins
  • Filters
  • Supplier selection
  • Open-jaw and multi-city
  • Flexible dates
  • Price an offer

Book, pay and cancel

  • Book
  • Quote a held booking
  • Pay a held booking
  • Cancel a held booking
  • Quote a cancellation
  • Confirm a cancellation

After booking

  • Get a booking
  • Get the ticket PDF
  • Get the ticket number
  • ACD passport uploads
  • Use these docs with AI

Flights · Book, pay and cancel

Pay a held booking

Pay a held order, issue the tickets and debit the agency balance. Requires Read+Write permission.

POST/api/v1/flights/pay_order

Pay for a held booking and issue the tickets. The agency balance is debited by pricing.b2bPrice.

Permission: Read+Write. Rate group: booking (1,200 requests per minute per key). In the sandbox no money moves.

The amount and the currency are resolved by the server from the stored booking. Do not send currency: the request is refused with 400 (Do not pass 'currency'. Currency is resolved automatically...). An amount is not read; it is ignored.

Identifying the order

FieldDescription
order_idThe supplier's order id (booking.reference of the booking response). Preferred.
booking_idThe PNR of the booking.
pnrThe airline record locator. Not unique: prefer order_id.

The first one present wins, in this order; another one that holds a different value is dropped, and an ignored_field warning names it. The FX-Port UUID is not accepted. Other fields:

FieldTypeDescription
payment_typestringbalance (default), card or arc_bsp_cash. Only Duffel reads it; Amadeus Algeria and Travelport pay from the balance whatever you send.
three_d_secure_session_idstringFor a Duffel card payment that needs 3-D Secure.
price_guaranteedbooleanDefault true: pay the price of the hold. Leave it out. false asks FX-Port to re-price at payment time.
supplier_idstringOnly used when the booking has no supplier of its own; default duffel_1.

Legacy names: orderId, bookingId, paymentType, threeDSecureSessionId, priceGuaranteed, supplierId still work and are answered with a deprecated_field warning.

Payment cannot revive an expired hold. pay_order fails when the PNR expired or was cancelled, the deadline has passed or the airline released the hold early; airlines can release a PNR before the time that was displayed. Read success, paymentSucceeded and paymentDetails.status of the answer: do not confirm the traveler, and do not charge them in your own system, until the payment succeeded.

pay_order has no send_email switch: the confirmation e-mail is always requested (also in the sandbox, where emailSent can turn true).

Request example

curl https://api.fx-port.com/api/v1/flights/pay_order \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "order_id": "eJzTd9ePNA9wdA0FAAs8AmE",
  "payment_type": "balance"
}'

Response example

The held Amadeus Algeria order of the previous pages, paid. Real response of 2026-11-01, trimmed (the itinerary, the traveler pricing and the owner are shortened); the agency and the contact data are fake. There is no exchangeRate because both currencies are DZD.

{
  "environment": "sandbox",
  "success": true,
  "paymentSucceeded": true,
  "requestId": "1ad06b56",
  "orderId": "eJzTd9ePNA9wdA0FAAs8AmE",
  "processingTime": 2.981,
  "paymentDetails": {
    "paymentId": null,
    "status": "succeeded",
    "type": "issuance",
    "paymentAmount": 4925.44,
    "currency": "DZD",
    "createdAt": "2026-11-01T15:26:21.896668",
    "message": "Payment successful! Tickets have been issued."
  },
  "booking": {
    "reference": "eJzTd9ePNA9wdA0FAAs8AmE",
    "status": "confirmed",
    "pnr": "Y7PAEU",
    "type": "instant",
    "createdAt": "2026-11-01T15:26:21.896668",
    "cancelledAt": null,
    "content": "managed",
    "syncedAt": "2026-11-01T15:26:21.896668",
    "availableActions": [],
    "voidableUntil": "2026-11-01T23:00:00Z",
    "cancellation": null
  },
  "supplier": { "id": "amadeus_aqc_dz_1", "technology": "amadeus_aqc", "name": "Amadeus AQC", "offerId": "1" },
  "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": "paid", "paidAt": "2026-11-01T15:26:21.896710", "paymentRequiredBy": null, "priceGuaranteeExpiresAt": null, "awaitingPayment": false },
  "documents": [
    {
      "type": "electronic_ticket",
      "documentType": "ETICKET",
      "uniqueIdentifier": "124-2446977397",
      "status": "ISSUED",
      "passengerIds": ["1"],
      "segmentIds": ["1"]
    }
  ],
  "bookingReferences": [{ "pnr": "Y7PAEU", "carrier": { "iataCode": "AH", "name": "Air Algerie" } }],
  "passengers": [
    {
      "id": "1",
      "type": "ADULT",
      "title": "mr",
      "givenName": "JAMES",
      "familyName": "SMITH",
      "gender": "male",
      "bornOn": "1988-06-15",
      "email": "[email protected]",
      "phoneNumber": "15555550123",
      "infantPassengerId": null,
      "loyaltyProgrammeAccounts": [],
      "ticketNumber": "124-2446977397"
    }
  ],
  "travelerPricing": ["..."],
  "itinerary": ["..."],
  "internalBooking": { "bookingId": "Y7PAEU", "internalId": "711371c3-00aa-4be2-b790-9f0ca2ccfdb7" },
  "meta": { "paymentStatus": "paid", "orderConfirmed": true, "supportHold": true, "supportVoid": true, "voidDeadline": "2026-11-01T23:00:00Z" },
  "postProcessing": { "bucketLink": null, "emailSent": true }
}

Field meanings

FieldMeaning
paymentSucceededtrue or false: check it and paymentDetails.status.
paymentDetails.statussucceeded once the tickets are issued.
paymentDetails.typeissuance for a hold that is paid and ticketed.
paymentDetails.paymentAmountWhat was debited: equals pricing.b2bPrice.
booking.statusconfirmed now (it was pending while held); booking.type is instant.
booking.voidableUntilThe end of the same-day void window: midnight in the supplier's time zone of the day of the payment. A hold created days ago is voidable until midnight of the day it is paid.
documents[n].uniqueIdentifierThe e-ticket number.
pricingThe price block; pricing.acd on Amadeus Algeria. See Flight pricing.
requiresACDPresent and true for an ACD booking: send the passports.
internalBooking.internalIdThe FX-Port id of the booking (a UUID): the same as id of get_booking and postProcessing.bookingDbId of book_flight. Use it as the key of the booking in your own database. internalBooking.bookingId is the PNR.

Rounding can make finalPrice and b2bPrice one hundredth different from get_booking: see Flight pricing.

Errors

A refusal by the supplier is a 200 with success: false and paymentSucceeded: false: the HTTP status is not enough. error.code is a number as text for Amadeus and a name for Duffel.

WhereWhat you seeMeaning
200, success: falseerror.code 4926, "The flightOrder is already issued"The order was already paid.
200, success: falseerror.code 9112, "NEED ITINERARY for record ..."The reservation is cancelled or expired (a cancelled hold).
200, success: falseorder_type_not_eligible_for_payment (Duffel), with a userMessageA Duffel order that was bought instantly cannot be paid later.
400bad_requestNone of order_id, booking_id, pnr was sent (Missing required parameter: order_id (or booking_id / pnr)).
400bad_requestcurrency was in the body (Do not pass 'currency'...).
400invalid_booking_id, invalid_payment_type, invalid_identifier, invalid_field_typeAn id is not a valid token (letters, digits and - _ . : = ~, up to 128 characters), payment_type is not balance, card or arc_bsp_cash, or price_guaranteed is not a boolean.
402insufficient_balance (details: available, required, currency)Live only.
403read_write_permission_requiredA read-only key.
403agency_not_activeLive only.
404not_foundThe order is not a booking of your agency in this environment: Booking not found for order_id: <id>. Cannot process payment. An unknown order and the order of another agency are answered the same, and nothing is paid.
503balance_lock_timeoutLive only: another operation of your agency holds the balance in the same currency for a moment. Nothing was paid: the lock is taken before the supplier is called, so you can send the same request again after a few seconds. 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.

Do not retry a payment blindly: read the booking with get_booking first. A second pay_order for an order that is paid answers 4926, which is harmless. 503 balance_lock_timeout is safe to repeat: the payment never started.

Related

  • Quote a held booking, Cancel a held booking and Booking flows.
PreviousQuote a held bookingNext Cancel a held booking

On this page

  • Identifying the order
  • Request example
  • Response example
  • Field meanings
  • Errors
  • Related

© 2026 FX-Port

TermsPrivacy[email protected]