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

API fundamentals

Conventions and naming

Request and response conventions, the canonical snake_case names, and the legacy camelCase names that still work and how to move off them.

The rules below hold for every endpoint of flights, hotels, eSIMs and the account. Learn them once.

Transport and format

Base URLhttps://api.fx-port.com/api/v1. One host for sandbox and live: the key decides (Authentication).
ProtocolHTTPS only. http:// is redirected with a 301.
BodiesJSON in UTF-8 with Content-Type: application/json. The one exception is the ACD document upload, which is multipart/form-data. A JSON body sent without that content type is not read as JSON.
MethodsGET for lookups, catalogues and the account; POST for searches, prices, bookings and everything that takes a body.
Trailing slashDo not add one. /esims/packages/ answers 307 with an empty body.
CompressionSend Accept-Encoding: gzip. Flight searches are 0.7 to 14 MB of JSON, ten times smaller compressed. Streams are never compressed.
CachingResponses are Cache-Control: private, no-store. Cache on your side what you can (Rate limits). The airport and destination typeaheads are the only cacheable answers.
BrowsersDo not call the API from a browser: CORS is enabled for FX-Port's own sites only, and a key in a browser is a leaked key.
User-AgentSend your own. Cloudflare blocks the default agents of Python's urllib and Perl's libwww-perl (details).
HealthGET https://api.fx-port.com/health and /health/ready need no key and are outside /api/v1.
Headers you can readX-Request-Id, X-Environment, ratelimit-limit, ratelimit-remaining, ratelimit-reset, ratelimit-policy, Retry-After, Deprecation, Link, X-FX-Naming, X-FX-Deprecated-Fields, X-FX-Ignored-Fields.
API host pagesThe interactive documentation of the API host is off. This site is the documentation, and the OpenAPI file is at /openapi.json.

Every JSON object answer starts with "environment": "sandbox" or "live", and every response carries X-Environment and X-Request-Id.

Naming

Names you send are snake_case. search_id, offer_id, booking_type, contact_details, phone_number, first_name, date_of_birth, cabin_class. The guides and the reference show these names and only these.

Over the years some endpoints took, or returned, other spellings: searchId, offerId, bookingType, contactDetails. Nothing was removed. Every previous spelling still works, and the API now tells you which one to use.

A previous spelling still works, and says so

When a request uses a previous spelling of a field, or a field the endpoint does not read, the answer carries a warnings array and headers that name what to use instead. The status code is not affected.

curl -s -X POST https://api.fx-port.com/api/v1/flights/price_flight \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"searchId": "fs_dfx_441e54ae-d7fe-414f-9fba-5415ee5ffb76", "offerId": "fx_offer_f3d89262"}'
HTTP/1.1 200 OK
Deprecation: true
Link: <https://fx-port.com/docs/conventions#naming>; rel="deprecation"
X-FX-Deprecated-Fields: searchId->search_id,offerId->offer_id
{
  "environment": "sandbox",
  "success": true,
  "warnings": [
    {
      "code": "deprecated_field",
      "field": "searchId",
      "replacement": "search_id",
      "message": "`searchId` is a previous spelling; use `search_id`. The old name keeps working.",
      "documentation_url": "https://fx-port.com/docs/conventions#naming"
    },
    { "code": "deprecated_field", "field": "offerId", "replacement": "offer_id", "message": "..." }
  ],
  "results": { "...": "..." }
}
Warning codeMeaningHeader
deprecated_fieldThe request used a previous spelling of a field. replacement is the name to use.Deprecation, X-FX-Deprecated-Fields
ignored_fieldThe request carried a field the endpoint does not read. It had no effect.X-FX-Ignored-Fields
coerced_valueA value was changed to a supported one; the message says how.none

If a field you meant to use shows up as ignored_field, that is usually a typo or a wrong spelling: the API never rejects unknown fields, so this is how you find out.

Responses keep their keys and add snake_case twins

Response keys that were camelCase stay exactly as they were. Next to the keys you feed back into later calls, or compute with, the response adds the snake_case twin, with the same value:

{
  "requestId": "11fa601e",
  "request_id": "11fa601e",
  "suppliers": [{ "searchId": "fs_dfx_441e54ae-d7fe-414f-9fba-5415ee5ffb76", "search_id": "fs_dfx_441e54ae-d7fe-414f-9fba-5415ee5ffb76" }]
}

Read the twin when it is there and fall back to the older key, so your code works with both:

search_id = supplier.get("search_id") or supplier["searchId"]

The reference marks every previous spelling as deprecated and names its replacement. The complete lists, generated from the API itself, are in Legacy names and twins. Machine readers can fetch them from GET /api/v1/conventions/naming (no key needed).

Get only snake_case

Add ?naming=snake to any request, or the header X-FX-Naming: snake, and every key of the answer is snake_case (requestId becomes request_id, b2bPrice becomes b2b_price). Values are never touched. The answer carries X-FX-Naming: snake. This is the simplest choice for a new integration written in a language that likes snake_case. Enumerated values keep the spelling documented for each endpoint.

One concept, one name

ConceptNameNotes
A flight searchsearch_idOf the supplier entry the offer came from. Needed by price and book.
A flight offeroffer_idThe id of the offer object.
A suppliersupplier_idFor example duffel_1. Only searches take it as input; price and book derive it from the offer.
A bookingbooking_idEndpoints that look a booking up accept the PNR, the supplier's order id or the FX-Port id.
The airline reservationpnrThe reference you give the traveler.
The supplier's orderorder_idNeeded to pay or cancel a held order.
A hotelhotel_idThe id of the hotel in the content, for example test_hotel_do_not_book.
A hotel ratebook_hashThe token of one rate, from get_rates to pre_book.
A hotel bookingbooking_idThe same value as the partner_order_id of pre_book and book_hotel: it identifies the booking when you poll it, fetch its voucher and cancel it.
An eSIMiccidThe id of the SIM card.
A requestrequest_idQuote it to support.

Identifiers

Ids are opaque strings. Do not parse them, do not assume a length, and do not treat one as a secret: a booking reference can be guessed or leaked, so check ownership in your own backend. The API only ever returns objects of your own agency: asking for the id of another agency is answered with 403 or 404, never with the data.

Money

  • Amounts are JSON numbers in the currency of your agency, with two decimals of precision (21679.66). Do not use floating point arithmetic for money: round to the currency's minor unit and compare in cents.
  • The supplier's own currency and the rate used are next to the prices (supplierCurrency, agencyCurrency, exchangeRate), so you can show or audit both.
  • Prices come in the same names across products: base_price (before taxes), taxes_and_fees, final_price (what the traveler pays), agency_commission (the commission your agency earns on it) and b2b_price (what your agency is debited: final_price minus agency_commission). eSIM catalogues expose the same idea as balance_debit_amount.
  • In the sandbox no balance is debited and GET /balances is empty.

Dates and times

  • Dates are YYYY-MM-DD and are the local date at the place they describe (a departure date is the date at the departure airport).
  • Timestamps are ISO 8601 in UTC, for example 2026-11-01T15:18:23Z. A few supplier-derived values come without a zone and are UTC too (the eSIM order and SIM created_at, usage expiry). The one exception is cached_at in the eSIM compatible-devices meta, which is in server local time and only tells you how old the cache is.
  • Flight times in itineraries are the local time of the airport and carry no zone.

Pagination

WhereParametersAnswer
Account history, balance historypage, limit (1 to 100)pagination: { page, limit, total, hasMore }
eSIM bookingspage, limit (1 to 200), search, allpagination: { total, page, limit, pages }
Flight and hotel searchesnoneEverything comes back in one answer. Narrow it with the filters of the endpoint.

Both pagination objects also carry total_pages and has_more, so you can read the same names everywhere. total is the real number of matching rows (for the account history it counts the rows the request can reach, up to a ceiling documented on its page).

Enumerations

Send the spellings the guides show (cabin_class: "economy", booking_type: "issue", type: "adult"). Where a value is not recognised the API answers 400 with a code, instead of silently replacing it by a default (older versions of the flight search did the latter). Responses use the spelling of each endpoint, which is documented with it.

Request ids

Every response carries an X-Request-Id header (the brief hand-over 503 during a deployment is the only exception), and every JSON body a request_id. Send your own X-Request-Id (8 to 64 characters of A-Z a-z 0-9 . _ -) and it is echoed back, which lets you join your logs with ours; otherwise one is generated (req_ and 12 hex characters). Quote it when you contact support.

Flight answers keep the 8-character requestId they always had, and their request_id repeats it. For every other endpoint, and for the meta.request_id of an error, the id is the one in the header. Either finds your request: send the header when you have it, and the body value when you only have the answer.

Safe retries

There is no Idempotency-Key header. Searches, prices and lookups can be repeated freely. Calls that create, pay or cancel must be repeated only after you have read the object back: see Retrying safely.

PreviousSandboxNext Legacy names and twins

On this page

  • Transport and format
  • Naming
  • A previous spelling still works, and says so
  • Responses keep their keys and add snake_case twins
  • Get only snake_case
  • One concept, one name
  • Identifiers
  • Money
  • Dates and times
  • Pagination
  • Enumerations
  • Request ids
  • Safe retries

© 2026 FX-Port

TermsPrivacy[email protected]