# Quickstart

> Get a sandbox key, search, price and book a flight, then check the booking, in about ten minutes.

Source: https://fx-port.com/docs/quickstart

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.


**Step 1: Create an account and a sandbox key**

Sign up at [fx-port.com/register](https://fx-port.com/register), open the [API page](https://fx-port.com/dashboard/api) 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:

~~~bash
export FXP_KEY="fxp_test_YOUR_KEY"
~~~

**Step 2: Check that the key works**

`GET /suppliers` 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.


**Request**

```bash
curl https://api.fx-port.com/api/v1/suppliers \
  -H "Authorization: Bearer fxp_test_YOUR_KEY"
```


~~~json
{
  "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.

**Step 3: 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.


**Python**

~~~python
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": "traveler@example.com", "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"])
~~~

**JavaScript**

~~~js
// 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: "traveler@example.com", 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);
~~~

**cURL**

~~~bash
# 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":"traveler@example.com","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:

~~~text
cheapest offer: fx_offer_a7df9202 21708.29 DZD
priced at: 21708.29
booked: 2LL5ZF confirmed
stored booking: confirmed 21708.29 DZD
~~~

Prices 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](https://fx-port.com/docs/conventions.md) for the rules every endpoint follows (names, ids, money, dates, pagination) and [Errors](https://fx-port.com/docs/errors.md) 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](https://fx-port.com/docs/hotels/sandbox-testing.md) has a complete, tested script, and [Hotel data: mini and full](https://fx-port.com/docs/hotels/hotel-data.md) 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:


**Request**

```bash
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](https://fx-port.com/docs/esims.md).

## 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](https://fx-port.com/docs/security.md) first, keep keys on your server, and check [Environments](https://fx-port.com/docs/environments.md) for what differs (live needs a funded balance, the sandbox does not).

## What to read next


- [Conventions and naming](https://fx-port.com/docs/conventions.md): Names, ids, money, dates and the rules every endpoint follows.
- [Errors](https://fx-port.com/docs/errors.md): The error body, every code and what to do about each kind.
- [Rate limits and fair use](https://fx-port.com/docs/rate-limits.md): The limits per minute and how to back off.
- [Streaming search results](https://fx-port.com/docs/streaming.md): First offers in about two seconds instead of waiting for the slowest supplier.
- [Flight booking flows](https://fx-port.com/docs/flights/booking-flows.md): Instant, hold and ACD flows with every status.
- [API reference](https://fx-port.com/docs/api.md): Every endpoint with examples in nine languages.
- [Use these docs with AI](https://fx-port.com/docs/ai-tools.md): llms.txt, OpenAPI and Markdown for assistants.
