Quickstart
Get a sandbox key, search, price and book a flight, then check the booking, in about ten minutes.
This guide takes about ten minutes. You will get a sandbox key, make a first call, then search, price and book a flight and read the booking back. Nothing here costs money: sandbox flight bookings are made on the suppliers' test systems, so no balance is debited and no real ticket exists.
Create an account and a sandbox key
Sign up at fx-port.com/register, open the API page of the dashboard and create a key. Choose Read + Write for this guide, because booking needs it. A sandbox key starts with
fxp_test_.Keep the key on your server, in an environment variable, never in front-end code:
export FXP_KEY="fxp_test_YOUR_KEY"Check that the key works
GET /is free and quick. It answers with the suppliers your key can reach, which proves the key, the environment and the network path in one call.suppliers curl https://api.fx-port.com/api/v1/suppliers \ -H "Authorization: Bearer fxp_test_YOUR_KEY"{ "environment": "sandbox", "success": true, "products": [ { "product": "flights", "suppliers": [ { "id": "amadeus_aqc_dz_1", "name": "Amadeus Algeria", "technology": "amadeus_aqc", "routes": ["DZ"], "capabilities": { "hold": true, "void": true, "flexible_dates": true, "ancillaries": false } }, { "id": "duffel_1", "name": "Duffel", "technology": "duffel", "routes": ["*"], "capabilities": { "hold": false, "void": false, "flexible_dates": false, "ancillaries": true } } ] } ] }"environment": "sandbox"is in every JSON answer. It tells you which environment your key is in, so you cannot confuse the two.Search, price, book and read the booking back
The program below does the whole flight flow. It searches Algiers to Paris 45 days ahead on Duffel, takes the cheapest offer, prices it, books it as an instant ticket and reads the booking back.
import os from datetime import date, timedelta import requests API = "https://api.fx-port.com/api/v1" HEADERS = {"Authorization": f"Bearer {os.environ['FXP_KEY']}", "Content-Type": "application/json"} def post(path, body): response = requests.post(API + path, headers=HEADERS, json=body, timeout=90) data = response.json() if not response.ok: raise SystemExit(f"{response.status_code} {data.get('errors') or data}") return data # 1. Search (Duffel works on every sandbox key; leave supplier_id out to search every supplier) search = post("/get_flights", { "origin": "ALG", "destination": "CDG", "departure_date": (date.today() + timedelta(days=45)).isoformat(), "cabin_class": "economy", "passengers": {"adults": 1}, "supplier_id": "duffel_1", }) supplier = search["suppliers"][0] offer = min(supplier["results"]["offers"], key=lambda o: o["pricing"]["finalPrice"]) search_id = supplier.get("search_id") or supplier["searchId"] print("cheapest offer:", offer["id"], offer["pricing"]["finalPrice"], offer["pricing"]["agencyCurrency"]) # 2. Price: confirms the fare with the supplier before you book priced = post("/flights/price_flight", {"search_id": search_id, "offer_id": offer["id"]}) print("priced at:", priced["results"]["offers"][0]["pricing"]["finalPrice"]) # 3. Book (sandbox bookings never debit money; send_email false keeps the traveler's inbox quiet) booking = post("/flights/book_flight", { "search_id": search_id, "offer_id": offer["id"], "booking_type": "issue", "send_email": False, "passengers": [{ "type": "adult", "gender": "MALE", "first_name": "John", "last_name": "Smith", "date_of_birth": "1988-06-15", "identity_documents": [{"type": "passport", "number": "A12345678", "issuing_country": "DZ", "expiry_date": "2031-04-18", "nationality": "DZ"}], }], "contact_details": {"email": "[email protected]", "phone_number": "+213554657687"}, }) pnr = booking["booking"]["pnr"] print("booked:", pnr, booking["booking"]["status"]) # 4. Read the booking back details = post("/flights/get_booking", {"booking_id": pnr}) print("stored booking:", details["booking"]["status"], details["booking"]["total_price"], details["booking"]["currency"])// Node 18 or later (fetch is built in); run it as an ES module: node quickstart.mjs const API = "https://api.fx-port.com/api/v1"; const headers = { Authorization: `Bearer ${process.env.FXP_KEY}`, "Content-Type": "application/json" }; async function post(path, body) { const response = await fetch(API + path, { method: "POST", headers, body: JSON.stringify(body) }); const data = await response.json(); if (!response.ok) throw new Error(`${response.status} ${JSON.stringify(data.errors ?? data)}`); return data; } // 1. Search (Duffel works on every sandbox key; leave supplier_id out to search every supplier) const departure = new Date(Date.now() + 45 * 86_400_000).toISOString().slice(0, 10); const search = await post("/get_flights", { origin: "ALG", destination: "CDG", departure_date: departure, cabin_class: "economy", passengers: { adults: 1 }, supplier_id: "duffel_1", }); const supplier = search.suppliers[0]; const offer = supplier.results.offers.reduce((best, o) => (o.pricing.finalPrice < best.pricing.finalPrice ? o : best)); const searchId = supplier.search_id ?? supplier.searchId; console.log("cheapest offer:", offer.id, offer.pricing.finalPrice, offer.pricing.agencyCurrency); // 2. Price: confirms the fare with the supplier before you book const priced = await post("/flights/price_flight", { search_id: searchId, offer_id: offer.id }); console.log("priced at:", priced.results.offers[0].pricing.finalPrice); // 3. Book (sandbox bookings never debit money; send_email false keeps the traveler's inbox quiet) const booking = await post("/flights/book_flight", { search_id: searchId, offer_id: offer.id, booking_type: "issue", send_email: false, passengers: [ { type: "adult", gender: "MALE", first_name: "John", last_name: "Smith", date_of_birth: "1988-06-15", identity_documents: [{ type: "passport", number: "A12345678", issuing_country: "DZ", expiry_date: "2031-04-18", nationality: "DZ" }], }, ], contact_details: { email: "[email protected]", phone_number: "+213554657687" }, }); const pnr = booking.booking.pnr; console.log("booked:", pnr, booking.booking.status); // 4. Read the booking back const details = await post("/flights/get_booking", { booking_id: pnr }); console.log("stored booking:", details.booking.status, details.booking.total_price, details.booking.currency);# 1. Search. Copy "searchId" of the supplier and the "id" of the offer you want from the answer. curl -s -X POST https://api.fx-port.com/api/v1/get_flights \ -H "Authorization: Bearer $FXP_KEY" -H "Content-Type: application/json" \ -d '{"origin":"ALG","destination":"CDG","departure_date":"2026-12-20","cabin_class":"economy","passengers":{"adults":1},"supplier_id":"duffel_1"}' # 2. Price curl -s -X POST https://api.fx-port.com/api/v1/flights/price_flight \ -H "Authorization: Bearer $FXP_KEY" -H "Content-Type: application/json" \ -d '{"search_id":"fs_dfx_...","offer_id":"fx_offer_..."}' # 3. Book curl -s -X POST https://api.fx-port.com/api/v1/flights/book_flight \ -H "Authorization: Bearer $FXP_KEY" -H "Content-Type: application/json" \ -d '{"search_id":"fs_dfx_...","offer_id":"fx_offer_...","booking_type":"issue","send_email":false, "passengers":[{"type":"adult","gender":"MALE","first_name":"John","last_name":"Smith","date_of_birth":"1988-06-15", "identity_documents":[{"type":"passport","number":"A12345678","issuing_country":"DZ","expiry_date":"2031-04-18","nationality":"DZ"}]}], "contact_details":{"email":"[email protected]","phone_number":"+213554657687"}}' # 4. Read the booking back (booking_id is the "pnr" of step 3) curl -s -X POST https://api.fx-port.com/api/v1/flights/get_booking \ -H "Authorization: Bearer $FXP_KEY" -H "Content-Type: application/json" \ -d '{"booking_id":"J2RRA3"}'Run it and you get something like:
cheapest offer: fx_offer_a7df9202 21708.29 DZD priced at: 21708.29 booked: 2LL5ZF confirmed stored booking: confirmed 21708.29 DZDPrices are in the currency of your agency (here DZD), whatever currency the supplier charges.
The identifiers that travel from call to call
| You get it from | Field | You send it to | As |
|---|---|---|---|
| Search | suppliers[].searchId (twin search_id) | price and book | search_id |
| Search | suppliers[].results.offers[].id (twin offer_id) | price and book | offer_id |
| Book | booking.pnr | get booking, tickets, cancel, ACD documents | booking_id |
| Book | booking.reference (the supplier's order id) | pay, cancel a hold | order_id |
A search is valid for a limited time and a fare can change or sell out, so always price right before you book. Read Conventions and naming for the rules every endpoint follows (names, ids, money, dates, pagination) and Errors for what to do when a call fails.
Hotels and eSIMs in the sandbox
Hotels. You can search and price any hotel in the sandbox, but you can only book the supplier's test hotels. Booking a random hotel does not work. Use test_hotel_do_not_book (any residency) or test_hotel (residency dz, ru or us) and cancel what you book. The hotel sandbox guide has a complete, tested script, and Hotel data: mini and full shows which hotel payload to use for a result list and which for a details page.
eSIMs. List the packages of a country, order one with the traveler's e-mail and phone, then top it up later:
curl 'https://api.fx-port.com/api/v1/esims/packages?country=france&limit=2' \
-H "Authorization: Bearer fxp_test_YOUR_KEY"Continue with the eSIM guide.
Going live
The code you wrote does not change. Swap the key for a fxp_live_ key and the same base URL answers with live data. From then on a Read + Write key issues real tickets and debits your agency balance, so read Security first, keep keys on your server, and check Environments for what differs (live needs a funded balance, the sandbox does not).