Package catalog
List eSIM data packages by country and type, and check device compatibility.
List packages
GET /
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.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
country | string | No | Country 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. |
type | string | No | Scope 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. |
locale | string | No | Language 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):
| Legacy | Use 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 / (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
| Field | Type | Meaning |
|---|---|---|
package_id | string | Package identifier — pass it as package_id when creating an order. id carries the same value (legacy name) |
type | string | Always sim: the package is sold as a new eSIM (topup packages have type topup) |
title | string | Human-readable package label (data amount + validity) in the language of the request |
data | string | Data allowance for the package, e.g. "1 GB" or "Unlimited" |
amount | integer | Data allowance in MB. 0 means unlimited — check is_unlimited rather than treating 0 as "no data" |
is_unlimited | boolean | true for unlimited-data packages |
is_fair_usage_policy | boolean or integer | Whether 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_policy | string | null | Human-readable fair-usage terms (e.g. throttle speed and daily threshold); null when no policy applies |
day | integer | Validity in days (see operator_activation_policy for when it starts) |
voice | integer | null | Voice minutes included, when the package bundles voice (null = data-only) |
text | integer | null | SMS included, when the package bundles SMS (null = data-only) |
is_regional | boolean | Always false. Regional bundles (for example europe) are global packages: check operator_type |
is_global | boolean | true when operator_type is global |
Country
| Field | Type | Meaning |
|---|---|---|
country_name | string | Destination country or region, human-readable, in the language of the request |
country_code | string | Destination country, ISO 3166-1 alpha-2. An empty string for regional and worldwide bundles |
country_slug | string | Destination slug — pass as the country query parameter. Regions have their own slug (europe) |
country_image_url | string | Flag/country image URL for display |
Operator (underlying mobile network)
| Field | Type | Meaning |
|---|---|---|
operator_name | string | Underlying mobile network operator/brand for this package |
operator_id | integer | Identifier of the operator |
operator_type | string | Package scope: local (one country) or global (regional and worldwide bundles). There is no regional value |
operator_style / operator_gradient_start / operator_gradient_end | string | Display styling hints (light/dark theme, gradient colors) for this operator's branding |
operator_is_prepaid | boolean | Whether the underlying plan is prepaid |
operator_esim_type | string | e.g. Prepaid |
operator_warning | string | null | Operator-specific caution to show travelers (e.g. topup window), when applicable |
operator_apn_type | string | automatic or manual — whether APN settings are auto-configured |
operator_is_roaming | boolean | Whether this package operates via roaming rather than a local SIM profile |
operator_info | string[] | Short bullet facts about the operator/network (technology, rechargeability, coverage) |
operator_image_url | string | Operator logo URL |
operator_plan_type | string | e.g. data (data-only plan) |
operator_activation_policy | string | When validity starts, e.g. first-usage (starts on first connection, not purchase) |
operator_is_kyc_verify | boolean | Whether the destination country requires identity verification for this operator (rare; relevant for a few countries) |
operator_rechargeability | boolean | Whether this eSIM can be topped up — see Top up an eSIM |
operator_other_info | string | null | Longer free-text coverage notes/exclusions (e.g. territories not covered) |
operator_coverages | array | Per-country network list: { name, code, networks: [{ name, types }] } |
operator_apn | object | APN 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
| Field | Type | Meaning |
|---|---|---|
base_price | number | Base 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_price | number | The price to charge the traveler, in your agency currency. Both fields have the same value and include your commission |
balance_debit_amount | number | The 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_commission | string | Your 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) |
currency | string | Your agency's settlement currency, the currency of every amount above |
supplier_currency | string | Currency the supplier prices the package in |
exchange_rate | number | Rate used to convert supplier_currency to currency for this package |
meta field reference
| Field | Meaning |
|---|---|
total_packages | Number of packages in data after filtering |
filters_applied | Echo of the country / type filters actually applied |
locale | Language used for text fields in this response |
agency_currency | Same as currency on each package — your agency's settlement currency. currency carries the same value (the canonical name) |
supplier_currency | Same as supplier_currency on each package |
exchange_rate | Same as exchange_rate on each package |
commission_percentage | The 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), withoperator_typeglobal.
Errors
| HTTP | code | Cause |
|---|---|---|
202 | — | The catalog is still loading after a restart. success is false and data is empty: wait a few seconds and retry |
400 | invalid_country, invalid_type, invalid_locale | A parameter is not valid |
401 | authentication_required, invalid_api_key | Missing or invalid API key |
429 | rate_limit_exceeded | Over 6,000 requests per minute for this key |
Compatible devices
GET /
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
| Field | Meaning |
|---|---|
brand | Device manufacturer, spelled as the supplier does (the case differs between entries) |
model | Manufacturer's internal model identifier |
name | Human-readable marketing name for the device |
os | Operating system family: ios or android |
meta.total_devices | Number of devices in data |
meta.cached_at | When 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_days | Age of the list in days |