1. Home
  2. eSIM API

NewREST API with an OpenAPI 3.1 file

eSIM API for travel agencies, OTAs and resellers

Sell travel eSIMs through one REST API. List data packages by country with prices in your agency currency, order one for a traveler, give the traveler the QR code, follow usage and top up. One base URL serves sandbox and live. Begin with a free sandbox key; live keys work after your agency is approved.

Get free sandbox keysRead the eSIM guide
Base URL
https://api.fx-port.com/api/v1
Authentication
Authorization: Bearer fxp_test_...
Key prefixes
fxp_test_ / fxp_live_
Endpoints
12
Countries with packages
190+
Code sample languages
9

In this page

  • Console
  • Five steps
  • Prices
  • After the sale
  • Endpoints
  • Production
  • Sandbox
  • Who builds on it
  • FAQ
  • Related APIs
  • Get started

The eSIM API, call by call

Follow an eSIM sale step by step as each request is built, sent and answered. The sessions cover selling an eSIM, keeping a traveler connected, checking devices and bookings, and the failures worth handling. The requests and answers are examples from the documentation, and this page never calls the API.

3 requests: List the packages, Order the eSIM, Read the setup data.

Illustrative session: the bodies are examples from the documentation, dates are moved forward so they stay ahead of today, and times are illustrative. This page sends no request.

Download the Postman collection

Find a package, order it, read the setup data

Start with the packages of one country, order one for a traveler, then read the setup data of the order. The sandbox creates a test eSIM with a dummy activation code and debits nothing, and the console shows a trimmed copy of the catalog answer.

  1. List the packages

    The catalog call takes country and type and returns the matching packages, with prices in your agency currency. Keep the package_id of the one the traveler picks.

    Request GET /esims/packages
    curl 'https://api.fx-port.com/api/v1/esims/packages?country=france&type=local' \
      -H "Authorization: Bearer fxp_test_YOUR_KEY"
    Response 200 OK · 180 ms · 3.6 KB
    200 OK
    Content-Type: application/json
    Cache-Control: private, no-store
    X-Environment: sandbox
    ratelimit-limit: 6000
    ratelimit-policy: 6000;w=60
    
    {
      "environment": "sandbox",
      "success": true,
      "endpoint": "/packages",
      "data": [
        {
          "id": "france-in-7days-1gb",
          "package_id": "france-in-7days-1gb",
          "type": "sim",
          "price": 402.5,
          "base_price": 402.5,
          "amount": 1024,
          "day": 7,
          "is_unlimited": false,
          "title": "1 GB - 7 days",
          "is_fair_usage_policy": false,
          "fair_usage_policy": null,
          "data": "1 GB",
          "voice": null,
          "text": null,
          "country_name": "France",
          "country_code": "FR",
          "country_slug": "france",
          // ...
          "operator_name": "KPN France Data only",
          "operator_id": 1704,
          // ...
          "operator_type": "local",
          "operator_is_prepaid": false,
          "operator_esim_type": "Prepaid",
          "operator_warning": null,
          "operator_apn_type": "automatic",
          "operator_is_roaming": true,
          // ...
          "operator_plan_type": "data",
          "operator_activation_policy": "first-usage",
          "operator_is_kyc_verify": false,
          "operator_rechargeability": true,
          // ...
          // ... 1 more
          // ...
          "is_regional": false,
          // ... 10 more
        },
        // ...
      ],
      "meta": {
        "total_packages": 12,
        "filters_applied": {
          // ... 2 more
        },
        "locale": "en",
        "agency_currency": "DZD",
        "currency": "DZD",
        "supplier_currency": "USD",
        "exchange_rate": 250,
        "commission_percentage": "12 %",
        "commission_rate": 12
      }
    }

    Example trimmed for display

    • PASS Status code is 200
    • PASS $.success equals true
    • PASS $.data[0].package_id exists
    • PASS $.data[0].balance_debit_amount exists

    Reference of GET /esims/packages in the documentation

  2. Order the eSIM

    Creating an order takes the package_id with the traveler's email address and phone number. Keep booking_id and data.iccid from the answer. In the sandbox, balanceDebited, transactionCreated and payoutCreated are all false.

    Request POST /esims/orders
    curl https://api.fx-port.com/api/v1/esims/orders \
      -H "Authorization: Bearer fxp_test_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "package_id": "france-in-7days-1gb",
      "first_name": "John",
      "last_name": "Smith",
      "email": "[email protected]",
      "phone": "+15555550123",
      "description": "Order for John Smith"
    }'
    Response 200 OK · 900 ms · 1.5 KB
    200 OK
    Content-Type: application/json
    Cache-Control: private, no-store
    X-Environment: sandbox
    ratelimit-limit: 1200
    ratelimit-policy: 1200;w=60
    x-fx-limit-class: booking
    
    {
      "environment": "sandbox",
      "success": true,
      "data": {
        "order": {
          "code": "20261101-106852",
          "order_id": "20261101-106852",
          "package_id": "france-in-7days-1gb",
          "type": "sim",
          "description": "Order for John Smith",
          "esim_type": "local",
          "created_at": "2026-11-09 15:23:22",
          "package": "KPN France Data only-1 GB - 7 days",
          "data": "1 GB",
          "validity": 7,
          "price": 468.84,
          "currency": "DZD",
          "total_price": 468.84,
          "retail_price": 468.84,
          "agency_currency": "DZD",
          "supplier_currency": "USD",
          "exchange_rate": 250,
          "voice": null,
          "text": null,
          "coverage": ""
        },
        "sim": {
          "iccid": "8900000540463077957",
          "created_at": "2026-11-09 15:23:22",
          "lpa": "lpa.airalo.com",
          "imsis": null,
          "qrcode": "LPA:1$lpa.airalo.com$DUMMY-2610301358-iKFxr-44741",
          // ...
          "direct_apple_installation_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.airalo.com$DUMMY-2610301358-iKFxr-44741",
          "apn_type": "automatic",
          "apn_value": "N/A",
          "apn": {
            // ... 2 more
          },
          "is_roaming": true,
          "confirmation_code": null,
          "msisdn": null,
          "sharing": {
            "access_code": 8803,
            "link": "https://esims.cloud/v8hy7-usgkq6wg"
          }
        },
        "iccid": "8900000540463077957"
      },
      "booking_id": "FX-1793546602916",
      "message": "eSIM order placed successfully",
      "balanceDebited": false,
      "balance_debited": false,
      "transactionCreated": false,
      "transaction_created": false,
      "payoutCreated": false,
      "payout_created": false
    }

    Example trimmed for display

    • PASS Status code is 200
    • PASS $.environment equals sandbox
    • PASS $.balance_debited equals false
    • PASS $.data.iccid exists

    Reference of POST /esims/orders in the documentation

  3. Read the setup data

    The setup data call takes the booking_id and returns the QR code, the activation data, the install links and the installation steps of the order. Show them only to the traveler who owns the eSIM.

    Request GET /esims/bookings/{booking_id}/setup-instructions
    curl https://api.fx-port.com/api/v1/esims/bookings/FX-1793546602916/setup-instructions \
      -H "Authorization: Bearer fxp_test_YOUR_KEY"
    Response 200 OK · 210 ms · 1.8 KB
    200 OK
    Content-Type: application/json
    Cache-Control: private, no-store
    X-Environment: sandbox
    ratelimit-limit: 6000
    ratelimit-policy: 6000;w=60
    
    {
      "environment": "sandbox",
      "success": true,
      "data": {
        "code": "20261101-106852",
        "order_id": "20261101-106852",
        "currency": "DZD",
        "package_id": "france-in-7days-1gb",
        "quantity": 1,
        "type": "sim",
        "description": "Order for John Smith",
        "esim_type": "local",
        "validity": 7,
        "package": "KPN France Data only-1 GB - 7 days",
        "data": "1 GB",
        "price": 468.84,
        "created_at": "2026-11-09 15:23:22",
        // ...
        "installation_guides": {
          "en": "https://www.airalo.com/help/getting-started-with-airalo"
        },
        "text": null,
        "voice": null,
        "sims": [
          {
            "created_at": "2026-11-09 15:23:22",
            "iccid": "8900000540463077957",
            "lpa": "lpa.airalo.com",
            "imsis": null,
            "qrcode": "LPA:1$lpa.airalo.com$DUMMY-2610301358-iKFxr-44741",
            // ...
            "apn_type": "automatic",
            "apn_value": "N/A",
            "is_roaming": true,
            "confirmation_code": null,
            "apn": {
              "ios": {
                "apn_type": "automatic",
                "apn_value": "N/A"
              },
              "android": {
                "apn_type": "automatic",
                "apn_value": "N/A"
              }
            },
            "msisdn": null,
            "direct_apple_installation_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.airalo.com$DUMMY-2610301358-iKFxr-44741",
            "direct_android_installation_url": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.airalo.com$DUMMY-2610301358-iKFxr-44741",
            "sharing": {
              "access_code": 8803,
              "link": "https://esims.cloud/v8hy7-usgkq6wg"
            }
          }
        ],
        "total_price": 468.84,
        "retail_price": 468.84,
        "agency_currency": "DZD"
      }
    }

    Example trimmed for display

    • PASS Status code is 200
    • PASS $.data.package_id equals france-in-7days-1gb
    • PASS $.data.sims[0].iccid exists

    Reference of GET /esims/bookings/{booking_id}/setup-instructions in the documentation

Read the usage, then top up

Once a traveler has an eSIM, read how much data is left, ask which packages can be added to that exact eSIM, then create the top-up. Usage is cached, so a figure can be minutes old. The top-up history is in the sidebar and in the transcript.

  1. Read the usage

    The usage call takes the ICCID of the order and returns remaining and total in megabytes, the status and the expiry. The answer comes from a cache, and force=true asks the supplier again.

    Request GET /esims/usage/{iccid}
    curl https://api.fx-port.com/api/v1/esims/usage/8900000540463077957 \
      -H "Authorization: Bearer fxp_test_YOUR_KEY"
    Response 200 OK · 160 ms · 261 B
    200 OK
    
    {
      "environment": "sandbox",
      "success": true,
      "data": {
        "data": {
          "remaining": 1024,
          "total": 1024,
          "expired_at": "2026-11-16 15:23:29",
          "is_unlimited": false,
          "status": "ACTIVE",
          "remaining_voice": 0,
          "remaining_text": 0,
          "total_voice": 0,
          "total_text": 0
        },
        "meta": {
          "message": "success"
        }
      }
    }
    • PASS Status code is 200
    • PASS $.data.data.status equals ACTIVE
    • PASS $.data.data.remaining exists

    Reference of GET /esims/usage/{iccid} in the documentation

  2. List top-up packages

    This call lists the packages that can be added to this exact eSIM today. An empty list means the eSIM cannot be topped up now. Take the package_id of the top-up from this list, never from the original order.

    Request GET /esims/topup-packages/{iccid}
    curl https://api.fx-port.com/api/v1/esims/topup-packages/8900000540463077957 \
      -H "Authorization: Bearer fxp_test_YOUR_KEY"
    Response 200 OK · 240 ms · 1.1 KB
    200 OK
    
    {
      "environment": "sandbox",
      "success": true,
      "data": {
        "data": [
          {
            "id": "france-in-7days-1gb-topup",
            "package_id": "france-in-7days-1gb-topup",
            "type": "topup",
            "price": 468.84,
            "amount": 1024,
            "day": 7,
            "is_unlimited": false,
            "title": "1 GB - 7 days",
            "data": "1 GB",
            "short_info": null,
            // ... 12 more
          },
          {
            // ... 22 more
          }
        ]
      }
    }

    Example trimmed for display

    • PASS Status code is 200
    • PASS $.data.data has 2 items
    • PASS $.data.data[0].type equals topup

    Reference of GET /esims/topup-packages/{iccid} in the documentation

  3. Create the top-up

    Creating a top-up takes the package_id from that list and the ICCID. On live, your balance is debited only after the supplier accepts it. In the sandbox the balance in the answer is zero and nothing is debited.

    Request POST /esims/topups
    curl https://api.fx-port.com/api/v1/esims/topups \
      -H "Authorization: Bearer fxp_test_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "package_id": "france-in-7days-1gb-topup",
      "iccid": "8900000540463077957",
      "first_name": "John",
      "last_name": "Smith",
      "email": "[email protected]",
      "phone": "+15555550123"
    }'
    Response 200 OK · 900 ms · 682 B
    200 OK
    
    {
      "environment": "sandbox",
      "success": true,
      "data": {
        "topup": {
          "code": "20261101-106853",
          "order_id": "20261101-106853",
          "package_id": "france-in-7days-1gb-topup",
          // ... 10 more
        },
        "iccid": "8900000540463077957",
        "balance": {
          // ... 3 more
        }
      },
      "booking_id": "FX-1793546625391",
      "message": "eSIM topup completed successfully",
      "balanceDebited": false,
      "balance_debited": false,
      "transactionCreated": false,
      "transaction_created": false,
      "payoutCreated": false,
      "payout_created": false
    }
    • PASS Status code is 200
    • PASS $.environment equals sandbox
    • PASS $.balance_debited equals false
    • PASS $.data.balance.current equals 0

    Reference of POST /esims/topups in the documentation

  4. Read the top-up history

    The top-up history lists the top-ups of one eSIM, newest first. Top-ups are not in the booking list, so this is where you find them.

    Request GET /esims/bookings/{iccid}/topup-history
    curl https://api.fx-port.com/api/v1/esims/bookings/8900000540463077957/topup-history \
      -H "Authorization: Bearer fxp_test_YOUR_KEY"
    Response 200 OK · 130 ms · 615 B
    200 OK
    
    {
      "environment": "sandbox",
      "success": true,
      "data": [
        {
          "id": "bd6ff7d7-392f-4d56-8dea-4f0f3dff0af9",
          "booking_id": "FX-1793546625391",
          "order_id": "20261101-106853",
          "status": "confirmed",
          "currency": "DZD",
          "total_price": 468.84,
          "agency_commission": 50.24,
          "type": "topup",
          "package_name": "1 GB - 7 days",
          "iccid": "8900000540463077957",
          "customer_email": "[email protected]",
          "customer_first_name": "John",
          "customer_last_name": "Smith",
          "customer_phone": "+15555550123",
          "provider_name": "Airalo",
          // ... 4 more
        }
      ]
    }
    • PASS Status code is 200
    • PASS $.data[0].type equals topup

    Reference of GET /esims/bookings/{iccid}/topup-history in the documentation

Check a device, then read your bookings

Before the sale, check that the traveler's phone supports eSIM. After the sale, read your bookings and check that a booking reference is yours. Bulk usage is listed in the sidebar.

  1. Check the device list

    The compatible devices call returns the devices known to support eSIM, with brand, model and os. Look the traveler's phone up before you sell.

    Request GET /esims/compatible-devices
    curl https://api.fx-port.com/api/v1/esims/compatible-devices \
      -H "Authorization: Bearer fxp_test_YOUR_KEY"
    Response 200 OK · 120 ms · 283 B
    200 OK
    
    {
      "environment": "sandbox",
      "success": true,
      "data": [
        {
          "model": "T803D",
          "os": "android",
          "brand": "Alcatel",
          "name": "V3 Ultra"
        },
        {
          "model": "iPhone15,2",
          "os": "ios",
          "brand": "Apple",
          "name": "iPhone 14 Pro"
        }
      ],
      "meta": {
        "total_devices": 631,
        "cached_at": "2026-11-09T15:07:43.957236",
        "cache_age_days": 0.01
      }
    }
    • PASS Status code is 200
    • PASS $.success equals true
    • PASS $.data[0].model exists

    Reference of GET /esims/compatible-devices in the documentation

  2. List your bookings

    The booking list returns the eSIM orders of your agency, newest first. This request asks for one booking with limit, and pagination says how many orders match and whether another page follows.

    Request GET /esims/bookings
    curl 'https://api.fx-port.com/api/v1/esims/bookings?limit=1' \
      -H "Authorization: Bearer fxp_test_YOUR_KEY"
    Response 200 OK · 150 ms · 714 B
    200 OK
    
    {
      "environment": "sandbox",
      "success": true,
      "data": [
        {
          "id": "639417b4-b103-4ece-bd5d-850315322278",
          "booking_id": "FX-1793546602916",
          "order_id": "20261101-106852",
          "status": "confirmed",
          "currency": "DZD",
          // ... 4 more
          "iccid": "8900000540463077957",
          // ... 9 more
        }
      ],
      "pagination": {
        "total": 61,
        "page": 1,
        "limit": 1,
        "pages": 61,
        "total_pages": 61,
        "has_more": true
      }
    }
    • PASS Status code is 200
    • PASS $.data has 1 item
    • PASS $.pagination.has_more equals true

    Reference of GET /esims/bookings in the documentation

  3. Check ownership

    The ownership check answers is_owner true when the booking belongs to your agency. A booking of another agency and one that does not exist get the same answer, so the call cannot be used to discover references.

    Request GET /esims/bookings/{booking_id}/verify
    curl https://api.fx-port.com/api/v1/esims/bookings/FX-1793546602916/verify \
      -H "Authorization: Bearer fxp_test_YOUR_KEY"
    Response 200 OK · 90 ms · 167 B
    200 OK
    
    {
      "environment": "sandbox",
      "isOwner": true,
      "is_owner": true,
      "userAgencyId": "0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
      "bookingAgencyId": "0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d"
    }
    • PASS Status code is 200
    • PASS $.is_owner equals true

    Reference of GET /esims/bookings/{booking_id}/verify in the documentation

  4. Read usage in bulk

    One call reads the usage of several eSIMs listed in iccids and returns one entry per ICCID, each with its own status. Always send iccids.

    Request GET /esims/usage/bulk
    curl 'https://api.fx-port.com/api/v1/esims/usage/bulk?iccids=8900000540463077957' \
      -H "Authorization: Bearer fxp_test_YOUR_KEY"
    Response 200 OK · 220 ms · 321 B
    200 OK
    
    {
      "environment": "sandbox",
      "success": true,
      "data": [
        {
          "iccid": "8900000540463077957",
          "data": {
            "data": {
              "remaining": 1024,
              "total": 1024,
              "expired_at": "2026-11-16 15:23:29",
              "is_unlimited": false,
              "status": "ACTIVE",
              "remaining_voice": 0,
              // ... 3 more
            },
            "meta": {
              "message": "success"
            }
          },
          "status": "success"
        }
      ]
    }
    • PASS Status code is 200
    • PASS $.data[0].status equals success

    Reference of GET /esims/usage/bulk in the documentation

Failures and how to react to each

All failures share one body, and the first entry of errors has a stable code. These order calls fail on purpose, each for a different reason and with a different reaction.

  1. Read key on an order

    A Read key on the order call is refused with 403 and read_write_permission_required, before the body is read. Switch to a Read+Write key.

    Request POST /esims/orders
    curl https://api.fx-port.com/api/v1/esims/orders \
      -H "Authorization: Bearer fxp_test_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "package_id": "france-in-7days-1gb",
      "first_name": "John",
      "last_name": "Smith",
      "email": "[email protected]",
      "phone": "+15555550123",
      "description": "Order for John Smith"
    }'
    Response 403 Forbidden · 90 ms · 456 B
    403 Forbidden
    
    {
      "environment": "sandbox",
      "success": false,
      "errors": [
        {
          "code": "read_write_permission_required",
          "type": "permission_error",
          "title": "Read write permission required",
          "message": "This endpoint requires an API key with read_write permission.",
          "field": null,
          "details": {},
          "documentation_url": "https://fx-port.com/docs/errors#read_write_permission_required"
        }
      ],
      "meta": {
        "request_id": "req_7f0c2e9a41b84d6e8a35c1d2",
        "status": 403
      },
      "request_id": "req_7f0c2e9a41b84d6e8a35c1d2"
    }
    • PASS Status code is 403
    • PASS Error code is read_write_permission_required
    • PASS $.errors[0].type equals permission_error

    Reference of POST /esims/orders in the documentation

  2. Invalid email address

    A value that is not an email address is refused with 400 and invalid_email, and field names email. Fix the value before you send the order again: the same request fails the same way.

    Request POST /esims/orders
    curl https://api.fx-port.com/api/v1/esims/orders \
      -H "Authorization: Bearer fxp_test_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "package_id": "france-in-7days-1gb",
      "first_name": "John",
      "last_name": "Smith",
      "email": "traveler-at-example.com",
      "phone": "+15555550123",
      "description": "Order for John Smith"
    }'
    Response 400 Bad Request · 100 ms · 381 B
    400 Bad Request
    
    {
      "environment": "sandbox",
      "success": false,
      "errors": [
        {
          "code": "invalid_email",
          "type": "validation_error",
          "title": "Invalid email",
          "message": "email is not a valid email address",
          "field": "email",
          "details": {},
          "documentation_url": "https://fx-port.com/docs/errors#invalid_email"
        }
      ],
      "meta": {
        "request_id": "req_7f0c2e9a41b84d6e8a35c1d2",
        "status": 400
      },
      "request_id": "req_7f0c2e9a41b84d6e8a35c1d2"
    }
    • PASS Status code is 400
    • PASS Error code is invalid_email
    • PASS $.errors[0].field equals email

    Reference of POST /esims/orders in the documentation

  3. Duplicate order

    An order for a package that is already being processed is refused with 409 and duplicate_request. Do not send it again at once: look the booking up first, and order again only if none was created.

    Request POST /esims/orders
    curl https://api.fx-port.com/api/v1/esims/orders \
      -H "Authorization: Bearer fxp_test_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "package_id": "france-in-7days-1gb",
      "first_name": "John",
      "last_name": "Smith",
      "email": "[email protected]",
      "phone": "+15555550123",
      "description": "Order for John Smith"
    }'
    Response 409 Conflict · 110 ms · 423 B
    409 Conflict
    
    {
      "environment": "sandbox",
      "success": false,
      "errors": [
        {
          "code": "duplicate_request",
          "type": "conflict",
          "title": "Duplicate request",
          "message": "An identical order is already being processed. Please wait before retrying.",
          "field": null,
          "details": {},
          "documentation_url": "https://fx-port.com/docs/errors#duplicate_request"
        }
      ],
      "meta": {
        "request_id": "req_7f0c2e9a41b84d6e8a35c1d2",
        "status": 409
      },
      "request_id": "req_7f0c2e9a41b84d6e8a35c1d2"
    }
    • PASS Status code is 409
    • PASS Error code is duplicate_request
    • PASS $.errors[0].type equals conflict

    Reference of POST /esims/orders in the documentation

  4. Too many requests

    Over the per-minute limit, the call is refused with 429 and rate_limit_exceeded, and the Retry-After header says how many seconds to wait. Then retry with backoff.

    Request POST /esims/orders
    curl https://api.fx-port.com/api/v1/esims/orders \
      -H "Authorization: Bearer fxp_test_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "package_id": "france-in-7days-1gb",
      "first_name": "John",
      "last_name": "Smith",
      "email": "[email protected]",
      "phone": "+15555550123",
      "description": "Order for John Smith"
    }'
    Response 429 Too Many Requests · 80 ms · 504 B
    429 Too Many Requests
    Retry-After: 12
    
    {
      "environment": "sandbox",
      "success": false,
      "errors": [
        {
          "code": "rate_limit_exceeded",
          "type": "rate_limit_error",
          "title": "Rate limit exceeded",
          "message": "Too many requests hit the API too quickly. Please retry your request after the time specified in the `ratelimit-reset` header.",
          "field": null,
          "details": {
            "retry_after": 12
          },
          "documentation_url": "https://fx-port.com/docs/errors#rate_limit_exceeded"
        }
      ],
      "meta": {
        "request_id": "req_7f0c2e9a41b84d6e8a35c1d2",
        "status": 429
      },
      "request_id": "req_7f0c2e9a41b84d6e8a35c1d2"
    }
    • PASS Status code is 429
    • PASS Error code is rate_limit_exceeded
    • PASS Header Retry-After is present
    • PASS $.errors[0].details.retry_after equals 12

    Reference of POST /esims/orders in the documentation

From catalog to connected traveler in five steps

An integration is a short path: read the catalog, create the order, give the traveler the eSIM, follow the usage and top up when the data runs out. Only the order and the top-up change anything. Every other call only reads.

Read the catalog for one destination

List the packages of one country, with prices already in your agency currency, and keep the package_id of the one the traveler picks. Always send country: the whole catalog is a very large answer without pagination. Read package ids from the answer instead of hard-coding them, because the catalog changes.

  • GET /api/v1/esims/packages

Request

curl 'https://api.fx-port.com/api/v1/esims/packages?country=france&type=local' \
  -H "Authorization: Bearer fxp_test_YOUR_KEY"

Response 200 OK

{
  "environment": "sandbox",
  "success": true,
  "endpoint": "/packages",
  "data": [
    {
      "id": "france-in-7days-1gb",
      "package_id": "france-in-7days-1gb",
      "type": "sim",
      "price": 402.5,
      "base_price": 402.5,
      // ... 28 more
      "total_price": 468.84,
      "balance_debit_amount": 418.6,
      "b2b_price": 418.6,
      "agency_commission": "12 %",
      "commission_rate": 12,
      "currency": "DZD",
      // ... 2 more
    },
    // ...
  ],
  "meta": {
    "total_packages": 12,
    "filters_applied": {
      // ... 2 more
    },
    "locale": "en",
    "agency_currency": "DZD",
    "currency": "DZD",
    "supplier_currency": "USD",
    "exchange_rate": 250,
    "commission_percentage": "12 %",
    "commission_rate": 12
  }
}

Package catalog by country

Create the order

Send the package_id with the traveler's email address and phone number. One order creates exactly one eSIM, and on live it debits your agency balance. Keep the booking_id and the ICCID from the answer, because every later call needs one of them.

  • POST /api/v1/esims/orders

Request

curl https://api.fx-port.com/api/v1/esims/orders \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "package_id": "france-in-7days-1gb",
  "first_name": "John",
  "last_name": "Smith",
  "email": "[email protected]",
  "phone": "+15555550123",
  "description": "Order for John Smith"
}'

Response 200 OK

{
  "environment": "sandbox",
  "success": true,
  "data": {
    "order": {
      "code": "20261101-106852",
      "order_id": "20261101-106852",
      "package_id": "france-in-7days-1gb",
      // ... 17 more
    },
    "sim": {
      // ... 4 more
      "qrcode": "LPA:1$lpa.airalo.com$DUMMY-2610301358-iKFxr-44741",
      // ... 7 more
      "sharing": {
        "access_code": 8803,
        "link": "https://esims.cloud/v8hy7-usgkq6wg"
      }
    },
    "iccid": "8900000540463077957"
  },
  "booking_id": "FX-1793546602916",
  "message": "eSIM order placed successfully",
  "balanceDebited": false,
  "balance_debited": false,
  "transactionCreated": false,
  "transaction_created": false,
  "payoutCreated": false,
  "payout_created": false
}
What the order answer carries
FieldWhat it holds
booking_id The FX-Port reference of the order. Keep it for the setup data, the ownership check and your own records.
data.iccid The identifier of the eSIM. Usage, top-ups and installation instructions are all asked by ICCID.
data.sim.qrcode data.sim.qrcode_url The activation string that the QR code encodes, and a link to the QR code image.
data.sim.direct_apple_installation_url data.sim.sharing A link that starts the installation on an iPhone, and a sharing page where the traveler can open the eSIM with its access code. The setup data call adds the link for Android.
The activation data is sensitive: show it only to the traveler who owns the eSIM.
If the supplier has not returned the eSIM yet, the order still exists: the ICCID starts with PENDING-, and the data is available from the setup data call once the supplier has it.

Create an eSIM order

Give the traveler the eSIM

The answer carries the QR code and its activation string, so you can show them in your own screens. FX-Port also asks the supplier to email the QR code and a sharing link to the traveler's address, so you do not have to relay it. The setup data call returns the same data again later, with the installation steps.

  • GET /api/v1/esims/bookings/{booking_id}/setup-instructions

Request

curl https://api.fx-port.com/api/v1/esims/bookings/FX-1793546602916/setup-instructions \
  -H "Authorization: Bearer fxp_test_YOUR_KEY"

Response 200 OK

{
  "environment": "sandbox",
  "success": true,
  "data": {
    "code": "20261101-106852",
    "order_id": "20261101-106852",
    "currency": "DZD",
    "package_id": "france-in-7days-1gb",
    "quantity": 1,
    "type": "sim",
    "description": "Order for John Smith",
    "esim_type": "local",
    "validity": 7,
    "package": "KPN France Data only-1 GB - 7 days",
    "data": "1 GB",
    // ... 5 more
    "sims": [
      {
        "created_at": "2026-11-09 15:23:22",
        "iccid": "8900000540463077957",
        // ... 2 more
        "qrcode": "LPA:1$lpa.airalo.com$DUMMY-2610301358-iKFxr-44741",
        // ... 6 more
        "direct_apple_installation_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.airalo.com$DUMMY-2610301358-iKFxr-44741",
        // ... 2 more
      }
    ],
    // ... 3 more
  }
}

Setup data by booking or ICCID

Follow the usage

Read the data left and the expiry of an eSIM by its ICCID. The answer is cached for up to 15 minutes, so use it for a screen or a reminder, not as a meter.

  • GET /api/v1/esims/usage/{iccid}

Request

curl https://api.fx-port.com/api/v1/esims/usage/8900000540463077957 \
  -H "Authorization: Bearer fxp_test_YOUR_KEY"

Response 200 OK

{
  "environment": "sandbox",
  "success": true,
  "data": {
    "data": {
      "remaining": 1024,
      "total": 1024,
      "expired_at": "2026-11-16 15:23:29",
      "is_unlimited": false,
      "status": "ACTIVE",
      "remaining_voice": 0,
      "remaining_text": 0,
      "total_voice": 0,
      "total_text": 0
    },
    "meta": {
      "message": "success"
    }
  }
}

Single and bulk eSIM usage

Top up when the data runs out

List the packages that can be added to this exact eSIM, then create the top-up with one of them. If the list is empty, the eSIM cannot be topped up now and the traveler needs a new one.

  • GET /api/v1/esims/topup-packages/{iccid}
  • POST /api/v1/esims/topups

Request

curl https://api.fx-port.com/api/v1/esims/topups \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "package_id": "france-in-7days-1gb-topup",
  "iccid": "8900000540463077957",
  "first_name": "John",
  "last_name": "Smith",
  "email": "[email protected]",
  "phone": "+15555550123"
}'

Response 200 OK

{
  "environment": "sandbox",
  "success": true,
  "data": {
    "topup": {
      "code": "20261101-106853",
      "order_id": "20261101-106853",
      "package_id": "france-in-7days-1gb-topup",
      "package": "KPN France Data only-1 GB - 7 days",
      "data": "1 GB",
      "validity": 7,
      "price": 468.84,
      // ... 6 more
    },
    "iccid": "8900000540463077957",
    "balance": {
      "previous": 0,
      "current": 0,
      "currency": "DZD"
    }
  },
  "booking_id": "FX-1793546625391",
  "message": "eSIM topup completed successfully",
  "balanceDebited": false,
  "balance_debited": false,
  "transactionCreated": false,
  "transaction_created": false,
  "payoutCreated": false,
  "payout_created": false
}

Check and create top-ups

Prices, commission and your balance

Every package carries its money fields, computed on the server in your agency currency. A price in a request body is ignored, so an order is always created at the price the server computes.

Money fields of an eSIM package
FieldWhat it holds
base_price price The package price before your commission. It is not the amount debited from your balance.
total_price retail_price The price to charge the traveler, with your commission included.
balance_debit_amount b2b_price What leaves your agency balance when you order the package on live. Read it, do not compute it.
agency_commission commission_rate Your commission rate, as text and as a number. Bookings return your commission as an amount instead.

In the catalog example, the traveler pays 468.84 DZD. An order on live debits your balance 418.60 DZD, and the difference, 50.24 DZD, is your commission.

The rate belongs to your agency

The commission rate comes back with every package, and it is not the same for every agency. A contracted corporate agency can have a rate of zero, and then the total price equals the debit.

Read the fields, do not rebuild them

The debit can be higher than the base price, because amounts specific to your agency are part of it. So read balance_debit_amount and never compute it from base_price. The word price is the base price in the catalog but the retail price in orders, top-up packages and setup data: use base_price and total_price everywhere and the ambiguity disappears.

Priced in your agency currency

Every amount is already converted into your agency currency, whichever currency the supplier uses. The answer also shows the supplier currency and the exchange rate, so you can verify the rate yourself and never need to convert again.

How eSIM prices are built

After the sale: usage, top-ups and setup data

A sale does not end with the order. These calls keep the traveler connected, and they work only on eSIMs that your agency ordered in the same environment as your key.

Reading usage, and how fresh it is

Usage returns the data left, the allowance, the status and the expiry of one eSIM, or of up to 50 eSIMs in one call. FX-Port keeps the last answer for up to 15 minutes to protect the supplier's rate limits, so a figure can be that old. Pass force=true to ask the supplier again, for example right after a top-up. Do not do it on every request, because the supplier limits how often usage can be read. If the supplier cannot answer, you get the last known figure marked as stale.

Single and bulk eSIM usage

Top-ups: ask first, then create

A top-up is two calls. First list the packages that can be added to this exact eSIM, then create the top-up with one of the returned package ids. Never reuse the package id of the original order, and do not keep a top-up package for later: ask again when the traveler wants to top up. A top-up adds data to the eSIM and does not replace what is left, so read the result from usage with force=true.

Check and create top-ups

When a top-up is not possible

Suppliers choose which mobile operators serve a country, and in rare cases they change one. The eSIM can then keep working until it expires, while its package can no longer be topped up. The list of top-up packages comes back empty. If a supplier rejects a top-up, your balance is not touched and the attempt is saved as a failed booking. The answer is a supplier_error that you can react to.

Why a top-up can be unavailable

Setup data and installation steps

The setup data call returns the QR code, the activation data, the install links and the installation steps of an order whenever you need them. The instructions call returns the steps per operating system, in the language you ask for. The steps are generic text from the supplier, so show them next to the QR code of the order. Show both only to the traveler who owns the eSIM.

Setup data by booking or ICCID

Bookings, top-up history and ownership

The booking list returns the eSIM orders of your agency in the environment of your key, newest first. You can search it by booking id, traveler name, email address, package name or ICCID. Top-ups are not in that list: read them with the top-up history of an eSIM. Before you build a link or a screen around a booking id, the ownership check tells you whether it is yours. A booking of another agency and one that does not exist get the same answer.

List and verify eSIM bookings

Every eSIM endpoint, with OpenAPI, Postman and code samples

The eSIM API has 12 endpoints under one base URL. Every row opens the reference page of that call, with its request fields, responses, errors and examples in 9 languages.

eSIM API endpoints with method, path, key permission and purpose
EndpointWhat it doesKey
GET /api/v1/esims/packagesList the packages of one country or region, with prices already in your agency currency. Always send a country: the whole catalog is very large and has no pagination.Read
GET /api/v1/esims/compatible-devicesThe devices known to support eSIM, for a check before the traveler buys.Read
POST /api/v1/esims/ordersCreate one eSIM from a package for a traveler and provision it with the supplier. On live it debits your agency balance.Read+Write
GET /api/v1/esims/topup-packages/{iccid}The packages that can be added to one exact eSIM today. An empty list means it cannot be topped up now.Read
POST /api/v1/esims/topupsAdd a top-up package to an existing eSIM. On live, your balance is debited only after the supplier accepts it.Read+Write
GET /api/v1/esims/usage/{iccid}The data left, the allowance, the status and the expiry of one eSIM. The answer can be up to 15 minutes old.Read
GET /api/v1/esims/usage/bulkThe usage of up to 50 eSIMs in one call, one entry per ICCID, each with its own status.Read
GET /api/v1/esims/bookingsThe eSIM orders of your agency, newest first, with search and pagination. Top-ups are not in this list.Read
GET /api/v1/esims/bookings/{iccid}/topup-historyThe top-ups of one eSIM, newest first. The list is empty when the eSIM has none.Read
GET /api/v1/esims/bookings/{booking_id}/verifyCheck that a booking reference belongs to your agency, before you build a link or a screen around it.Read
GET /api/v1/esims/bookings/{booking_id}/setup-instructionsThe QR code, the activation data, the install links and the installation steps of an order, as JSON.Read
GET /api/v1/esims/instructions/{iccid}Step-by-step installation instructions for one ICCID, per operating system, in the language you ask for.Read

OpenAPI 3.1

One file describes every endpoint and every field. Generate a client from it, import it into an API tool or give it to a coding assistant.

OpenAPI 3.1 file

Postman collection

The eSIM collection covers all 12 endpoints in folders that follow the stages of a sale. It holds no keys, so add your own.

eSIM Postman collection

Code samples in 9 languages

Every reference page prints its request in cURL, Node.js, Python, PHP, Go, Java, C#, Swift and Kotlin, generated from the OpenAPI description. Each eSIM request is available in all of them.

Full API reference

Docs for AI assistants

llms.txt and llms-full.txt hand AI assistants the documentation as plain text, and every docs page can be read as Markdown by adding .md to its address.

llms.txt index for AI tools

eSIM API documentation

Built for production: keys, errors, retries and caching

An eSIM integration is judged in production on authentication, failures, retries, caching and rate limits. This is what to know about each.

One base URL, two kinds of key

Every request carries a key as a bearer token in the Authorization header. The prefix says where the key works: fxp_test_ for the sandbox and fxp_live_ for live. Both hit the same base URL, and each JSON answer states the environment it came from in environment. Keys belong on your server, never in a browser or an app. FX-Port keeps only a hash of each key, so a lost key cannot be shown again. A change to a key can take a few minutes to apply.

Environments and Postman collections

Which key does which call

A Read key can list packages, check devices, read usage and read bookings. Creating an order or a top-up needs a Read+Write key, and a Read key that attempts it gets read_write_permission_required. Any key can be locked to a list of IP addresses, and a call from another address gets ip_not_allowed. Use a Read key wherever the code only displays results.

Keys and permission levels

One error format for every failure

A failed call returns an errors array. Its first entry has a stable code, a type that says how to react and the field at fault. Test the code, never the message. The error list shared by flights, hotels, eSIMs and the account holds 180 codes. When the supplier refuses an order or a top-up, the answer is a supplier_error carrying the supplier's own HTTP status. Nothing is debited and a failed booking is saved. Right after a restart, the catalog and the device list can answer 202 with success set to false: wait a few seconds and ask again.

The full list of error codes

Retrying without ordering twice

FX-Port has no idempotency key, so there is no Idempotency-Key header to send. Reads are safe to repeat; creating an order or a top-up is not. FX-Port refuses an obvious duplicate with 409 and duplicate_request: an order for a package that is still being processed. On live it also refuses the same package ordered again within 60 seconds of your agency's latest confirmed order, whoever the traveler is. Orders usually answer in a few seconds, but a slow supplier can take much longer. If a call times out or the connection drops, do not send the order again at once, because the eSIM may exist. Look for it with the search parameter of the booking list, using the traveler's email address, and order again only if nothing was created.

Retrying safely after a timeout

No webhooks: ask when you need it

FX-Port does not call a URL of yours, in the sandbox or on live. An order answers with the eSIM, or with a PENDING- placeholder while the supplier has not returned it yet. You ask for anything else when you need it: the setup data, the usage or the booking list.

Caching and speed

Usage is kept for up to 15 minutes and the list of top-up packages for a few minutes, and force=true skips the cache. The unfiltered catalog is a very large answer without pagination, so send country and accept compression with Accept-Encoding: gzip. Package titles come back in the language of the locale parameter; the first request in a language other than English is slower, because the text is fetched then and cached afterwards.

Rate limits

Limits are counted per key and per minute in two groups, and every response carries ratelimit-limit and ratelimit-remaining, so you can slow down before you are refused. After a 429, wait the seconds in Retry-After and retry with exponential backoff and jitter. Too many orders or top-ups of one key or agency running at the same time can be answered with concurrency_limit_exceeded.

eSIM rate limits per key
Kind of callPer minute
Creating orders and top-ups1,200
Every other eSIM endpoint6,000

For now the ceiling on orders and top-ups running at the same time is in observation mode: it is counted but not enforced, so nothing is refused because of it yet, and the changelog announces the day it is enforced. Read these numbers as the current policy rather than a promise. Sandbox keys have the same per-minute counters as live keys, so schedule load tests instead of running them in a loop.

Per-key rate limits

Names and versions

Request fields are snake_case, such as first_name and agent_id. Earlier spellings still work: user_id is read as agent_id and adds a warnings entry, while customer_email is accepted as email without one. Many answers keep an older key beside its snake_case twin with the same value, such as order_id beside code, and ?naming=snake returns snake_case keys only. The version sits in the path, /api/v1: a breaking change would get a new prefix, while new fields and spellings are additive.

Start in the free sandbox, then go live

Everything on this page works in the sandbox today. A sandbox key is self-serve and free, and it never touches your real balance.

  1. 1

    Create an account

    Sign up for an FX-Port account. Sandbox keys are free, and sandbox bookings carry no real charges.

    Create your FX-Port account

  2. 2

    Create a sandbox key

    On the API page of your dashboard, create a key that starts with fxp_test_. A Read key can search and read; a Read+Write key can also create test bookings.

    Open the API page of the dashboard

  3. 3

    Send your first request

    There is one base URL, and the key goes in the Authorization header as a bearer token. The quickstart walks through a first call.

    Read the API quickstart

  4. 4

    Go live when you are ready

    Swap in a fxp_live_ key. Live works once your agency is approved; until then the API answers 403 agency_not_active. Real bookings are paid from a funded balance.

    Read how going live works

A first catalog request in cURL. The docs print the same request in every supported language.

Request

curl 'https://api.fx-port.com/api/v1/esims/packages?country=france&type=local' \
  -H "Authorization: Bearer fxp_test_YOUR_KEY"

Quickstart: first sandbox call

What the sandbox does

An order in the sandbox creates a real test eSIM at the supplier, with a dummy activation code, so it cannot be installed on a phone. Nothing is debited, and the answers say so: balanceDebited, transactionCreated and payoutCreated are all false. A top-up answers a balance of zero. Usage is static: a new eSIM shows its full allowance and the status ACTIVE. Sandbox bookings are visible only to sandbox keys.

eSIMs in the sandbox

What only live can test

The balance check, the duplicate check and the balance lock run on live only. So the answers 402 with insufficient_balance, 409 for the same package ordered again within 60 seconds, and 503 with balance_lock_timeout never appear in the sandbox. Write their handling from the list of error codes.

The full list of error codes

Going live

Live keys are tied to agency approval: until your agency is approved, a live key answers agency_not_active and your sandbox key keeps working. Live orders are paid from a funded agency balance. Going live changes the key, not the code, because sandbox and live share one base URL.

Approval required for live keys

Who builds on the eSIM API

Each audience leans on a different part of the same calls.

Agencies that sell trips

Agencies sell an eSIM next to the flight and the hotel. They order with the traveler's own email address, so the supplier sends the QR code and a sharing link, and they read usage when a traveler asks. The commission rate comes back with every package, and agent_id records which user of your agency placed an order.

OTAs and marketplaces

Marketplaces build the browsing page first: one country at a time, local or regional packages, the networks and the data allowance of each package, and a device check before the traveler buys. A screen that lists a traveler's eSIMs can read the usage of up to 50 of them in one call.

Travel-tech and corporate teams

Travel-tech teams and corporate travel departments build their own booking tools. Everything they need to start is public: the OpenAPI file, the Postman collection and plain-text docs that an AI coding assistant can read, so the first prototype starts from the real contract.

eSIM API: frequently asked questions

What is the FX-Port eSIM API, and who is it for?

It is a REST API that lets a travel agency, an OTA or a reseller sell travel data eSIMs from one integration. You list the packages of a destination with prices in your agency currency and order one for a traveler. You give the traveler the QR code, follow usage and top up. Everything is JSON over HTTPS, and an OpenAPI file describes every endpoint.

Is there a free sandbox, and how do I get a key?

Yes. Create an FX-Port account, open the API page of your dashboard and create a sandbox key; you can do it yourself, at no cost. An order in the sandbox creates a test eSIM with a dummy activation code, so it cannot be installed on a phone. Nothing is debited from your balance. Sandbox and live share one base URL, so the code you test is the code you ship.

What does going live require?

Your agency must be approved before a live key works. Until then a live key is refused and your sandbox key keeps working. Live orders are paid from a funded agency balance, which the sandbox never touches, and any key can be locked to a list of IP addresses. Switching means changing the key only; every answer names the environment it came from.

Which countries and packages can I sell?

Packages are available for 190+ countries, and regional bundles appear under their region. Packages differ in data allowance, validity, networks and whether they can be topped up. Read these fields and the package id from the catalog answer instead of hard-coding them. Ask for one country at a time, because the whole catalog is a very large answer. A separate device list lets you check a traveler's phone before the sale.

How does the traveler receive the eSIM?

The order answer carries the QR code, its activation string, an install link for iPhone and a sharing page. You can show them in your own screens. FX-Port also asks the supplier to email the QR code and a sharing link to the traveler's address as soon as the eSIM is created. If the supplier has not returned the eSIM yet, the order still exists and the data is available later from the setup data call.

How do prices, commission and my balance fit together?

Every package carries these money fields in your agency currency: the base price before your commission, the total price to charge the traveler, the amount debited from your balance and your commission rate. Your commission is the total price minus the debit. Read these fields instead of rebuilding them, because the debit is specific to your agency. In the sandbox nothing is debited; on live the debit leaves your agency balance.

Can I top up an eSIM, and how do I see what is left?

Yes, in two calls: list the packages that can be added to the exact eSIM, then create the top-up with one of them. A top-up is not always possible, because a supplier can change the operator of a country. An empty list then means the traveler needs a new eSIM. Usage is cached for up to 15 minutes, so treat it as a recent figure, and one call reads up to 50 eSIMs.

How are failures reported, and are there webhooks?

A failure comes back in one format with a stable error code, and that code is what your program should test, not the message text. Reads are safe to repeat with backoff. Creating an order or a top-up is different: there is no idempotency key, so after a timeout look up the booking before you try again. FX-Port refuses an obvious duplicate, such as the same package ordered twice within 60 seconds on live. There are no webhooks.

Can I cancel or refund an eSIM order through the API?

No. The API has no endpoint to cancel or refund an eSIM order. Every order creates exactly one eSIM, so check the package and the traveler's details before you send it. If the supplier refuses an order, nothing is debited and a failed booking is saved. For a problem with a specific order, write to the developer team and quote its booking id.

What do I need to start, and which tools help?

You need a sandbox key and an HTTP client. The docs add an OpenAPI 3.1 file, a Postman collection, code samples in 9 languages and plain-text exports for AI assistants. Keep your keys on your server, never in a browser or an app. The part that takes longer is your own checkout, the delivery of the QR code to the traveler and error handling.

Other FX-Port APIs

An eSIM usually goes with a trip. Flights and hotels use the same keys, the same error body and the same conventions as eSIMs, and their bookings sit in the same account history.

  • Flights API

    Search, price and book flights, pay held orders, cancel, and read tickets.

  • Hotels API

    Find hotels, check rates, pre-book and book, read vouchers and cancel.

  • Transfers APIPreview

    Quote, book, follow and cancel airport transfers.

Rather not write code? Agencies can also sell eSIMs from the FX-Port dashboard. Sell eSIMs from the dashboard

Run your first eSIM order in the sandbox

Create an account, make a sandbox key, list packages, create an order and read usage today. Live keys follow once your agency is approved.

Create your accountEmail the developer team

Ask an AI assistant about this API

Open this page in an assistant and ask for a step-by-step integration plan.

FX-PORT

Your trusted B2B travel distribution platform. Access global inventory and grow your business with better rates and easy tools.

  • Terms
  • Privacy
  • Contact us
  • Corporate
  • Blog
  • B2B Flights API
  • B2B Transfers API
  • B2B Ferries API
  • B2B Hotels API
  • B2B eSIM API
  • B2B Bed Bank
  • B2B Flight Consolidator
  • Airlines
  • Airports
  • Countries
  • eSIMs

Flight Services

FX-Port is a comprehensive B2B flight distribution platform enabling travel agencies to access and sell flight tickets worldwide. With connections to over 500 airlines, agencies can offer competitive wholesale rates with instant confirmations and automated ticketing.

Our platform provides real-time availability, price comparison tools, and automated booking management. Travel agencies can access exclusive B2B airfares, manage commissions, and streamline their flight operations through our integrated system.

Accommodation Services

Access our extensive network of over 1.5 million properties worldwide through our B2B hotel booking platform. Travel agencies can offer competitive rates with guaranteed margins and instant room confirmations across all accommodation categories.

From luxury resorts to budget-friendly options, our platform provides comprehensive property information, dynamic pricing, and automated booking management for seamless hotel distribution.

Travel Solutions

Comprehensive transfer services with a global network of verified providers, offering airport transfers, city tours, and professional chauffeur services. Our ferry booking system connects major maritime routes with instant booking capabilities and competitive B2B rates.

Specialized Umrah packages with end-to-end services including visa processing, accommodation, and transportation. Custom travel packages with dynamic pricing and automated inventory management for travel agencies.

FX-Port revolutionizes B2B travel distribution with cutting-edge technology and comprehensive solutions. Our platform enables travel agencies to access global inventory, manage bookings efficiently, and grow their business with competitive rates and automated tools.

With support for multiple currencies, local payment processing, and 24/7 customer service, we ensure seamless operations for travel agencies worldwide. Our platform features real-time availability, instant confirmations, and comprehensive booking management tools.

Travel agencies can leverage our platform to offer a complete range of travel services, including flight bookings, hotel reservations, transfer services, ferry tickets, Umrah packages, and custom travel experiences. Our B2B marketplace connects agencies with verified suppliers worldwide.

FX-Port provides enterprise-grade security, automated commission management, and detailed analytics for business growth. Join hundreds of successful agencies using our platform to streamline their operations and expand their market presence.

© 2026 FX-PORT. All rights reserved.

Skip to content
FX-Port for SuppliersFlights—Airlines·Consolidators·OTAs·GDS AgenciesHotels—Bed Banks·PMS/CMS·HotelsAll Travel Suppliers
CorporateAPI
Login
Get access now
Dashboard