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

Concepts

  • Overview
  • Pricing
  • Supplier continuity

Catalog and orders

  • Package catalog
  • Create an order
  • Top up

After the order

  • Usage
  • Bookings
  • Setup instructions
  • Use these docs with AI

eSIMs · Catalog and orders

Package catalog

List eSIM data packages by country and type, and check device compatibility.

List packages

GET /api/v1/esims/packages

Permission: Read

Returns the package catalog with prices computed for your agency, in your agency's currency. Results are cached server-side and refreshed periodically, so repeated calls are cheap.

Always send country. The unfiltered catalog is about 4 MB (more than 2,000 packages), has no pagination and takes several seconds. Ask for one destination at a time, and accept compressed responses (Accept-Encoding: gzip).

Query parameters

ParameterTypeRequiredDescription
countrystringNoCountry or region slug, for example france, turkey or europe. Take it from country_slug of a package. A country name as people write it is read as its slug (United States is united-states, Bosnia & Herzegovina is bosnia-and-herzegovina); case and accents do not matter. An unknown slug, or a name that matches no slug, returns an empty list.
typestringNoScope of the package: local (one country) or global (every regional and worldwide bundle). Any other value that is a plain word (for example regional) returns an empty list: there is no regional value. A value with spaces or symbols is refused with invalid_type. Upper case is accepted.
localestringNoLanguage of the text fields. Falls back to the Accept-Language header, then en. Any spelling of a locale is read and only its language counts: fr-CA, zh-Hans, pt_BR, fr_FR.UTF-8, or a whole Accept-Language value such as fr-FR,fr;q=0.9 (the first language is used). Supported: en, fr, ar, de, es, it, ja, ko, pl, pt, ro, ru, sq, tr, vi, zh. Another well-formed language is answered in English with a coerced_value warning, and meta.locale says en. A value that is not a locale is refused with invalid_locale.

Legacy names (still work; the response carries a deprecated_field warning):

LegacyUse instead
filter[type]type

all_pages is an old parameter that does nothing: every call returns the whole filtered catalog, and the response lists all_pages in its warnings as ignored_field.

curl --request GET \
  --url 'https://api.fx-port.com/api/v1/esims/packages?country=france&type=local&locale=en' \
  --header 'Authorization: Bearer fxp_test_YOUR_KEY'
const params = new URLSearchParams({ country: "france", type: "local", locale: "en" });
const response = await fetch(`https://api.fx-port.com/api/v1/esims/packages?${params}`, {
  headers: { Authorization: "Bearer fxp_test_YOUR_KEY" },
});
if (response.status === 202) {
  // the catalog is still loading after a restart: wait a few seconds and retry
}
const { data: packages, meta } = await response.json();
console.log(meta.total_packages, packages[0].package_id, packages[0].balance_debit_amount);
import requests

response = requests.get(
    "https://api.fx-port.com/api/v1/esims/packages",
    params={"country": "france", "type": "local", "locale": "en"},
    headers={"Authorization": "Bearer fxp_test_YOUR_KEY"},
    timeout=30,
)
body = response.json()
for package in body["data"]:
    print(package["package_id"], package["title"], package["total_price"], package["currency"])

Response example

Example response for GET /api/v1/esims/packages?country=france&type=local (one of the twelve packages shown, lists trimmed):

{
  "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",
      "country_image_url": "https://cdn-revamp.airalo.com/images/9753dedb-d495-47cf-b6e4-82e555564743.png",
      "operator_name": "KPN France Data only",
      "operator_id": 1704,
      "operator_style": "dark",
      "operator_gradient_start": "#F1D4FB",
      "operator_gradient_end": "#EEC0FF",
      "operator_type": "local",
      "operator_is_prepaid": false,
      "operator_esim_type": "Prepaid",
      "operator_warning": null,
      "operator_apn_type": "automatic",
      "operator_is_roaming": true,
      "operator_info": ["Data-only eSIM.", "Rechargeable online with no expiry."],
      "operator_image_url": "https://cdn-revamp.airalo.com/images/01b47d41-fbb4-4032-8f5a-d1ca6a0f3688.png",
      "operator_plan_type": "data",
      "operator_activation_policy": "first-usage",
      "operator_is_kyc_verify": false,
      "operator_rechargeability": true,
      "operator_other_info": "Covers metropolitan (mainland) France and Corsica. Does not include overseas territories (Guadeloupe, Martinique, Reunion, ...",
      "operator_coverages": [
        { "name": "FR", "code": "FR", "networks": [{ "name": "Orange", "types": ["5G"] }] }
      ],
      "operator_apn": {
        "ios": { "apn_type": "automatic", "apn_value": null },
        "android": { "apn_type": "automatic", "apn_value": null }
      },
      "is_regional": false,
      "is_global": false,
      "retail_price": 468.84,
      "total_price": 468.84,
      "balance_debit_amount": 418.6,
      "b2b_price": 418.6,
      "agency_commission": "12 %",
      "commission_rate": 12,
      "currency": "DZD",
      "supplier_currency": "USD",
      "exchange_rate": 250.0
    }
  ],
  "meta": {
    "total_packages": 12,
    "filters_applied": { "country": "france", "type": "local" },
    "locale": "en",
    "agency_currency": "DZD",
    "currency": "DZD",
    "supplier_currency": "USD",
    "exchange_rate": 250.0,
    "commission_percentage": "12 %",
    "commission_rate": 12
  }
}

Save package_id from the response — it's what you pass as package_id when creating an order. id carries the same value (it is the legacy name; keep reading it if your code already does). The package object is described in full in the eSIM package object.

Response field reference

Package identity and data allowance

FieldTypeMeaning
package_idstringPackage identifier — pass it as package_id when creating an order. id carries the same value (legacy name)
typestringAlways sim: the package is sold as a new eSIM (topup packages have type topup)
titlestringHuman-readable package label (data amount + validity) in the language of the request
datastringData allowance for the package, e.g. "1 GB" or "Unlimited"
amountintegerData allowance in MB. 0 means unlimited — check is_unlimited rather than treating 0 as "no data"
is_unlimitedbooleantrue for unlimited-data packages
is_fair_usage_policyboolean or integerWhether a fair usage policy throttles the speed after a threshold. It is a boolean on limited packages and 0 or 1 on unlimited ones: treat 0 as false and 1 as true. Read fair_usage_policy for the exact terms
fair_usage_policystring | nullHuman-readable fair-usage terms (e.g. throttle speed and daily threshold); null when no policy applies
dayintegerValidity in days (see operator_activation_policy for when it starts)
voiceinteger | nullVoice minutes included, when the package bundles voice (null = data-only)
textinteger | nullSMS included, when the package bundles SMS (null = data-only)
is_regionalbooleanAlways false. Regional bundles (for example europe) are global packages: check operator_type
is_globalbooleantrue when operator_type is global

Country

FieldTypeMeaning
country_namestringDestination country or region, human-readable, in the language of the request
country_codestringDestination country, ISO 3166-1 alpha-2. An empty string for regional and worldwide bundles
country_slugstringDestination slug — pass as the country query parameter. Regions have their own slug (europe)
country_image_urlstringFlag/country image URL for display

Operator (underlying mobile network)

FieldTypeMeaning
operator_namestringUnderlying mobile network operator/brand for this package
operator_idintegerIdentifier of the operator
operator_typestringPackage scope: local (one country) or global (regional and worldwide bundles). There is no regional value
operator_style / operator_gradient_start / operator_gradient_endstringDisplay styling hints (light/dark theme, gradient colors) for this operator's branding
operator_is_prepaidbooleanWhether the underlying plan is prepaid
operator_esim_typestringe.g. Prepaid
operator_warningstring | nullOperator-specific caution to show travelers (e.g. topup window), when applicable
operator_apn_typestringautomatic or manual — whether APN settings are auto-configured
operator_is_roamingbooleanWhether this package operates via roaming rather than a local SIM profile
operator_infostring[]Short bullet facts about the operator/network (technology, rechargeability, coverage)
operator_image_urlstringOperator logo URL
operator_plan_typestringe.g. data (data-only plan)
operator_activation_policystringWhen validity starts, e.g. first-usage (starts on first connection, not purchase)
operator_is_kyc_verifybooleanWhether the destination country requires identity verification for this operator (rare; relevant for a few countries)
operator_rechargeabilitybooleanWhether this eSIM can be topped up — see Top up an eSIM
operator_other_infostring | nullLonger free-text coverage notes/exclusions (e.g. territories not covered)
operator_coveragesarrayPer-country network list: { name, code, networks: [{ name, types }] }
operator_apnobjectAPN settings per platform: { ios: { apn_type, apn_value }, android: { apn_type, apn_value } }; apn_value is null when the setup is automatic

Pricing — four different amounts, see eSIM pricing for how to use them

FieldTypeMeaning
base_pricenumberBase price in your agency currency, before your commission. It is not the amount debited from your balance. price carries the same value (legacy name)
retail_price / total_pricenumberThe price to charge the traveler, in your agency currency. Both fields have the same value and include your commission
balance_debit_amountnumberThe amount debited from your agency balance when you order this package. total_price minus this amount is your commission. b2b_price carries the same value (the name flights and hotels use)
agency_commissionstringYour commission rate as text: "12 %" for standard agencies, "0 %" for contracted corporate agencies. Bookings carry the commission as an amount instead. commission_rate carries the same rate as a number (12 or 0)
currencystringYour agency's settlement currency, the currency of every amount above
supplier_currencystringCurrency the supplier prices the package in
exchange_ratenumberRate used to convert supplier_currency to currency for this package

meta field reference

FieldMeaning
total_packagesNumber of packages in data after filtering
filters_appliedEcho of the country / type filters actually applied
localeLanguage used for text fields in this response
agency_currencySame as currency on each package — your agency's settlement currency. currency carries the same value (the canonical name)
supplier_currencySame as supplier_currency on each package
exchange_rateSame as exchange_rate on each package
commission_percentageThe commission rate applied across this response, as text. commission_rate carries it as a number

Caching, languages and speed

  • English is served from a cache that is refreshed about every 50 minutes.
  • Another language is fetched from the supplier on its first call (about 10 seconds) and then cached for an hour: the first request in a new language is slow, the next ones are fast. Package titles are translated; the operator facts are only partly.
  • A regional bundle appears under its region slug (country=europe), with operator_type global.

Errors

HTTPcodeCause
202—The catalog is still loading after a restart. success is false and data is empty: wait a few seconds and retry
400invalid_country, invalid_type, invalid_localeA parameter is not valid
401authentication_required, invalid_api_keyMissing or invalid API key
429rate_limit_exceededOver 6,000 requests per minute for this key

Compatible devices

GET /api/v1/esims/compatible-devices

Permission: Read

Returns the cached list of devices known to support eSIM (about 630 entries), useful for pre-sale device checks before a traveler purchases a package. The list is refreshed about once a week.

curl --request GET \
  --url https://api.fx-port.com/api/v1/esims/compatible-devices \
  --header 'Authorization: Bearer fxp_test_YOUR_KEY'
const response = await fetch("https://api.fx-port.com/api/v1/esims/compatible-devices", {
  headers: { Authorization: "Bearer fxp_test_YOUR_KEY" },
});
const { data: devices } = await response.json();
const supported = devices.some((device) => device.model === "iPhone15,2");

Response example

{
  "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-01T15:07:43.957236",
    "cache_age_days": 0.01
  }
}

Response field reference

FieldMeaning
brandDevice manufacturer, spelled as the supplier does (the case differs between entries)
modelManufacturer's internal model identifier
nameHuman-readable marketing name for the device
osOperating system family: ios or android
meta.total_devicesNumber of devices in data
meta.cached_atWhen the list was fetched from the supplier: the server's clock without a zone (2026-11-01T15:07:43.957236), unlike the UTC timestamps of the bookings
meta.cache_age_daysAge of the list in days

Right after a restart the list can still be loading in the background: the call answers 202 with "success": false and an empty data array, or 503 while the device fetcher itself is not running yet. Retry after a few seconds.

PreviousSupplier continuityNext Create an eSIM order

On this page

  • List packages
  • Query parameters
  • Response example
  • Response field reference
  • meta field reference
  • Caching, languages and speed
  • Errors
  • Compatible devices
  • Response example
  • Response field reference

© 2026 FX-Port

TermsPrivacy[email protected]