# FX-Port API Documentation
---
> Complete endpoint guides, request and response examples, passenger rules, pricing fields, and security guidance.
---
# Authentication
Source: /authentication
All requests must include a Bearer token in the `Authorization` header.
```http
Authorization: Bearer fxp_live_YOUR_KEY
```
Get your API keys at [fx-port.com/dashboard/api](https://fx-port.com/dashboard/api).
## Permission levels [#permission-levels]
| Permission | What it allows |
| -------------- | --------------------------------------------------------------------------------------- |
| **Read** | Search, price, get quotes, retrieve bookings, airport lookups, balances, history |
| **Read+Write** | All read access **plus** creating bookings, paying held orders, cancelling reservations |
A read key returning `403 read_write_permission_required` means the operation is state-changing
(booking, payment, or cancellation). Use a read+write key for those.
## Key prefixes and environments [#key-prefixes-and-environments]
There is **one base URL** for all environments. The environment is determined entirely by the key prefix.
| Prefix | Environment | Behaviour |
| ----------- | ----------- | ----------------------------------------------------------- |
| `fxp_live_` | **Live** | Real agency data and live financial ledger |
| `fxp_test_` | **Sandbox** | Isolated test bookings; balance endpoints return empty data |
External keys (issued to agencies) automatically resolve the agency identity from the key itself.
## IP restrictions [#ip-restrictions]
You can restrict a live key to specific IP addresses in the dashboard. Recommended for production
Read+Write keys.
## Security guide [#security-guide]
An FX-Port API key with **Read+Write** permission can create bookings and trigger real financial
debits from your agency balance. Treat it with the same care as a payment credential.
### Always keep keys server-side [#always-keep-keys-server-side]
**Never expose your FX-Port API key in:**
* Browser JavaScript (React client components, Vue, Angular, plain JS)
* Mobile apps (React Native, Expo, Flutter, native iOS/Android)
* Public repositories, commit history, or CI/CD environment variables visible in build logs
* Client-side `.env` files shipped with a compiled app
Your app's **backend** (Node.js, Python, Go, etc.) should be the only code that ever reads and uses
the FX-Port API key. If your front-end needs to trigger a booking, it should call **your own
authenticated backend**, which then calls FX-Port on the user's behalf.
```
Browser/App → Your backend → FX-Port API
↑
API key lives here only
```
### Minimal-permission keys [#minimal-permission-keys]
Use a **Read-only** key for integrations that only display search results, balances, or status.
Reserve the **Read+Write** key for the backend service that actually creates bookings.
### Key rotation [#key-rotation]
Rotate keys immediately if you suspect a leak. The dashboard lets you invalidate and replace a
key without downtime.
### Broader security hygiene [#broader-security-hygiene]
These rules apply to every sensitive credential in your application stack, not just FX-Port keys.
* **Supabase / database API keys** — never expose the `service_role` key in a browser or mobile app.
Only the `anon` key can be used client-side, and even that should be behind Row-Level Security policies.
* **Third-party vendor keys** (Stripe, Twilio, etc.) — always consumed server-side. A leaked Stripe
secret key or Twilio auth token can cause direct financial loss.
* **WordPress and open-source CMS plugins** — themes and plugins are a frequent attack vector.
Credentials stored in a WordPress database are regularly exfiltrated through plugin vulnerabilities.
If you run a CMS alongside your booking integration, keep credentials in separate, isolated
infrastructure and audit installed plugins regularly.
* **Dependency CVEs** — keep your runtime and framework versions current. Subscribe to security
advisories for your stack ([nodejs.org/en/security](https://nodejs.org/en/security),
[github.com/advisories](https://github.com/advisories)) and apply patches promptly.
---
# Environments
Source: /environments
There is **one base URL** for all environments:
```
https://api.fx-port.com
```
The environment is determined entirely by the **API key prefix** — there are no separate URLs.
| Key prefix | Environment | Behaviour |
| ----------- | ----------- | ----------------------------------------------------------------------- |
| `fxp_live_` | **Live** | Real agency data; debits real balance; real bookings |
| `fxp_test_` | **Sandbox** | Isolated test bookings; no real charges; balance endpoints return empty |
## Getting API keys [#getting-api-keys]
Create keys in the FX-Port dashboard at [fx-port.com/dashboard/api](https://fx-port.com/dashboard/api).
Both sandbox and live keys can have **Read** or **Read+Write** permissions:
| Permission | Sandbox key use | Live key use |
| ---------- | ---------------------------------------------- | ---------------------------------------------- |
| Read | Search, price, get quotes, retrieve bookings | Same |
| Read+Write | Search + **create test bookings, pay, cancel** | Search + **real bookings, real balance debit** |
## Postman collections [#postman-collections]
Pre-built collections with every endpoint, auto-wired test scripts, and randomised (Latin/ASCII)
passenger names. Download, import into Postman, set your API key as the `api_key` variable, and
every request is ready to run. **The collections contain no API keys** — add your own.
Search, price, book, hold/pay, cancel, get booking, get ticket, airports.
Balances, financial ledger, and booking history.
Discover enabled suppliers and capabilities.
Direct links:
* [FX-Port-Flights.postman\_collection.json](/postman/FX-Port-Flights.postman_collection.json)
* [FX-Port-Account.postman\_collection.json](/postman/FX-Port-Account.postman_collection.json)
* [FX-Port-Suppliers.postman\_collection.json](/postman/FX-Port-Suppliers.postman_collection.json)
Get your API keys at [fx-port.com/dashboard/api](https://fx-port.com/dashboard/api) — sandbox keys
are free.
---
# Introduction
Source: /
**FX-Port** is a unified travel API. One integration gives you **flights, hotels, eSIMs, and more**,
sourced across many suppliers and returned with the best available prices — no per-supplier code.
* **Flights** — search, price, book, hold/pay across GDS, NDC, LCC, and direct airlines. **Available now.**
* **Hotels** — global hotel inventory. **Live; docs landing shortly.**
* **eSIMs** — data packages in 190+ countries. **Live; docs landing shortly.**
* **More products** are on the way.
**Work in progress.** FX-Port is actively expanding: more suppliers, more GDS/NDC connections,
and new products. We aim to **avoid breaking changes** — any breaking or major change ships under
a **new API version** so your current integration keeps working. Have feedback, a feature request,
or found a bug? Email us at [dev@fx-port.com](mailto:dev@fx-port.com).
**API keys can trigger real financial debits.** A Read+Write key creates bookings and charges your
agency balance. **Never** expose keys in front-end code, mobile apps, or public repos — always call
the API from your backend. See [Authentication](/authentication) for the full security guide.
## Get API access in minutes [#get-api-access-in-minutes]
There is **no approval queue, no agreement to sign, and no deposit** required to start building.
Self-serve from day one:
**Create an account** — sign up at [fx-port.com/register](https://fx-port.com/register).
Takes about a minute.
**Generate your API keys** — open the [API page](https://fx-port.com/dashboard/api) in your
dashboard and create a key. Start with a sandbox key (`fxp_test_`) to build and test for free.
**Start building** — your first search call works immediately. See the
[quick start](#quick-start-flights-in-3-calls) below.
**Sandbox is free and unlimited to explore.** You only need a funded balance when you want to
issue real bookings with a `fxp_live_` key — see [Environments](/environments).
## Base URL [#base-url]
```
https://api.fx-port.com
```
All paths are prefixed with `/api/v1/`. **One URL for all environments** — live vs sandbox is
determined by your API key prefix (`fxp_live_` or `fxp_test_`).
## Where to start [#where-to-start]
API keys, permissions, IP restrictions, and the full security guide. Read this first.
Live vs sandbox, and downloadable Postman collections.
Discover every active supplier and its capabilities.
Balances, financial ledger, and booking history across all products.
Search, price, book, hold, pay, cancel, get PDF.
Coming to the docs shortly — already live on the API.
## Quick start (flights, in 3 calls) [#quick-start-flights-in-3-calls]
**Search** — `POST /api/v1/get_flights`. Save `suppliers[0].searchId` and an offer `id`.
**Price** — `POST /api/v1/flights/price_flight` with that `search_id` and `offer_id`. Required
before booking.
**Book** — `POST /api/v1/flights/book_flight` with `booking_type: "issue"`, the passengers, and
contact details. Tickets are issued immediately.
```bash
# 1. Search
curl --request POST \
--url https://api.fx-port.com/api/v1/get_flights \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--header 'Content-Type: application/json' \
--data '{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"cabin_class": "economy",
"passengers": {
"adults": 1
}
}'
```
Continue with the full [Flight booking flow →](/flights/booking-flows)
## Built with AI tools? [#built-with-ai-tools]
Point your AI coding assistant at these machine-readable files for accurate, up-to-date context:
| URL | Content |
| ---------------------------------- | ----------------------------------------------------- |
| [`/llms.txt`](/llms.txt) | Page index — titles, URLs, descriptions (lightweight) |
| [`/llms-full.txt`](/llms-full.txt) | Full documentation text |
## Contact [#contact]
Questions, feedback, or issues? Email the developer team at
[dev@fx-port.com](mailto:dev@fx-port.com). Get your API keys at
[fx-port.com/dashboard/api](https://fx-port.com/dashboard/api).
---
# Sandbox notice
Source: /sandbox
Sandbox API keys (`fxp_test_...`) connect to the **sandbox environments of our underlying
suppliers** (Amadeus test, Duffel test, Airalo sandbox, etc.).
Supplier sandbox environments are **not under FX-Port's control**. They may experience brief
outages for maintenance, and some flights or airlines may return errors during pricing or
booking that would not occur on live. This is a known limitation of third-party sandbox
environments — it often works correctly on live even when sandbox fails.
## What sandbox does [#what-sandbox-does]
| Behaviour | Detail |
| ------------------------------------- | ---------------------------------------------------------------------------------------- |
| **No real balance debit** | All financial operations are skipped. Sandbox bookings do not charge your balance. |
| **Realistic but synthetic inventory** | Flight offers are real data structures but use test fares. Not all routes are available. |
| **Balance endpoints return empty** | `GET /api/v1/balances` returns `[]` in sandbox. This is expected. |
| **Webhooks are sent** | Sandbox webhooks fire to the sandbox webhook URL you configure. |
## Common sandbox quirks [#common-sandbox-quirks]
* **Duffel sandbox**: offer inventory is randomised and changes frequently. An offer available in
search may expire by the time you price it. This is normal — retry with a fresh search.
* **Amadeus test**: some airline codes or routes may reject at the booking step even though they
appeared in search results. Try a different route (e.g. `ALG → ORN`) if you hit repeated errors.
* **Outages**: if a supplier's sandbox is down, FX-Port will still respond but the supplier's
entry in the response will have `success: false`. Wait and retry.
## Testing checklist [#testing-checklist]
Before switching to live keys, verify in sandbox:
1. **Search** — your search request structure is accepted and returns offers
2. **Price** — re-pricing a cached offer works and pricing fields match your expectations
3. **Book (issue)** — instant booking completes and returns a PNR and e-ticket
4. **Book (hold)** — hold booking creates a reservation with `status: awaiting_payment`
5. **Pay** — `pay_order` with the held reservation's `order_id` succeeds
6. **Get booking** — `get_booking` returns the full booking including `bucket_link` (PDF) once generated
7. **Cancel a hold** — `cancel_order` releases an unpaid held PNR. Issued-ticket void, exchange,
re-issue, and refund remain manual support operations.
---
# GET /api/v1/balances/{balance_id}/history
Source: /account/balance-history
The complete financial ledger for a specific currency balance. Returns every money movement recorded
against the balance.
**Permission:** Read
Call `GET /api/v1/balances` first to obtain the `balance_id` for the currency you want to query.
**Sandbox:** Returns empty `data` array.
## The `source` field [#the-source-field]
Each entry has a `source` field:
| `source` | What it represents |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"transactions"` | **Authoritative balance movements.** Recorded every time the balance changes: top-up, booking charge, or refund. **Use these for reconciliation totals.** |
| `"flight_balance_operations"` | **Per-booking flight audit records.** Granular events within a booking lifecycle (initial debit, FX surcharge, adjustment, refund). One booking can produce multiple rows. |
| `"hotel_balance_operations"` | **Per-booking hotel audit records.** Same concept as flight operations. |
A single action (e.g. confirming a flight) produces **both** a `transactions` row and a
`flight_balance_operations` row. Both appear in this response. Sum only `transactions` rows
for financial totals.
## Path parameter [#path-parameter]
| Parameter | Description |
| -------------- | --------------------------------------------------- |
| `{balance_id}` | UUID of the balance row from `GET /api/v1/balances` |
## Query parameters [#query-parameters]
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------------------------------------------------------- |
| `from` | string | Inclusive start. ISO-8601 date (`2026-07-01`) or datetime (`2026-07-01T00:00:00Z`) |
| `to` | string | Inclusive end. Bare dates expand to `23:59:59 UTC` |
| `type` | string | Filter by type: `CREDIT` \| `DEBIT` \| `REFUND` (applies to `transactions` only) |
| `page` | integer | 1-based page number. Default: `1` |
| `limit` | integer | Items per page. Range 1–100. Default: `20` |
## Transaction types [#transaction-types]
| Type | Direction | When it appears |
| ------------------- | ------------- | ----------------------------------------------------------------------- |
| `CREDIT` / `credit` | Funds **in** | Manual top-ups, deposit returns when a hold is released |
| `DEBIT` / `debit` | Funds **out** | Booking confirmations, FX exchange surcharges, manual debit adjustments |
| `REFUND` / `refund` | Funds **in** | Full or partial refund after a booking is cancelled or voided |
Uppercase values appear in `transactions` rows; lowercase in `*_balance_operations` rows.
## Request example [#request-example]
```bash
curl --get \
--url https://api.fx-port.com/api/v1/balances/db002f7a-c136-4f32-b19d-38a2542f2af9/history \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--data-urlencode 'from=2026-07-01' \
--data-urlencode 'to=2026-07-31' \
--data-urlencode 'limit=50'
```
## Response example [#response-example]
```json
{
"environment": "live",
"success": true,
"data": [
{
"source": "transactions",
"id": "3e8b1c02-d7a4-4f5e-9b2a-1c0d8e7f3a91",
"type": "CREDIT",
"amount": 2500.00,
"currency": "DZD",
"description": "Balance top-up — bank transfer ref TXN-20260711",
"balanceId": "db002f7a-c136-4f32-b19d-38a2542f2af9",
"createdAt": "2026-07-11T09:15:00.000000+00:00"
},
{
"source": "flight_balance_operations",
"id": "658185df-234c-43ba-8175-ba9a5abe6007",
"type": "debit",
"amount": 87.50,
"currency": "DZD",
"description": "Exchange fee debit for flight booking 4YEK64",
"bookingRecordId": "136dc334-b581-48c9-8e4c-7ec87cc5f41a",
"createdAt": "2026-07-09T14:30:00.000000+00:00"
},
{
"source": "transactions",
"id": "31936df0-299d-429e-b0f5-056a6c0587cc",
"type": "DEBIT",
"amount": 5081.44,
"currency": "DZD",
"description": "Booking confirmation — flight XE4YCB (ALG → CDG)",
"balanceId": "db002f7a-c136-4f32-b19d-38a2542f2af9",
"createdAt": "2026-07-01T15:42:34.272792+00:00"
},
{
"source": "transactions",
"id": "a4c72d11-08b1-4e3f-b9d8-2e5a1f6c8b02",
"type": "REFUND",
"amount": 125.00,
"currency": "DZD",
"description": "Partial refund for hotel booking HMPSHEPI1",
"balanceId": "db002f7a-c136-4f32-b19d-38a2542f2af9",
"createdAt": "2026-07-01T05:00:09.901817+00:00"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 4,
"hasMore": false
},
"meta": {
"agencyId": "2e800fb6-0bbf-4fc1-b0bd-0f3be3b5bcc4",
"balanceId": "db002f7a-c136-4f32-b19d-38a2542f2af9",
"balanceCurrency": "DZD",
"environment": "live"
}
}
```
## Field reference [#field-reference]
| Field | Present when | Description |
| ----------------- | --------------------------- | ------------------------------------------------------------------------------------- |
| `source` | Always | Entry type — see table above |
| `id` | Always | Entry UUID |
| `type` | Always | `CREDIT`, `DEBIT`, or `REFUND` (uppercase for transactions; lowercase for operations) |
| `amount` | Always | Absolute value — always positive. Direction conveyed by `type`. |
| `currency` | Always | ISO 4217 code — always matches the requested balance currency |
| `description` | Always | Human-readable label including booking reference and action |
| `balanceId` | `transactions` only | UUID of the balance this transaction is recorded against |
| `bookingRecordId` | `*_balance_operations` only | UUID of the internal booking record — use to correlate with a specific booking |
| `createdAt` | Always | ISO-8601 datetime |
**Reconciliation tip:** A single booking event produces both a `transactions` row and a
`flight_balance_operations` row. When computing net balance changes, sum only `transactions` rows.
When tracing a specific booking, filter by `source = "flight_balance_operations"` and `bookingRecordId`.
---
# GET /api/v1/balances
Source: /account/balances
Returns the current balance for each currency the agency holds.
**Permission:** Read
Capture the `id` field from each balance row — you need it as the `{balance_id}` path parameter
to query that currency's financial history.
**Sandbox:** Returns empty `data` array and `meta.financialOperationsEnabled: false`.
## Request example [#request-example]
```bash
curl --request GET \
--url https://api.fx-port.com/api/v1/balances \
--header 'Authorization: Bearer fxp_live_YOUR_KEY'
```
## Response example [#response-example]
An agency can hold multiple currency balances. Each row has its own `id`.
```json
{
"environment": "live",
"success": true,
"data": [
{
"id": "db002f7a-c136-4f32-b19d-38a2542f2af9",
"agency_id": "2e800fb6-0bbf-4fc1-b0bd-0f3be3b5bcc4",
"currency_code": "EGP",
"available_balance": 412500.00,
"created_at": "2024-08-22T10:24:06.533800+00:00",
"updated_at": "2026-08-17T04:45:15.697817+00:00"
},
{
"id": "a71c9e34-2b18-40df-9c77-6b0a1f2e5d40",
"agency_id": "2e800fb6-0bbf-4fc1-b0bd-0f3be3b5bcc4",
"currency_code": "EUR",
"available_balance": 18750.50,
"created_at": "2024-08-22T10:24:06.533800+00:00",
"updated_at": "2026-08-16T22:10:03.120400+00:00"
},
{
"id": "c5f0a812-7d63-4e9a-8b21-4e9f0c6a2b11",
"agency_id": "2e800fb6-0bbf-4fc1-b0bd-0f3be3b5bcc4",
"currency_code": "NGN",
"available_balance": 9250000.00,
"created_at": "2025-01-14T09:02:41.000000+00:00",
"updated_at": "2026-08-15T11:30:55.000000+00:00"
}
],
"meta": {
"agencyId": "2e800fb6-0bbf-4fc1-b0bd-0f3be3b5bcc4",
"environment": "live"
}
}
```
## Field reference [#field-reference]
| Field | Description |
| ------------------- | ---------------------------------------------------------------------- |
| `id` | Balance UUID — use as `{balance_id}` for the history endpoint |
| `agency_id` | Agency UUID |
| `currency_code` | ISO 4217 currency code (e.g. `EGP`, `EUR`, `NGN`, `DZD`, `USD`, `INR`) |
| `available_balance` | Usable balance, rounded to 2 decimal places |
| `created_at` | When the balance row was created |
| `updated_at` | Last movement against this balance |
---
# GET /api/v1/bookings/{id}
Source: /account/booking-detail
Returns the detail record for a single booking. You can look it up by the **internal booking id**
(a UUID) or by a public reference.
**Permission:** Read
| Path parameter | Accepts | Description |
| -------------- | ------------------------------------------------------------------------ | -------------------- |
| `{id}` | internal `id` (UUID) · PNR / booking reference · order id · ICCID (eSIM) | The booking to fetch |
**Prefer the internal `id` (UUID).** PNRs and booking references are **not globally unique** — the
same PNR can be issued by a different airline or supplier, so a lookup by PNR can be ambiguous.
The internal `id` returned in every booking/response payload is unique and stable. Store it and
use it as your primary key; treat the PNR as a display reference only.
## Query parameters [#query-parameters]
| Parameter | Default | Description |
| --------- | ------- | -------------------------------------------------------------------------------------------- |
| `product` | `all` | Scope to a specific product for faster resolution: `flights`, `hotels`, `esims`, `transfers` |
## Request examples [#request-examples]
```bash
# Recommended: internal id (UUID), unambiguous
curl --get \
--url https://api.fx-port.com/api/v1/bookings/e133d887-fd3c-4a44-a19e-4f20ddb690d4 \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--data-urlencode 'product=flights'
# Also supported: PNR (may be ambiguous across suppliers)
curl --get \
--url https://api.fx-port.com/api/v1/bookings/XE4YCB \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--data-urlencode 'product=flights'
```
## Response example [#response-example]
```json
{
"success": true,
"data": {
"product": "flights",
"bookingId": "XE4YCB",
"status": "confirmed",
"record": {
"booking_id": "XE4YCB",
"status": "confirmed",
"total_price": 5086.53,
"agency_commission": 5.09,
"currency": "DZD",
"departure_airport": "ALG",
"arrival_airport": "CDG",
"departure_date": "2026-09-15T08:30:00",
"ticket_number": "124-2442997931",
"bucket_link": "https://s3.eu-central-003.backblazeb2.com/213carousat/flight-bookings/XE4YCB-JAMES-SMITH.pdf",
"supplier_id": "amadeus_aqc_dz_1",
"environment": "live",
"created_at": "2026-08-17T11:22:00+00:00",
"paid_at": "2026-08-17T11:22:05+00:00"
},
"pricing": [
{
"supplier_currency": "DZD",
"agency_currency": "DZD",
"base_price": 3160.00,
"taxes_and_fees": 1921.44,
"agency_commission": 5.09,
"final_price": 5086.53,
"b2b_price": 5081.44,
"environment": "live"
}
],
"travelerPricing": [
{
"traveler_id": "1",
"traveler_type": "ADULT",
"fare_option": "STANDARD",
"supplier_currency": "DZD",
"agency_currency": "DZD",
"base": 3160.00,
"total": 5086.53,
"b2b_price": 5081.44,
"environment": "live"
}
],
"balanceOperations": []
},
"meta": {
"agencyId": "2e800fb6-0bbf-4fc1-b0bd-0f3be3b5bcc4",
"environment": "live"
}
}
```
## Field reference [#field-reference]
| Field | Description |
| ------------------------------ | ---------------------------------------------------------------------- |
| `record.bucket_link` | Confirmation PDF download URL (flights only) |
| `record.ticket_number` | E-ticket number(s) |
| `pricing[0].b2b_price` | Amount debited from agency balance = `final_price − agency_commission` |
| `pricing[0].taxes_and_fees` | Taxes + platform fees. Excludes `agency_commission`. |
| `travelerPricing[n].b2b_price` | Per-traveler debit amount |
---
# GET /api/v1/history
Source: /account/history
Booking summaries across one or all products, sorted by `createdAt` descending.
**Permission:** Read
Returns lightweight records — enough for status tracking, reconciliation, and dashboards. For
full traveler details, fare rules, and pricing breakdown, use the product-specific booking
endpoints (e.g. `GET /api/v1/bookings/{id}` or `POST /api/v1/flights/get_booking`).
## Query parameters [#query-parameters]
| Parameter | Type | Default | Description |
| --------- | ------- | ------- | ----------------------------------------------------------------------------- |
| `product` | string | `all` | `all` \| `flights` \| `hotels` \| `esims` \| `transfers` |
| `status` | string | — | Filter to a single status (e.g. `confirmed`, `awaiting_payment`, `cancelled`) |
| `from` | string | — | Inclusive start. ISO-8601 date or datetime |
| `to` | string | — | Inclusive end. ISO-8601 date or datetime |
| `page` | integer | `1` | 1-based page number |
| `limit` | integer | `20` | Items per page. Range 1–100 |
## Status reference [#status-reference]
| Status | Products |
| ------------------ | ------------------------------------- |
| `confirmed` | Flights, hotels, esims, transfers |
| `awaiting_payment` | Flights (held orders not yet paid) |
| `pending` | Flights, hotels (booking in progress) |
| `cancelled` | All products |
| `failed` | All products |
| `refunded` | Flights, hotels |
## Request examples [#request-examples]
```bash
# All recent bookings
curl --request GET \
--url https://api.fx-port.com/api/v1/history \
--header 'Authorization: Bearer fxp_live_YOUR_KEY'
# Confirmed flights only, this month
curl --get \
--url https://api.fx-port.com/api/v1/history \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--data-urlencode 'product=flights' \
--data-urlencode 'status=confirmed' \
--data-urlencode 'from=2026-08-01'
```
## Response example [#response-example]
```json
{
"success": true,
"data": [
{
"product": "flights",
"id": "e133d887-fd3c-4a44-a19e-4f20ddb690d4",
"bookingId": "XE4YCB",
"status": "confirmed",
"totalPrice": 6350.00,
"currency": "EGP",
"createdAt": "2026-08-17T11:22:00+00:00",
"paidAt": "2026-08-17T11:22:05+00:00",
"departureAirport": "CAI",
"arrivalAirport": "DXB"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"hasMore": false
},
"meta": {
"agencyId": "2e800fb6-0bbf-4fc1-b0bd-0f3be3b5bcc4",
"environment": "live",
"product": "all"
}
}
```
Each entry includes both the internal `id` (a stable UUID) and the public `bookingId` (PNR /
reference). **Use `id` as your primary key** when storing or re-fetching a booking — PNRs are not
globally unique. Fetch full details with [`GET /api/v1/bookings/{id}`](/account/booking-detail)
using that `id`.
---
# Account API overview
Source: /account
Read-only endpoints for querying account state: balances, financial transaction history, and booking
summaries.
**Permission:** Read (all endpoints in this section)
These endpoints expose account-level data for reconciliation, dashboards, and integration monitoring.
They are **not** the booking operations API. For full traveler details, fare rules, and booking
actions, use the product-specific endpoints (e.g. `/api/v1/flights/get_booking`).
## Endpoints [#endpoints]
| Method | Path | Description |
| ------ | --------------------------------------- | -------------------------------------------------- |
| `GET` | `/api/v1/balances` | Current balance for each currency the agency holds |
| `GET` | `/api/v1/balances/{balance_id}/history` | Full financial ledger for a specific currency |
| `GET` | `/api/v1/history` | Booking summaries across all products |
| `GET` | `/api/v1/bookings/{id}` | Single booking detail by public reference |
## Sandbox behaviour [#sandbox-behaviour]
In sandbox:
* `GET /api/v1/balances` returns an empty `data` array
* `GET /api/v1/balances/{id}/history` returns an empty `data` array
* `GET /api/v1/history` returns sandbox bookings only
* `GET /api/v1/bookings/{id}` returns sandbox bookings only
Sandbox bookings do not affect the live ledger.
## Error responses [#error-responses]
| HTTP | `detail.error` | Cause |
| ----- | ------------------- | -------------------------------------------- |
| `400` | `invalid_date` | `from` or `to` is not a valid ISO-8601 value |
| `400` | `invalid_product` | `product` is not one of the allowed values |
| `401` | — | Missing or invalid API key |
| `403` | — | Key lacks permission |
| `404` | `booking_not_found` | No booking matches in this environment |
| `500` | — | Internal server error |
---
# eSIMs API
Source: /esims
**The eSIMs API is live and being documented.** FX-Port already sells eSIM data packages across
190+ countries (via Airalo and more). Full public documentation — package catalog, ordering,
QR delivery, and top-ups — is being finalised and will appear here shortly. **Check back in a
few days.**
## What's coming [#whats-coming]
* **Package catalog** by country / region with retail pricing
* **Create order** with traveler details; QR code delivered by email
* **Top-up** an existing eSIM by ICCID
* **Order lookup** and usage status
eSIM traveler naming follows the same **Latin/ASCII-only** rule as flights.
## Want early access? [#want-early-access]
Email [dev@fx-port.com](mailto:dev@fx-port.com) for early access to the eSIM docs.
---
# GET /api/v1/flights/airports
Source: /flights/airports
Airport autocomplete for origin/destination search inputs. Accepts an IATA code, city name, or
airport name. Returns matching airports from a global database. No authentication required.
**Permission:** Public (no API key needed)
Designed for debounced typeahead — throttle requests to avoid overloading (recommended: 300 ms
debounce). The response is a flat **array** of airport objects, not a wrapped object.
## Query parameters [#query-parameters]
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ----------------------------------------------------------------------------------------------- |
| `query` | string | Yes | IATA code (e.g. `ALG`), city name (e.g. `Paris`), or airport name |
| `locale` | string | No | Language for airport and city names. Default `en`. See [Supported locales](#supported-locales). |
## Request examples [#request-examples]
```bash
# IATA code lookup
curl "https://api.fx-port.com/api/v1/flights/airports?query=CDG&locale=en"
```
```bash
# City name search, French locale
curl "https://api.fx-port.com/api/v1/flights/airports?query=Paris&locale=fr"
```
```bash
# Arabic locale (airport names in Arabic)
curl "https://api.fx-port.com/api/v1/flights/airports?query=ALG&locale=ar"
```
## Response example [#response-example]
The response is a **flat array** of airport objects (not a wrapped `{results:[...]}` object).
```json
[
{
"ap": "CDG",
"airportname": "Charles de Gaulle",
"cityname": "Paris, France",
"cc": "FR",
"country": "France",
"timezone": "Europe/Paris",
"utc": "+02:00",
"lat": 49.01278,
"lng": 2.55,
"displayname": "Charles de Gaulle, Paris, France, (CDG)",
"shortdisplayname": "Paris, France (CDG)"
},
{
"ap": "ORY",
"airportname": "Orly",
"cityname": "Paris, France",
"cc": "FR",
"country": "France",
"timezone": "Europe/Paris",
"utc": "+02:00",
"displayname": "Orly, Paris, France, (ORY)",
"shortdisplayname": "Paris, France (ORY)"
}
]
```
## Key field reference [#key-field-reference]
| Field | Description |
| ------------------ | ----------------------------------------------------------------------- |
| `ap` | IATA airport code — use as `origin` or `destination` in search requests |
| `airportname` | Airport name in the requested locale |
| `cityname` | City and country in the requested locale |
| `cc` | ISO 3166-1 alpha-2 country code |
| `country` | Country name |
| `timezone` | IANA timezone (e.g. `Europe/Paris`) |
| `utc` | UTC offset at query time |
| `displayname` | Full display label — suitable for showing in a result list |
| `shortdisplayname` | Short display label — suitable for showing selected value |
The response contains additional internal fields (coordinates, entity keys, popularity data) that
are not listed here. Use only the fields documented above; internal fields may change.
## Supported locales [#supported-locales]
Pass as `locale=XX` in the query string. Unrecognised values default to `en`.
| `locale` | Language |
| -------- | --------------------- |
| `en` | English (default) |
| `fr` | French |
| `ar` | Arabic |
| `de` | German |
| `es` | Spanish |
| `es-AR` | Spanish (Argentina) |
| `it` | Italian |
| `pt` | Portuguese |
| `nl` | Dutch |
| `nl-BE` | Dutch (Belgium) |
| `pl` | Polish |
| `cs` | Czech |
| `ru` | Russian |
| `uk` | Ukrainian |
| `el` | Greek |
| `ro` | Romanian |
| `da` | Danish |
| `no` | Norwegian |
| `sv` | Swedish |
| `fi` | Finnish |
| `tr` | Turkish |
| `he` | Hebrew |
| `ko` | Korean |
| `ja` | Japanese |
| `zh-CN` | Chinese (Simplified) |
| `zh-TW` | Chinese (Traditional) |
| `th` | Thai |
| `id` | Indonesian |
| `ms` | Malay |
| `vi` | Vietnamese |
| `en-PH` | English (Philippines) |
Locale affects airport and city name translations. IATA codes (`ap`, `cc`) are always in ASCII.
---
# POST /api/v1/flights/book_flight
Source: /flights/book
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 [#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 [#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](/flights/passengers) for full rules.
### Identity document object [#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 [#request-examples]
```bash
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": "traveler@example.com",
"phone_number": "+213554657687"
}
}'
```
```bash
# Use only after price_flight returns supportHold: true for this offer.
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": "hold",
"passengers": [
{
"type": "adult",
"gender": "FEMALE",
"first_name": "Maria",
"last_name": "Garcia",
"date_of_birth": "1991-03-22",
"identity_documents": [
{
"type": "passport",
"number": "B87654321",
"issuing_country": "DZ",
"expiry_date": "2030-11-15"
}
]
}
],
"contact_details": {
"email": "traveler@example.com",
"phone_number": "+213554657687"
}
}'
```
```bash
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": "Omar",
"last_name": "Nguyen",
"date_of_birth": "1985-04-10",
"identity_documents": [
{
"type": "passport",
"number": "P11223344",
"issuing_country": "DZ",
"expiry_date": "2031-04-18"
}
]
},
{
"type": "adult",
"gender": "FEMALE",
"first_name": "Priya",
"last_name": "Nguyen",
"date_of_birth": "1987-08-20",
"identity_documents": [
{
"type": "passport",
"number": "P44332211",
"issuing_country": "DZ",
"expiry_date": "2030-12-01"
}
]
},
{
"type": "child",
"gender": "MALE",
"first_name": "Leo",
"last_name": "Nguyen",
"date_of_birth": "2018-05-14",
"identity_documents": [
{
"type": "passport",
"number": "P99887766",
"issuing_country": "DZ",
"expiry_date": "2029-05-14"
}
]
}
],
"contact_details": {
"email": "traveler@example.com",
"phone_number": "+213554657687"
}
}'
```
## Response example — instant issue [#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.
```json
{
"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 [#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 [#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`.
---
# Flight booking flows
Source: /flights/booking-flows
Every flight booking follows one of two flows. Both start with a search and a mandatory pricing
step.
## Instant issue (most common) [#instant-issue-most-common]
Tickets are issued immediately after booking.
**Search for flights**
`POST /api/v1/get_flights` — returns offers from one or all suppliers. Each supplier entry has
a `searchId` — save it for the next steps.
**Price the offer** *(required)*
`POST /api/v1/flights/price_flight` — confirms the current fare and caches the priced offer.
This step is **mandatory** — `book_flight` reads the priced offer from cache. Booking without
pricing first will fail.
**Book and issue tickets**
`POST /api/v1/flights/book_flight` with `"booking_type": "issue"` — tickets are issued
immediately. The response contains `booking.reference` (the supplier order ID) and `booking.pnr`.
**Retrieve booking + PDF**
`POST /api/v1/flights/get_booking` — returns the full booking record. Once the PDF is generated
(typically 10–30 seconds), `booking.bucket_link` contains the download URL.
```
GET_FLIGHTS → PRICE_FLIGHT → BOOK_FLIGHT (issue) → GET_BOOKING
```
***
## Hold then pay (suppliers supporting holds only) [#hold-then-pay-suppliers-supporting-holds-only]
Reserve a seat without paying, then pay later. Use this flow only when the selected search and
priced offer return `supportHold: true`. Never infer hold support from a supplier name; capability
can vary by supplier, airline, fare, and offer.
**Search and price** — same as instant issue (steps 1 and 2 above).
**Create a hold reservation**
`POST /api/v1/flights/book_flight` with `"booking_type": "hold"` — a reservation is created
without ticket issuance or a balance charge. The immediate supplier response may use
`booking.status: "pending"`; stored booking and quote responses use `"awaiting_payment"`.
**Preview current price** *(optional)*
`POST /api/v1/flights/get_order_quote` — returns the current confirmed price and whether the
hold is still payable. Pass `order_id` (preferred) or `pnr`.
**Pay and issue tickets**
`POST /api/v1/flights/pay_order` — charges the agency balance and issues tickets. Pass
`order_id`. Do **not** send `amount` or `currency` — they are resolved server-side.
**A held booking is not guaranteed until payment succeeds.** `pay_order` will fail when the PNR
has expired or been cancelled, the hold/payment deadline has passed, or the airline has released
the reservation early. Airlines can expire a hold before the date or time originally returned.
Treat the `pay_order` response as authoritative, handle failure without charging the traveler,
and run a new search when a fresh booking is required.
```
GET_FLIGHTS → PRICE_FLIGHT → BOOK_FLIGHT (hold) → [GET_ORDER_QUOTE] → PAY_ORDER → GET_BOOKING
```
***
## Cancelling a held reservation [#cancelling-a-held-reservation]
**`cancel_order` cancels a HELD (unpaid) reservation — it is NOT a refund.** It deletes the
reservation and releases the PNR with the airline. It does **not** apply to issued (paid) tickets
and does **not** refund anything, because a held reservation was never paid.
**Why this matters:** if you create a hold and then abandon it, the airline may **refuse a new
booking for the same passenger** on the same route while an un-voided reservation is pending.
Always cancel the old hold to release the PNR before re-booking that passenger.
```
BOOK_FLIGHT (hold) → (changed your mind) → CANCEL_ORDER → PNR released → safe to re-book
```
`POST /api/v1/flights/cancel_order` — pass `order_id` (preferred) or `booking_id`.
***
## Post-booking operations (void, exchange, refund) [#post-booking-operations-void-exchange-refund]
**Self-serve void, exchange, and refund on issued tickets are not yet available through the API.**
Right now, all post-booking operations on **paid/issued** bookings — **void, exchange, re-issue,
and refund** — must be **submitted to the FX-Port team for manual processing**. Email
[dev@fx-port.com](mailto:dev@fx-port.com), or open the booking confirmation page in the
FX-Port dashboard and use **Request support**. That action is available on every booking.
Requests are handled by a dedicated team over multiple channels — in-dashboard support, email, and
a direct messaging channel — **24/7**, and urgent cases such as same-day voids are actioned
immediately.
This limitation applies to **issued flight tickets only**. Post-booking operations for **hotels,
transfers, and eSIMs** are fully supported through the API. FX-Port is actively building self-serve
post-booking endpoints for flights and rolling them out per supplier as airline and GDS access is
granted. Until then, do **not** build a workflow that assumes automated refunds or voids on issued
tickets. See [Post-booking operations](/flights/post-booking) for details.
---
# POST /api/v1/flights/cancel_order
Source: /flights/cancel-order
Cancel a **held (unpaid)** reservation. This deletes the reservation and **releases the PNR** with
the airline.
**Permission:** Read+Write
**This is NOT a refund and does NOT apply to issued (paid) tickets.** A held reservation was never
paid, so nothing is refunded — the reservation is simply cancelled and the PNR released.
For refund / void / exchange of an **issued** ticket, see
[Post-booking operations](/flights/post-booking) (currently handled manually by the FX-Port
team through dashboard **Request support** or [dev@fx-port.com](mailto:dev@fx-port.com)).
## Why cancel a hold [#why-cancel-a-hold]
If you create a hold and then abandon it, the airline may **refuse a new booking for the same
passenger** on the same route while the un-voided reservation is pending. Always cancel the old
hold to release the PNR before re-booking that passenger.
## Identifying the booking [#identifying-the-booking]
| Field | Description |
| ------------ | ---------------------------------------------------------------- |
| `order_id` | Preferred. Supplier order ID returned when the hold was created. |
| `booking_id` | FX-Port booking ID or stored booking reference. |
| `pnr` | Airline PNR returned when the hold was created. |
Prefer `order_id`. PNRs are not globally unique across suppliers/airlines.
Send only one identifier. FX-Port resolves it to the supplier order ID before cancelling the hold.
## Request example [#request-example]
```bash
curl --request POST \
--url https://api.fx-port.com/api/v1/flights/cancel_order \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--header 'Content-Type: application/json' \
--data '{
"order_id": "eJzTd9ePcDWJdHYCAAs9AlU"
}'
```
## Response example [#response-example]
```json
{
"success": true,
"status": "cancelled",
"cancelledAt": "2026-08-17T12:00:00Z"
}
```
---
# POST /api/v1/flights/flexible_search
Source: /flights/flexible-search
Returns a date matrix showing the lowest price for each day ±3 days around the requested departure
and return dates, alongside regular offers.
**Permission:** Read
**Flexible date search is capability-based.** Only suppliers that support flexible-date matrices
are queried by this endpoint. Currently Amadeus AQC supplies the matrix; availability can expand
as suppliers are added. Always check for `flexPrices` before reading it.
## Request body [#request-body]
Same fields as `POST /api/v1/get_flights`. Omit `supplier_id` to use all enabled suppliers that
support flexible-date matrices, or target a supplier only after confirming that capability.
## Request example [#request-example]
```bash
curl --request POST \
--url https://api.fx-port.com/api/v1/flights/flexible_search \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--header 'Content-Type: application/json' \
--data '{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"return_date": "2026-09-22",
"cabin_class": "economy",
"passengers": {
"adults": 1
}
}'
```
## Response example (abbreviated) [#response-example-abbreviated]
```json
{
"success": true,
"searchId": "fs_flex001",
"flexPrices": {
"matrix": [
{
"departureDate": "2026-09-13",
"returnDate": "2026-09-20",
"finalPrice": 27100.00,
"agencyCurrency": "DZD"
},
{
"departureDate": "2026-09-14",
"returnDate": "2026-09-21",
"finalPrice": 26500.00,
"agencyCurrency": "DZD"
},
{
"departureDate": "2026-09-15",
"returnDate": "2026-09-22",
"finalPrice": 28526.17,
"agencyCurrency": "DZD"
}
]
},
"suppliers": [ "...regular offers..." ]
}
```
---
# POST /api/v1/flights/get_booking
Source: /flights/get-booking
Retrieve full booking details by FX-Port booking ID, supplier order ID, or PNR.
**Permission:** Read
## Identifying the booking [#identifying-the-booking]
The request body has one field, `booking_id`. Its value can be any of these identifiers:
| `booking_id` value | Description |
| ---------------------------- | ----------------------------------------------------- |
| internal booking `id` (UUID) | **Recommended** — stable and unambiguous |
| supplier order ID | Amadeus/Duffel order identifier stored on the booking |
| PNR | Airline record locator — not globally unique |
Always send the value in `booking_id`. Prefer the internal UUID or supplier order ID. PNRs can
collide across suppliers/airlines.
## Getting the confirmation PDF [#getting-the-confirmation-pdf]
After a booking is confirmed, the PDF is generated asynchronously (typically 10–30 seconds).
Call `get_booking` and check `booking.bucket_link`.
If `bucket_link` is `null`, the PDF is still generating. Retry every 5–10 seconds.
Once `bucket_link` is populated, it contains the direct download URL to the PDF.
Alternatively, use `POST /api/v1/flights/get_ticket` which returns a `ticket_ready` boolean
and the URL once ready.
## Request example [#request-example]
```bash
curl --request POST \
--url https://api.fx-port.com/api/v1/flights/get_booking \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--header 'Content-Type: application/json' \
--data '{
"booking_id": "e133d887-fd3c-4a44-a19e-4f20ddb690d4"
}'
```
## Response example [#response-example]
```json
{
"success": true,
"requestId": "a1b2c3d4",
"booking": {
"id": "e133d887-fd3c-4a44-a19e-4f20ddb690d4",
"booking_id": "EDJP6K",
"status": "confirmed",
"ticket_number": "057-1234567890",
"total_price": 11107.9,
"agency_commission": 11.1,
"currency": "DZD",
"supplier_id": "duffel_1",
"supplier_name": "Duffel",
"departure_airport": "ALG",
"arrival_airport": "ORY",
"departure_date": "2026-09-25T08:30:00",
"paid_at": "2026-08-18T09:14:05Z",
"bucket_link": null,
"environment": "sandbox",
"pricingBreakdown": {
"supplier_currency": "USD",
"agency_currency": "DZD",
"base_price": 6170.0,
"taxes_and_fees": 4926.8,
"agency_commission": 11.1,
"final_price": 11107.9,
"b2b_price": 11096.8,
"created_at": "2026-08-18T09:14:05Z",
"environment": "sandbox"
},
"travelerPricing": [
{
"traveler_id": "1",
"traveler_type": "ADULT",
"fare_option": "STANDARD",
"supplier_currency": "USD",
"agency_currency": "DZD",
"base": 4800.0,
"total": 11107.9,
"b2b_price": 11096.8,
"environment": "sandbox"
}
],
"balanceOperations": []
}
}
```
## Key fields [#key-fields]
| Field | Description |
| --------------------------------- | --------------------------------------------------------------------------------- |
| `booking.id` | Internal UUID — **use this as primary key** when storing or re-fetching a booking |
| `booking.booking_id` | PNR / airline record locator. Not globally unique; prefer `id`. |
| `booking.bucket_link` | Confirmation PDF URL. `null` until generated — poll until populated. |
| `booking.ticket_number` | E-ticket number(s) |
| `booking.status` | `confirmed`, `awaiting_payment`, `cancelled`, etc. |
| `pricingBreakdown.b2b_price` | Amount debited from agency balance = `final_price − agency_commission` |
| `pricingBreakdown.taxes_and_fees` | Taxes + platform fees. Excludes `agency_commission`. |
---
# POST /api/v1/flights/get_order_quote
Source: /flights/get-order-quote
Get the current quote (price, status, and available actions) for a held order. This step is
**optional** — call it only when you want to preview or refresh the price before paying.
**Permission:** Read
## Identifying the booking [#identifying-the-booking]
Provide **one** of the following. `order_id` is strongly preferred.
| Field | Description |
| ------------ | ------------------------------------------------------------ |
| `order_id` | Supplier order ID returned when the hold booking was created |
| `booking_id` | FX-Port booking ID |
| `pnr` | Airline record locator |
**PNRs are not unique.** The same PNR can be issued by a different supplier, GDS, or airline.
Looking a booking up **by PNR alone can be ambiguous**. Always use `order_id` when possible.
## Request example [#request-example]
```bash
curl --request POST \
--url https://api.fx-port.com/api/v1/flights/get_order_quote \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--header 'Content-Type: application/json' \
--data '{
"order_id": "eJzTd9ePcDWJdHYCAAs9AlU"
}'
```
## Response example [#response-example]
```json
{
"success": true,
"requestId": "a1b2c3d4",
"orderId": "eJzTd9ePcDWJdHYCAAs9AlU",
"orderQuote": {
"orderId": "eJzTd9ePcDWJdHYCAAs9AlU",
"bookingReference": "XE4YCB",
"status": "awaiting_payment",
"pricing": {
"supplierCurrency": "DZD",
"agencyCurrency": "DZD",
"basePrice": 3160.00,
"taxesAndFees": 1921.44,
"agencyCommission": 5.09,
"finalPrice": 5086.53,
"b2bPrice": 5081.44,
"totalAmount": 5081.44
},
"paymentStatus": {
"awaitingPayment": true,
"paymentRequiredBy": null,
"priceGuaranteeExpiresAt": null,
"priceGuaranteed": true,
"canPay": true
},
"liveMode": false
},
"supplier": {
"id": "amadeus_aqc_dz_1",
"technology": "amadeus_aqc",
"name": "Amadeus AQC"
},
"meta": {
"timestamp": "2026-08-17T11:00:00Z",
"priceGuaranteed": true,
"canPay": true
}
}
```
## Field meanings [#field-meanings]
| Field | Meaning |
| ------------------------------- | ---------------------------------------------------------------------------------------------- |
| `pricing.b2bPrice` | **The amount that will be debited from your agency balance** = `finalPrice − agencyCommission` |
| `pricing.totalAmount` | Same as `b2bPrice` — alias for clarity |
| `paymentStatus.canPay` | Whether the hold can currently be paid. See warning below. |
| `paymentStatus.priceGuaranteed` | Whether the price is guaranteed not to change before `priceGuaranteeExpiresAt` |
**Do not treat `canPay` as a hard guarantee.** Payment can fail because the PNR has expired or
been cancelled, the hold/payment deadline has passed, or the airline released the reservation
before the expected date or time. The only authoritative outcome is the `pay_order` response.
Always handle payment failure gracefully even when `canPay` was `true` moments earlier.
---
# POST /api/v1/flights/get_ticket
Source: /flights/get-ticket
Returns the PDF download URL for a confirmed booking. The PDF is generated asynchronously after
booking — poll until `ticket_ready` is `true`.
**Permission:** Read
**Simpler alternative**: Call `get_booking` and read `booking.bucket_link` directly. It is
populated as soon as the PDF is ready (same data, one fewer endpoint).
## Recommended polling strategy [#recommended-polling-strategy]
1. Book flight → store the returned `order_id` (or PNR).
2. After \~10 seconds, call `get_ticket`.
3. If `ticket_ready: false`, retry every 10 seconds (up to \~2 minutes).
4. Once `ticket_ready: true`, present or download the PDF from `download_url`.
## Request body [#request-body]
| Field | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------ |
| `booking_id` | string | Yes | PNR or supplier order ID |
## Request example [#request-example]
```bash
curl --request POST \
--url https://api.fx-port.com/api/v1/flights/get_ticket \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--header 'Content-Type: application/json' \
--data '{
"booking_id": "XE4YCB"
}'
```
## Response — ticket not ready yet [#response--ticket-not-ready-yet]
```json
{
"success": true,
"requestId": "a1b2c3d4",
"ticket_ready": false,
"booking_id": "XE4YCB",
"message": "Ticket is not ready yet. Please retry in a moment."
}
```
## Response — ticket ready [#response--ticket-ready]
```json
{
"success": true,
"requestId": "a1b2c3d4",
"ticket_ready": true,
"booking_id": "XE4YCB",
"download_url": "https://s3.eu-central-003.backblazeb2.com/213carousat/flight-bookings/XE4YCB-JAMES-SMITH.pdf"
}
```
---
# Flights API overview
Source: /flights
Search, price, book, and manage flight orders across every enabled supplier from a single API
surface. One request fans out to all connected GDS, NDC, LCC, and direct-airline suppliers and
returns the best available fares.
**New suppliers are added automatically.** FX-Port continuously onboards more GDS connections,
NDC channels, low-cost carriers (LCC), direct airline deals, and additional PCC / office IDs.
Your existing integration **automatically benefits** from every new supplier and improved fare
— no code changes needed. As we expand, newer API versions will deliver richer content (more
fare families, fare origins, and ancillary types); those ship under a new version so your
current integration keeps working.
## Flight concepts [#flight-concepts]
Read these before integrating — they apply to every flight endpoint.
Instant issue, hold-then-pay, and how cancellation actually works.
Passenger types, age rules at departure, seat vs lap infants, capacity limits.
What each price field means and the b2bPrice formula.
Void, exchange, and refund — how to request them today.
## Endpoints [#endpoints]
| Method | Path | Permission | Description |
| ------ | --------------------------------- | -------------- | -------------------------------------------------------------- |
| `POST` | `/api/v1/get_flights` | Read | Search for flights |
| `POST` | `/api/v1/flights/flexible_search` | Read | Search with ±3-day flexible date matrix |
| `POST` | `/api/v1/flights/price_flight` | Read | Price a cached offer (required before booking) |
| `POST` | `/api/v1/flights/book_flight` | **Read+Write** | Create a booking (hold or instant issue) |
| `POST` | `/api/v1/flights/get_order_quote` | Read | Get current price/status for a held order |
| `POST` | `/api/v1/flights/pay_order` | **Read+Write** | Pay and issue tickets for a held order |
| `POST` | `/api/v1/flights/cancel_order` | **Read+Write** | Cancel a **held (unpaid)** reservation to release the PNR |
| `POST` | `/api/v1/flights/get_booking` | Read | Retrieve booking details (includes PDF link) |
| `POST` | `/api/v1/flights/get_ticket` | Read | Download ticket PDF |
| `GET` | `/api/v1/flights/airports` | Public | Search airports by name or IATA code |
## Important notes [#important-notes]
**Hold warning** — Holding a flight can prevent creating another booking for the same passenger
on the same airline/route with some GDS suppliers. If you no longer need a hold, cancel it
(`cancel_order`) to release the PNR before trying again. A displayed deadline is not a guarantee:
an airline can release the PNR early, and only a successful `pay_order` confirms issuance. See
[Cancelling a held reservation](/flights/booking-flows#cancelling-a-held-reservation).
**Fee accuracy** — Exchange fees, refund fees, and penalties returned in `conditions` are
indicative and may differ from what is actually charged. Communicate to travellers that a
ticket is "refundable for a fee" or "changeable for a fee" rather than quoting exact amounts.
## Error responses [#error-responses]
| HTTP | Cause |
| ----- | ---------------------------------------------------- |
| `401` | Missing or invalid API key |
| `403` | Key lacks write permission for this operation |
| `404` | Search ID, offer ID, or booking not found |
| `410` | Offer expired and is no longer available (re-search) |
| `503` | Supplier API temporarily unavailable |
## Questions? [#questions]
Email the FX-Port developer team at [dev@fx-port.com](mailto:dev@fx-port.com) for integration
help, feature requests, or to report an API error. For void, exchange, re-issue, or refund requests,
use **Request support** on the booking confirmation page in the FX-Port dashboard or email
[dev@fx-port.com](mailto:dev@fx-port.com).
---
# Flight passengers & ages
Source: /flights/passengers
These rules are **specific to flights**. Hotels and eSIMs have their own passenger/guest
requirements, documented under their own sections as those products launch.
**Ages are evaluated at the departure date of the first flight segment, not the booking date.**
A passenger who turns 12 on/before departure must be ticketed as an adult; an infant who turns 2
on/before departure must be ticketed as a child (and given a seat). The wrong type causes
check-in rejection and possible rebooking fees.
## Passenger types [#passenger-types]
| Search count field | Booking `type` | Age at departure | Seat | Age array (search) |
| ------------------ | --------------- | ---------------- | --------------------- | --------------------------------------------- |
| `adults` | `adult` | 18+ | own | — |
| `seniors` | `senior` | 60+ | own | — |
| `young_adults` | `young_adult` | 12–17 | own | `young_adult_ages` |
| `children` | `child` | 2–11 | own | `child_ages` **(required)** |
| `infants` | `infant` | under 2 | **lap** (on an adult) | `infant_ages` **(required)** |
| `seated_infants` | `seated_infant` | under 2 | **own seat** | `seated_infant_ages` **(required)** |
* **Lap infant** (`infants`): sits on an adult's lap, no seat, no separate ticket in most markets.
* **Seated infant** (`seated_infants`): occupies a full seat and needs a ticket. Use this when the
family wants a dedicated seat for an under-2, or when the infant will turn 2 before travel.
* **Child age 2+** (`children`): is a regular seated child. One adult may travel with multiple
children, and an age-2 child can travel alongside one lap infant.
## Search — `passengers` object [#search--passengers-object]
Pass counts plus a parallel `_ages` array for every type that requires one. Array length must
equal the count.
The following is a valid six-passenger request: two adults, two children, one lap infant, and one
seated infant.
```json
{
"adults": 2,
"seniors": 0,
"young_adults": 0,
"children": 2,
"child_ages": [10, 6],
"infants": 1,
"infant_ages": [1],
"seated_infants": 1,
"seated_infant_ages": [1]
}
```
| Array | Required when | Value range (years at departure) |
| -------------------- | -------------------- | -------------------------------- |
| `child_ages` | `children > 0` | 2–11 |
| `infant_ages` | `infants > 0` | 0–1 |
| `seated_infant_ages` | `seated_infants > 0` | 0–1 |
| `young_adult_ages` | `young_adults > 0` | 12–17 |
**Passenger limits can be supplier-specific.** Some suppliers require one distinct adult or
senior for every under-2 passenger across seated and lap infants. During automatic or
multi-supplier searches, FX-Port silently skips any incompatible supplier and returns results
from the compatible suppliers. HTTP `400` is returned only when no selected supplier can serve
the passenger mix. This restriction does not apply to normal children aged 2–11.
## Capacity constraints [#capacity-constraints]
| Rule | Limit |
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Maximum **seated** passengers | **9** (adults + seniors + young\_adults + children + seated\_infants) |
| Lap infants (`infants`) | Cannot exceed adults + seniors (each lap infant needs a lap) |
| Under-2 seated infants (`seated_infants`) | Cannot exceed adults + seniors; children aged 2–11 are unrestricted by this rule |
| Supplier-specific under-2 rule | Some suppliers require `seated_infants + infants <= adults + seniors` |
| Accompanying traveler | Standard online bookings should include at least **1 adult or senior**. Unaccompanied-minor availability is airline-specific. |
When no selected supplier is compatible, the API returns:
```json
{
"success": false,
"error": "SUPPLIER_PASSENGER_COMBINATION_UNSUPPORTED",
"message": "No available flight provider supports this passenger combination. Some providers require one adult or senior for each passenger under age 2.",
"limitation": {
"scope": "supplier_specific",
"code": "UNDER_TWO_HOST_RATIO",
"underTwoPassengers": 2,
"adultSeniorHosts": 1
}
}
```
## Common mistakes [#common-mistakes]
These are the most frequent errors that cause bookings to fail, get rejected at check-in, or
misprice.
```jsonc
// ❌ children count without ages → validation error / wrong price
{
"adults": 1,
"children": 2
}
// ✅
{
"adults": 1,
"children": 2,
"child_ages": [8, 5]
}
// ❌ infant turning 2 before departure booked as lap "infant"
// → airline rejects at check-in; they need their own seat
{
"adults": 1,
"infants": 1
}
// ✅ classify a passenger who is 2 at departure as a child
{
"adults": 1,
"children": 1,
"child_ages": [2]
}
// ❌ more lap infants than adults
{
"adults": 1,
"infants": 2,
"infant_ages": [0, 1]
}
// ❌ 10 seated passengers (exceeds the 9-seat limit)
{
"adults": 5,
"children": 5,
"child_ages": [4, 5, 6, 7, 8]
}
// ✅ split into two separate bookings (max 9 seated each)
```
## Booking — passenger object [#booking--passenger-object]
When building the `passengers` array for `book_flight`, each object must have:
| Field | Type | Required | Description |
| -------------------- | ------ | -------- | ----------------------------------------------------------------------- |
| `type` | string | Yes | `adult`, `senior`, `young_adult`, `child`, `infant`, or `seated_infant` |
| `gender` | string | Yes | `MALE` or `FEMALE` |
| `first_name` | string | Yes | Given name as on passport |
| `last_name` | string | Yes | Family name as on passport |
| `date_of_birth` | string | Yes | `YYYY-MM-DD` — used to verify age at departure |
| `identity_documents` | array | Yes | One passport per passenger |
**Names must use plain Latin (ASCII) letters only.** Airlines and GDS systems reject accents and
non-Latin scripts. Romanize before sending: `Jose` not `José`, `Muller` not `Müller`; transliterate
Arabic/Cyrillic/Chinese (`Mohammed`, `Ivanov`, `Wang`). Allowed: `A–Z`, `a–z`, spaces, hyphens.
This name rule applies to **all bookings** (flights, hotels, transfers, eSIMs) — the hotels and
eSIMs sections will restate it as those products launch.
### Identity document object [#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`, `EG`, `FR`) |
| `expiry_date` | string | Yes | `YYYY-MM-DD` |
| `nationality` | string | No | ISO 3166-1 alpha-2 |
Always supply a passport — it is accepted for both domestic and international travel.
---
# POST /api/v1/flights/pay_order
Source: /flights/pay-order
Pay for a held booking and issue the tickets. Charges the agency balance by `pricing.b2bPrice`.
**Permission:** Read+Write
The **charge amount and currency are resolved server-side** from the stored booking. Do **not**
send `amount` or `currency` — the endpoint rejects them.
## Identifying the booking [#identifying-the-booking]
| Field | Description |
| -------------- | ---------------------------------------------------------------- |
| `order_id` | Preferred. Supplier order ID returned when the hold was created. |
| `booking_id` | FX-Port booking ID |
| `pnr` | Airline record locator — not unique, prefer `order_id` |
| `payment_type` | Optional. `"balance"` (default), `"card"`, or `"arc_bsp_cash"` |
**PNRs are not globally unique** across suppliers/GDS/airlines. Prefer `order_id` to
unambiguously identify the booking you intend to pay for.
**Payment cannot revive an expired hold.** `pay_order` fails if the PNR has expired or was
cancelled, the payment deadline is in the past, or the airline released the hold early. Airlines
can expire a PNR before the expected date or time. Treat `paymentSucceeded` and
`paymentDetails.status` from this response as the source of truth; do not confirm the traveler or
charge them in your system until payment succeeds.
## Request example [#request-example]
```bash
curl --request POST \
--url https://api.fx-port.com/api/v1/flights/pay_order \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--header 'Content-Type: application/json' \
--data '{
"order_id": "eJzTd9ePcDWJdHYCAAs9AlU",
"payment_type": "balance"
}'
```
## Response example [#response-example]
```json
{
"environment": "sandbox",
"success": true,
"paymentSucceeded": true,
"requestId": "a1b2c3d4",
"orderId": "ord_0000B9UietU1vZwsABCD",
"supplierCurrency": "USD",
"agencyCurrency": "DZD",
"exchangeRate": 250.0,
"paymentDetails": {
"paymentId": null,
"status": "succeeded",
"type": "balance",
"paymentAmount": 11096.8,
"currency": "DZD",
"message": "Payment successful! Tickets have been issued."
},
"booking": {
"reference": "ord_0000B9UietU1vZwsABCD",
"status": "confirmed",
"pnr": "EDJP6K",
"type": "instant",
"createdAt": "2026-08-18T09:14:00Z",
"voidableUntil": null
},
"pricing": {
"supplierCurrency": "USD",
"agencyCurrency": "DZD",
"basePrice": 6170.0,
"taxesAndFees": 4926.8,
"agencyCommission": 11.1,
"finalPrice": 11107.9,
"b2bPrice": 11096.8,
"additionalServices": []
},
"bookingReferences": [
{
"pnr": "EDJP6K",
"carrier": {
"iataCode": "AF",
"name": "Air France"
}
}
],
"documents": [
{
"type": "electronic_ticket",
"documentType": "ETICKET",
"uniqueIdentifier": "057-1234567890",
"status": "ISSUED"
}
],
"meta": {
"supportHold": false,
"supportVoid": false,
"voidDeadline": null
},
"postProcessing": {
"bucketLink": null,
"emailSent": false
}
}
```
## Field meanings [#field-meanings]
| Field | Meaning |
| ------------------------------ | ------------------------------------------------------------------------------------------ |
| `paymentDetails.status` | `"succeeded"`, `"pending"`, or `"failed"` — the single source of truth for payment outcome |
| `paymentDetails.paymentAmount` | Amount actually debited from agency balance = `pricing.b2bPrice` |
| `paymentDetails.currency` | The agency currency of the debit |
| `booking.voidableUntil` | Void cutoff. Issued tickets can be voided until this UTC timestamp. |
| `pricing.b2bPrice` | Debit amount = `finalPrice − agencyCommission` |
Supplier cost and commission internals (net fare, kiosk/system commission) are **never** returned.
Only agency-facing amounts are exposed.
---
# Post-booking operations
Source: /flights/post-booking
**This page covers flight tickets only.** For **hotels**, **transfers**, **eSIMs**, and our other
products, post-booking operations — cancellation, refund where the rate allows, and booking
updates — are **fully supported through the API and the dashboard**. Only **issued flight
tickets** still require a human in the loop, because airlines and GDSs gate those authorisations.
**Post-booking operations on issued tickets are not yet self-serve.** Void, exchange, re-issue,
and refund on **paid/issued** bookings currently require **manual processing by the FX-Port
team**. This is *not* a slow email queue: every booking has a **Request support** action in the
dashboard, and our team answers **around the clock**. See
[Support and turnaround](#support-and-turnaround) below.
## Current status [#current-status]
| Operation | Availability today |
| -------------------------------------- | -------------------------------------------------------------------------------------- |
| **Cancel a held (unpaid) reservation** | ✅ Self-serve via [`cancel_order`](/flights/cancel-order) — releases the PNR, no refund |
| **Void an issued ticket** (same-day) | ✉️ Manual — email the FX-Port team |
| **Exchange / re-issue** | ✉️ Manual — email the FX-Port team |
| **Refund an issued ticket** | ✉️ Manual — email the FX-Port team |
There is **no self-serve refund for issued (booked & paid) tickets**, and refund eligibility is
**not guaranteed** — it varies by airline, fare rules, and supplier. Do not build a customer-facing
flow that promises automated refunds.
## Support and turnaround [#support-and-turnaround]
Manual does not mean slow. Post-booking requests are handled by a **dedicated team you can reach
directly**, not by an anonymous ticket portal:
* **Multiple channels** — in-dashboard **Request support** on every booking, email
[dev@fx-port.com](mailto:dev@fx-port.com), plus a **direct messaging channel** and phone contact
agreed with your account manager at onboarding.
* **Around the clock** — coverage runs **24/7, including weekends and holidays**, because
disruptions do not keep office hours.
* **A named contact** — integrated partners get a dedicated account and technical contact who
already knows your setup, so you never re-explain your integration.
* **Fast by design** — urgent cases such as **same-day voids** and irregular-operations rebookings
are prioritised and actioned immediately on receipt, well inside airline deadlines.
This is deliberately different from providers where post-booking means a web form and a multi-day
wait. If a request is ever not moving fast enough, escalate on the same thread — it reaches the
people who can act.
## How to request a post-booking operation [#how-to-request-a-post-booking-operation]
Use any support channel:
* Open the booking confirmation page in the FX-Port dashboard and select **Request support**.
* Email [dev@fx-port.com](mailto:dev@fx-port.com).
* Message your dedicated channel or account contact for anything time-critical.
Include:
1. The **order ID** (`booking.reference`) — preferred, or the internal booking **id** (UUID)
2. The **PNR** (for cross-reference)
3. The operation you need: **void**, **exchange/re-issue**, or **refund**
4. For exchanges: the new dates / itinerary requested
5. Any airline reference or context
The team will confirm eligibility, quote any fees, and process the operation manually.
## Other products: already fully self-serve [#other-products-already-fully-self-serve]
Flight tickets are the exception, not the rule. Across the rest of the platform, post-booking is
already available end to end:
| Product | Post-booking today |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| **Hotels** | ✅ Cancellation and refund handling per the rate's cancellation policy, plus booking retrieval and voucher re-issue |
| **Transfers** | ✅ Cancellation and booking updates |
| **eSIMs** | ✅ Top-ups, usage and status retrieval |
| **Flights (held, unpaid)** | ✅ Self-serve cancellation via [`cancel_order`](/flights/cancel-order) |
| **Flights (issued)** | ✉️ Assisted — void, exchange, refund via the support channels above |
## What's coming [#whats-coming]
**Closing this gap is a priority, and work is actively underway.** FX-Port is building self-serve
post-booking endpoints for flights and enabling them **per supplier** as airline and GDS
authorisations are granted — each new authorisation moves another slice of the table above from
✉️ to ✅. Releases are documented here and announced by email to API consumers. Because these are
significant additions, richer post-booking capabilities may ship under a **new API version** so
your current integration is never broken.
Have a priority use case? Submit it from the booking confirmation page or email
[dev@fx-port.com](mailto:dev@fx-port.com) — partner demand directly shapes which suppliers we
unlock first.
---
# POST /api/v1/flights/price_flight
Source: /flights/price
Price a cached offer to get the current, confirmed fare. This step is **required** before
calling `book_flight`. `book_flight` reads the priced offer from cache; booking without
pricing first will fail.
**Permission:** Read
Use the **supplier-level** `searchId` from `suppliers[n].searchId`, not the top-level `requestId`.
## Request body [#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 the search results |
## Request example [#request-example]
```bash
curl --request POST \
--url https://api.fx-port.com/api/v1/flights/price_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"
}'
```
## Response example [#response-example]
`exchangeRate` appears at the **top level** when supplier and agency currencies differ.
The `pricing` block never contains an exchange rate field.
```json
{
"success": true,
"environment": "sandbox",
"requestId": "a1b2c3d4",
"offerId": "fx_offer_a1b2c3d4",
"offerType": "flight_offer",
"supplierCurrency": "USD",
"agencyCurrency": "DZD",
"exchangeRate": 250.0,
"processingTime": 1.2,
"results": {
"offers": [
{
"id": "fx_offer_a1b2c3d4",
"type": "flight_offer",
"supplier": {
"id": "duffel_1",
"name": "Duffel"
},
"supportHold": false,
"supportVoid": false,
"voidDeadline": null,
"bookableSeats": 9,
"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",
"price": {
"supplierCurrency": "USD",
"agencyCurrency": "DZD",
"base": 4800.0,
"total": 11107.9,
"b2bPrice": 11096.8
}
}
],
"conditions": {
"refundBeforeDeparture": { "allowed": true },
"changeBeforeDeparture": { "allowed": true }
}
}
]
}
}
```
`supportHold` and `voidDeadline` are **inside the offer** in the `results.offers[0]` array, not
at the top level. Check them here before deciding whether to book as `hold` or `issue`.
If the offer expired between search and pricing, you get HTTP `410` with `"error": "offer_expired"`.
Re-run the search to get a fresh offer.
See [Flight pricing](/flights/pricing) for what each price field means.
---
# Flight pricing
Source: /flights/pricing
Every flight pricing block exposes the same set of agency-facing money fields.
## Field meanings [#field-meanings]
| Field | Meaning |
| ------------------ | --------------------------------------------------------------------------------------- |
| `basePrice` | Base fare set by the airline, **before taxes** (agency currency) |
| `taxesAndFees` | Taxes plus all platform-side fees, folded together. **Excludes** your agency commission |
| `agencyCommission` | Your own markup — what you charge the traveller on top |
| `finalPrice` | What the traveller pays = `basePrice + taxesAndFees + agencyCommission` |
| `b2bPrice` | **The amount debited from your agency balance** = `finalPrice − agencyCommission` |
| `totalAmount` | Alias of `b2bPrice` on endpoints that expose it — the amount debited |
## The formula [#the-formula]
```
finalPrice = basePrice + taxesAndFees + agencyCommission
b2bPrice = finalPrice − agencyCommission (= debited from your balance)
```
**Example** (Egyptian Pound, `EGP`):
```json
{
"supplierCurrency": "EGP",
"agencyCurrency": "EGP",
"basePrice": 4200.00,
"taxesAndFees": 1850.00,
"agencyCommission": 300.00,
"finalPrice": 6350.00,
"b2bPrice": 6050.00
}
```
Check: `4200 + 1850 + 300 = 6350` ✓ and `6350 − 300 = 6050` ✓
## Exchange rate [#exchange-rate]
When `supplierCurrency` differs from `agencyCurrency` (for example a supplier prices in `USD` and
your agency settles in `NGN`), an `exchangeRate` field appears at the **response top level** — not
inside the `pricing` block.
```json
{
"exchangeRate": 1620.0,
"pricing": {
"supplierCurrency": "USD",
"agencyCurrency": "NGN",
"basePrice": 291600.00,
"taxesAndFees": 145800.00,
"agencyCommission": 8100.00,
"finalPrice": 445500.00,
"b2bPrice": 437400.00
}
}
```
When both currencies are the same (e.g. `EUR → EUR`), no `exchangeRate` is included.
## Per-traveler pricing [#per-traveler-pricing]
Search, price, and booking responses also include a `travelerPricing` array with a per-traveler
`price` object (`base`, `total`, and `b2bPrice`), so you can display a breakdown per passenger.
The per-traveler `b2bPrice` is that traveller's share of the amount debited.
---
# Hotels API
Source: /hotels
**The Hotels API is live and being documented.** FX-Port already books hotels across global
suppliers (RateHawk / WorldOTA and more). Full public documentation — search, availability,
room-level pricing, pre-book, and booking — is being finalised and will appear here shortly.
**Check back in a few days.**
## What's coming [#whats-coming]
* **Search** by destination, geo-radius, or specific hotel ID
* **Room & rate availability** with per-room pricing and cancellation policies
* **Pre-book** and **book** with guest details
* **Booking lookup** and voucher / confirmation retrieval
Hotel guest naming follows the same **Latin/ASCII-only** rule as flights (no accents or non-Latin
scripts). Hotels have their own guest and occupancy requirements, which will be documented in this
section.
## Want early access? [#want-early-access]
If you're building a hotel integration and want early access to the docs or a walkthrough, email
[dev@fx-port.com](mailto:dev@fx-port.com).
---
# GET /api/v1/suppliers
Source: /suppliers
Returns all enabled suppliers grouped by product. This endpoint spans **all products** (flights,
hotels, eSIMs), not just flights. Disabled suppliers and unimplemented products are omitted.
**Permission:** Read
**The supplier list grows over time.** FX-Port continuously adds GDS connections, NDC channels,
low-cost carriers, direct-airline deals, and additional PCC / office IDs. Your integration picks
up new suppliers automatically — no code change needed. Treat this list as dynamic; do not
hard-code it.
## Request [#request]
```bash
curl --request GET \
--url https://api.fx-port.com/api/v1/suppliers \
--header 'Authorization: Bearer fxp_live_YOUR_KEY'
```
## Response example [#response-example]
```json
{
"environment": "live",
"success": true,
"products": [
{
"product": "flights",
"suppliers": [
{
"id": "amadeus_aqc_dz_1",
"name": "Amadeus Algeria",
"technology": "amadeus_aqc",
"routes": ["DZ", "*"],
"description": "Dedicated Algiers GDS office with negotiated fares and an advantageous DZD rate on outbound Algeria routes. Supports hold reservations and same-day void.",
"capabilities": {
"hold": true,
"void": true,
"flexible_dates": true,
"ancillaries": false
},
"voidDeadline": "2026-08-17T23:00:00Z"
},
{
"id": "amadeus_aqc_tn_1",
"name": "Amadeus Tunisia",
"technology": "amadeus_aqc",
"routes": ["TN", "*"],
"description": "Tunis GDS office with negotiated North-Africa and European fares priced in TND. Supports hold and same-day void within the Tunis ticketing day.",
"capabilities": {
"hold": true,
"void": true,
"flexible_dates": true,
"ancillaries": false
},
"voidDeadline": "2026-08-17T23:00:00Z"
},
{
"id": "ndc_eg_1",
"name": "NDC Egypt",
"technology": "ndc",
"routes": ["EG", "*"],
"description": "Direct NDC content for Egyptian carriers with richer fare families and continuous pricing. Fares in EGP. Ancillary bundles available on supported carriers.",
"capabilities": {
"hold": false,
"void": false,
"flexible_dates": false,
"ancillaries": true
}
},
{
"id": "travelport_uk_1",
"name": "TravelPort UK",
"technology": "travelport",
"routes": ["GB", "*"],
"description": "TravelPort-connected UK office with broad European and long-haul coverage at published fares. Competitive on UK-origin itineraries. Seat and bag ancillaries on supported carriers.",
"capabilities": {
"hold": false,
"void": false,
"flexible_dates": false,
"ancillaries": true
}
},
{
"id": "travelport_ae_1",
"name": "TravelPort UAE",
"technology": "travelport",
"routes": ["AE", "*"],
"description": "Dubai TravelPort office with strong Gulf, Indian-subcontinent, and long-haul coverage. Fares in AED. Ancillaries available on supported carriers.",
"capabilities": {
"hold": false,
"void": false,
"flexible_dates": false,
"ancillaries": true
}
},
{
"id": "sabre_us_1",
"name": "Sabre USA",
"technology": "sabre",
"routes": ["US", "*"],
"description": "Sabre-connected US office with deep North-American and transatlantic inventory at published fares. Broad carrier coverage; ancillaries on supported airlines.",
"capabilities": {
"hold": false,
"void": false,
"flexible_dates": false,
"ancillaries": true
}
},
{
"id": "duffel_1",
"name": "Duffel",
"technology": "duffel",
"routes": ["*"],
"description": "Aggregated global content across hundreds of airlines at published fares. Competitive on European, Middle-Eastern, and long-haul routes. Seat and bag ancillaries on supported carriers. Holds not supported.",
"capabilities": {
"hold": false,
"void": false,
"flexible_dates": false,
"ancillaries": true
}
}
]
},
{
"product": "hotels",
"suppliers": [
{
"id": "worldota_1",
"name": "RateHawk",
"technology": "worldota",
"description": "Global hotel inventory from chains and independents with real-time availability and net rates."
}
]
},
{
"product": "esims",
"suppliers": [
{
"id": "airalo_1",
"name": "Airalo",
"technology": "airalo",
"description": "eSIM data plans across 190+ countries, activated instantly with QR delivery by email."
}
]
}
],
"meta": {
"environment": "live",
"permissionRequired": "read"
}
}
```
## Field reference [#field-reference]
### Common supplier fields [#common-supplier-fields]
| Field | Type | Description |
| ------------- | ------ | ---------------------------------------------------------------------- |
| `id` | string | Supplier identifier — pass as `supplier_id` in search/booking requests |
| `name` | string | Human-readable supplier name |
| `technology` | string | Underlying integration technology (see below) |
| `description` | string | Supplier characteristics, strengths, and operational notes |
### Integration technologies [#integration-technologies]
The `technology` field indicates how FX-Port connects to the supplier. Current values include:
| Technology | Meaning |
| -------------- | ----------------------------------------------------------- |
| `amadeus_aqc` | Amadeus Quick Connect (dedicated office / negotiated fares) |
| `amadeus_soap` | Amadeus classic SOAP / Web Services |
| `travelport` | TravelPort (Galileo / Apollo / Worldspan) |
| `sabre` | Sabre GDS |
| `ndc` | Direct airline NDC content |
| `duffel` | Duffel aggregated content |
| `worldota` | WorldOTA / RateHawk (hotels) |
| `airalo` | Airalo (eSIMs) |
New technologies are added as connections come online — do not hard-code this list.
### Flight-specific fields [#flight-specific-fields]
| Field | Type | Description |
| ----------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------- |
| `routes` | array | Market scope. A country code (e.g. `"DZ"`, `"TN"`, `"GB"`) means that origin is prioritised; `"*"` means global coverage. |
| `capabilities.hold` | boolean | Supports hold (reserve now, pay later) |
| `capabilities.void` | boolean | Supports same-day void of issued tickets |
| `capabilities.flexible_dates` | boolean | Supports the ±3-day flexible date matrix |
| `capabilities.ancillaries` | boolean | Supports ancillary upsells (seats, extra bags) |
| `voidDeadline` | string | Next void cutoff (UTC) — next midnight in the supplier's timezone |
## The `voidDeadline` field [#the-voiddeadline-field]
`voidDeadline` is recalculated on every request — **do not cache it across days**. It is the next
midnight in the supplier's operating timezone, expressed in UTC. Void eligibility varies by
supplier and airline — check it immediately before attempting a void.
---
# Basic route examples
Source: /flights/search/basic-routes
## One-way economy [#one-way-economy]
```json
{
"origin": "ALG",
"destination": "ORY",
"departure_date": "2026-09-15",
"cabin_class": "economy",
"passengers": { "adults": 1 }
}
```
## Round-trip economy [#round-trip-economy]
```json
{
"origin": "CAI",
"destination": "DXB",
"departure_date": "2026-09-15",
"return_date": "2026-09-22",
"cabin_class": "economy",
"passengers": { "adults": 2 }
}
```
## Direct / non-stop only [#direct--non-stop-only]
`direct: true` and `nonstop: true` are aliases; both become a zero-stop supplier filter.
```json
{
"origin": "ALG",
"destination": "ORY",
"departure_date": "2026-09-15",
"cabin_class": "economy",
"passengers": { "adults": 1 },
"direct": true
}
```
## All enabled suppliers [#all-enabled-suppliers]
Omit every supplier selector. FX-Port evaluates compatibility and calls all matching enabled
suppliers in parallel. New suppliers automatically join this request as FX-Port enables them.
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"return_date": "2026-09-22",
"cabin_class": "economy",
"passengers": { "adults": 1 }
}
```
## cURL [#curl]
```bash
curl --request POST \
--url https://api.fx-port.com/api/v1/get_flights \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--header 'Content-Type: application/json' \
--data '{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"return_date": "2026-09-22",
"cabin_class": "economy",
"passengers": { "adults": 1 }
}'
```
---
# Open-jaw searches
Source: /flights/search/complex-itineraries
Use exactly two `segments` instead of `origin`, `destination`, `departure_date`, and `return_date`
for an open-jaw itinerary.
Multi-destination search is not currently listed in the public API documentation or Postman
collection. It will be documented in a future release.
Each segment requires:
| Field | Description |
| ------------- | ----------------------------------- |
| `origin` | Departure IATA airport code |
| `destination` | Arrival IATA airport code |
| `date` | Local departure date (`YYYY-MM-DD`) |
## Open jaw [#open-jaw]
Outbound arrives at one airport; return departs from another.
```json
{
"segments": [
{
"origin": "ALG",
"destination": "CDG",
"date": "2026-09-15"
},
{
"origin": "ORY",
"destination": "ALG",
"date": "2026-09-22"
}
],
"cabin_class": "economy",
"passengers": { "adults": 1 }
}
```
FX-Port identifies this as open-jaw because the second segment is not the exact reverse of the first.
## cURL [#curl]
```bash
curl --request POST \
--url https://api.fx-port.com/api/v1/get_flights \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--header 'Content-Type: application/json' \
--data '{
"segments": [
{ "origin": "ALG", "destination": "CDG", "date": "2026-09-15" },
{ "origin": "ORY", "destination": "ALG", "date": "2026-09-22" }
],
"cabin_class": "economy",
"passengers": { "adults": 1 }
}'
```
Each selected supplier receives both open-jaw segments.
---
# Filter examples
Source: /flights/search/filters
All filters are optional and can be combined when their semantics do not conflict.
## Filter reference [#filter-reference]
| Field | Type | Meaning |
| -------------------- | --------- | -------------------------------------------------------------------------------- |
| `direct` / `nonstop` | boolean | Non-stop offers only |
| `refundable` | boolean | Refundable fares only when supported |
| `checked_bags` | boolean | Offers including checked baggage |
| `max_price` | number | Maximum total in the agency currency |
| `included_airlines` | string\[] | Strict allow-list of IATA carrier codes |
| `excluded_airlines` | string\[] | Strict block-list of IATA carrier codes |
| `preferred_airlines` | string\[] | Ranking preference; does not strictly exclude others |
| `from_cc` | string | Origin market country code |
| `to_cc` | string | Destination market country code |
| `flexible` | boolean | Request flexible-date matrix from capable suppliers while keeping regular offers |
Do not send `included_airlines` and `excluded_airlines` together for the same search. Choose a
whitelist or a blacklist. Amadeus rejects supplier requests that contain both.
## Refundable only [#refundable-only]
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"cabin_class": "economy",
"passengers": { "adults": 1 },
"refundable": true
}
```
## Checked baggage only [#checked-baggage-only]
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"return_date": "2026-09-22",
"cabin_class": "economy",
"passengers": { "adults": 1 },
"checked_bags": true
}
```
## Price cap, market hints, preferred airlines [#price-cap-market-hints-preferred-airlines]
`max_price` uses the agency currency. `from_cc`/`to_cc` help select the most suitable supplier
offices. `preferred_airlines` influences ranking without acting as a strict whitelist.
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"cabin_class": "economy",
"passengers": { "adults": 1 },
"max_price": 50000,
"from_cc": "DZ",
"to_cc": "FR",
"preferred_airlines": ["AF", "AH"]
}
```
## Include airlines (strict allow-list) [#include-airlines-strict-allow-list]
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"cabin_class": "economy",
"passengers": { "adults": 1 },
"included_airlines": ["AF", "AH"]
}
```
For Amadeus, FX-Port sends a native include filter. Duffel's offer request does not expose a native
carrier include field, so FX-Port filters returned offers while preserving the unified response.
## Exclude airlines (strict block-list) [#exclude-airlines-strict-block-list]
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"cabin_class": "economy",
"passengers": { "adults": 1 },
"excluded_airlines": ["TK"]
}
```
## Flexible dates across all suppliers [#flexible-dates-across-all-suppliers]
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"return_date": "2026-09-22",
"cabin_class": "economy",
"passengers": { "adults": 1 },
"flexible": true
}
```
All compatible suppliers may return regular offers; only suppliers with flexible-date support add a
`flexPrices` matrix. Always check for matrix presence. Use the dedicated
[`flexible_search`](/flights/flexible-search) endpoint when you want **only** flexible-capable
suppliers.
---
# POST /api/v1/get_flights
Source: /flights/search
Search available flight offers across one, several, or every enabled supplier. Results are grouped
under `suppliers[]`; each supplier has its own `searchId` and offer list.
**Permission:** Read
**Save the supplier-level `searchId`.** Pass `suppliers[n].searchId` to `price_flight` and
`book_flight` — never the top-level `requestId`.
## Request fields [#request-fields]
### Route [#route]
| Field | Type | Required | Description |
| ---------------- | ------ | -------- | ---------------------------------------------------------- |
| `origin` | string | Yes¹ | Departure IATA code (`ALG`, `CAI`, `LHR`) |
| `destination` | string | Yes¹ | Arrival IATA code |
| `departure_date` | string | Yes¹ | `YYYY-MM-DD` |
| `return_date` | string | No | Add for a round trip |
| `segments` | array | No² | Exactly two open-jaw legs: `{ origin, destination, date }` |
¹ Simple one-way/round-trip only. ² Use exactly two
`segments`
for open-jaw.
### Passengers and cabin [#passengers-and-cabin]
| Field | Type | Required | Description |
| ------------- | ------ | -------- | -------------------------------------------------------------------------- |
| `passengers` | object | Yes | Counts and age arrays; see [Flight passengers & ages](/flights/passengers) |
| `cabin_class` | string | No | `economy` (default), `premium_economy`, `business`, `first`, `any` |
### Filters [#filters]
| Field | Type | Description |
| -------------------- | --------- | -------------------------------------------------------------------------- |
| `direct` | boolean | `true` → non-stop offers only (alias: `nonstop`) |
| `refundable` | boolean | `true` → refundable offers only when supported |
| `checked_bags` | boolean | `true` → offers that include checked baggage |
| `max_price` | number | Maximum total price in the agency currency |
| `included_airlines` | string\[] | Strict airline allow-list |
| `excluded_airlines` | string\[] | Strict airline block-list |
| `preferred_airlines` | string\[] | Prioritise these carriers without a strict allow-list |
| `from_cc` | string | Origin market, ISO 3166-1 alpha-2 |
| `to_cc` | string | Destination market, ISO 3166-1 alpha-2 |
| `flexible` | boolean | Request flexible dates from capable suppliers; regular offers still return |
### Supplier selection [#supplier-selection]
| Field | Type | Behaviour |
| ------------------ | --------- | ---------------------------------------------------------------------------- |
| *(omit selectors)* | — | **Recommended.** FX-Port selects compatible enabled suppliers automatically. |
| `supplier_id` | string | Advanced: restrict the search to one exact supplier ID. |
| `supplier_ids` | string\[] | Advanced: restrict the search to an exact supplier set. |
Usually, omit all supplier fields and let FX-Port manage selection. For the special case “use
exactly 2 of our 5 suppliers”, send `supplier_ids: ["supplier_a", "supplier_b"]`.
The backend calls enabled suppliers in parallel. Unknown, disabled, or flexible-incompatible
strict selections return HTTP 400. Traveler-incompatible suppliers are skipped silently; HTTP 400
is returned only when no selected supplier remains compatible.
## Examples by category [#examples-by-category]
One-way, round-trip, direct-only, and all suppliers.
Family mix, lap/seated infants, senior/young adult, business, premium economy, all cabins.
Refundable, checked bags, price cap, market hints, include/exclude/preferred airlines, flexible dates.
Automatic selection (recommended) or exact supplier IDs for special cases.
Search two non-reversed flight segments.
## Minimal request [#minimal-request]
```bash
curl --request POST \
--url https://api.fx-port.com/api/v1/get_flights \
--header 'Authorization: Bearer fxp_live_YOUR_KEY' \
--header 'Content-Type: application/json' \
--data '{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"cabin_class": "economy",
"passengers": { "adults": 1 }
}'
```
## Response essentials [#response-essentials]
```json
{
"success": true,
"suppliers": [
{
"success": true,
"searchId": "fs_9fa52bad-a7be-42aa-9ad6-d3f9fa642dc3",
"supportHold": true,
"results": {
"totalOffers": 12,
"offers": [
{
"id": "fx_offer_a1b2c3d4",
"pricing": {
"supplierCurrency": "DZD",
"agencyCurrency": "DZD",
"basePrice": 18800.0,
"taxesAndFees": 9726.17,
"agencyCommission": 0.0,
"finalPrice": 28526.17,
"b2bPrice": 28526.17
}
}
]
},
"meta": {
"supplierId": "amadeus_aqc_dz_1",
"supplierName": "Amadeus Algeria",
"processingTime": 1.32
}
}
]
}
```
## Save these values [#save-these-values]
| Value | Location | Used by |
| ------------- | ----------------------------------- | ----------------------------------------------- |
| `searchId` | `suppliers[n].searchId` | Required `price_flight.search_id`, then booking |
| offer `id` | `suppliers[n].results.offers[m].id` | Required `price_flight.offer_id`, then booking |
| `supportHold` | Supplier and priced offer | Use hold only when still `true` after pricing |
---
# Passenger and cabin examples
Source: /flights/search/passenger-and-cabin
See [Flight passengers & ages](/flights/passengers) for full validation rules. Search counts
and age arrays must match the passenger objects submitted later to `book_flight`.
## Family: adults, child, lap infant [#family-adults-child-lap-infant]
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"return_date": "2026-09-22",
"cabin_class": "economy",
"passengers": {
"adults": 2,
"children": 1,
"child_ages": [8],
"infants": 1,
"infant_ages": [1]
}
}
```
## Complete mix with seated infant [#complete-mix-with-seated-infant]
The public payload is supplier-neutral. Passenger limits can differ by supplier, so automatic and
multi-supplier searches silently skip incompatible suppliers and keep any compatible results. This
example includes two adults and is compatible with suppliers that require one adult/senior host per
under-2 passenger. A traveler who is truly age 2 is a regular child and does not use an infant host.
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"cabin_class": "economy",
"passengers": {
"adults": 2,
"seniors": 0,
"young_adults": 0,
"children": 2,
"child_ages": [10, 6],
"infants": 1,
"infant_ages": [1],
"seated_infants": 1,
"seated_infant_ages": [1]
},
"supplier_ids": ["amadeus_aqc_dz_1", "duffel_1"]
}
```
## Supplier-specific fallback: one adult with two infant types [#supplier-specific-fallback-one-adult-with-two-infant-types]
This payload is valid at the FX-Port API level. Do not force a supplier: FX-Port calls compatible
suppliers, silently skips incompatible ones, and returns available results without a warning. If
the route has no compatible supplier, the API returns
`SUPPLIER_PASSENGER_COMBINATION_UNSUPPORTED`.
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-29",
"cabin_class": "economy",
"passengers": {
"adults": 1,
"seated_infants": 1,
"seated_infant_ages": [0],
"infants": 1,
"infant_ages": [0]
},
"flexible": true
}
```
The same traveler who is already age 2 belongs in `children`, not `seated_infants`. Children aged
2–11 do not use an infant-host slot.
## Adult, senior, and young adult [#adult-senior-and-young-adult]
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"cabin_class": "economy",
"passengers": {
"adults": 1,
"seniors": 1,
"young_adults": 1,
"young_adult_ages": [16]
},
"supplier_id": "duffel_1"
}
```
FX-Port preserves requested logical categories in unified responses even when a supplier prices a
senior or young adult as an adult internally.
## Cabin classes [#cabin-classes]
```json
{
"origin": "DXB",
"destination": "BOM",
"departure_date": "2026-09-15",
"cabin_class": "business",
"passengers": { "adults": 1 }
}
```
```json
{
"origin": "LHR",
"destination": "JFK",
"departure_date": "2026-09-15",
"cabin_class": "premium_economy",
"passengers": { "adults": 1 }
}
```
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"cabin_class": "any",
"passengers": { "adults": 1 }
}
```
`any` sends no cabin restriction; offers can contain different cabin classes.
---
# Supplier selection
Source: /flights/search/suppliers
Most integrations should omit supplier selectors. FX-Port filters enabled suppliers by route,
traveler mix, and requested capabilities, then queries the compatible suppliers in parallel. Each
successful result remains isolated under `suppliers[]`.
**Amadeus suppliers are available in the sandbox** but may return errors intermittently due to
Amadeus test-environment maintenance — which is outside FX-Port's control. If an Amadeus request
fails in sandbox, retry or test on live. See [Sandbox notice](/sandbox) for details.
## Automatic selection (recommended) [#automatic-selection-recommended]
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"cabin_class": "economy",
"passengers": { "adults": 1 }
}
```
Do not send `supplier_id` or `supplier_ids`. FX-Port handles supplier priority, compatibility, and
new supplier activation without requiring client changes. If one supplier fails while another
returns offers, the successful results are returned without a compatibility warning.
## Exact supplier IDs (advanced) [#exact-supplier-ids-advanced]
Use exact IDs only for testing or a special commercial/operational requirement. Fetch current IDs
from [`GET /api/v1/suppliers`](/suppliers) instead of hard-coding the supplier catalog.
### One exact supplier [#one-exact-supplier]
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"cabin_class": "economy",
"passengers": { "adults": 1 },
"supplier_id": "duffel_1"
}
```
### Exact subset [#exact-subset]
If FX-Port has 5 enabled suppliers but you only want Amadeus Algeria and Duffel:
```json
{
"origin": "ALG",
"destination": "CDG",
"departure_date": "2026-09-15",
"cabin_class": "economy",
"passengers": { "adults": 1 },
"supplier_ids": ["amadeus_aqc_dz_1", "duffel_1"]
}
```
Strict selection never substitutes a supplier outside the requested set. Unknown, disabled, or
`flexible_only`-incompatible IDs return HTTP 400. A selected supplier can still be skipped when the
traveler mix is incompatible, and upstream availability can prevent it from returning offers. HTTP
400 is returned for traveler compatibility only when no selected supplier remains compatible.
## Strict-selection error [#strict-selection-error]
```json
{
"environment": "sandbox",
"detail": {
"error": "invalid_supplier_selection",
"message": "One or more requested suppliers cannot be used for this flight search.",
"invalidSuppliers": ["missing_1"],
"disabledSuppliers": [],
"unsupportedFlexibleSuppliers": [],
"availableSuppliers": ["amadeus_aqc_dz_1", "duffel_1"]
}
}
```
Omitting selectors avoids this integration maintenance and is therefore recommended.