新REST 接口,提供 OpenAPI 3.1 文件
面向旅行社、OTA 与转售商的 eSIM API 接口
通过一个 REST 接口销售旅行 eSIM。按国家查询流量套餐,价格以您的代理商币种显示;为旅客下单,把二维码交给旅客,跟踪用量并充值。沙盒与正式环境共用一个 Base URL。先用免费的沙盒密钥开始,代理商通过审批后即可使用正式密钥。
文档仅提供英文版本。
- Base URL
https://api.fx-port.com/api/v1- 身份验证
Authorization: Bearer fxp_test_...- 密钥前缀
fxp_test_ / fxp_live_- 接口
- 12
- 有套餐的国家
- 190+
- 代码示例语言
- 9
eSIM API 接口,逐个调用演示
逐步查看一笔 eSIM 销售:每个请求如何构建、发送并得到响应。演示涵盖销售 eSIM、让旅客保持联网、检查设备与预订,以及值得处理的各类失败。其中的请求和响应均为文档中的示例,本页不会调用 API。
3 个请求:列出套餐, 订购 eSIM, 读取安装数据。
演示会话:请求体取自文档中的示例,日期已顺延以保持在今天之后,耗时仅为示意。本页面不会发送任何请求。
查找套餐、下单、读取安装数据
先查询一个国家的套餐,为旅客订购其中一个,再读取该订单的安装数据。沙盒会创建带虚拟激活码的测试 eSIM 且不扣款,控制台展示的是目录响应的精简副本。
列出套餐
目录调用接收
country和type,返回匹配的套餐,价格按您代理商的币种计算。请保存旅客选定套餐的package_id。请求 GET /esims/packages curl 'https://api.fx-port.com/api/v1/esims/packages?country=france&type=local' \ -H "Authorization: Bearer fxp_test_YOUR_KEY"响应 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 } }示例已截取显示
- PASS 状态码为 200
- PASS $.success 等于 true
- PASS $.data[0].package_id 存在
- PASS $.data[0].balance_debit_amount 存在
订购 eSIM
创建订单需要
package_id,以及旅客的邮箱和手机号。请保存响应中的booking_id和data.iccid。在沙盒中,balanceDebited、transactionCreated和payoutCreated全部为false。请求 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" }'响应 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 }示例已截取显示
- PASS 状态码为 200
- PASS $.environment 等于 sandbox
- PASS $.balance_debited 等于 false
- PASS $.data.iccid 存在
读取安装数据
安装数据调用接收
booking_id,返回该订单的二维码、激活数据、安装链接和安装步骤。请只向拥有该 eSIM 的旅客展示这些内容。请求 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"响应 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" } }示例已截取显示
- PASS 状态码为 200
- PASS $.data.package_id 等于 france-in-7days-1gb
- PASS $.data.sims[0].iccid 存在
文档中 GET /esims/bookings/{booking_id}/setup-instructions 的参考说明
读取用量,然后充值
旅客拿到 eSIM 后,先读取剩余流量,再查询哪些套餐可以添加到这个具体的 eSIM,最后创建充值。用量来自缓存,所以数字可能有几分钟的延迟。充值记录在侧边栏和文字记录中。
读取用量
用量调用接收订单的 ICCID,返回以兆字节计的
remaining和total、status以及到期时间。响应来自缓存,force=true会让系统重新向供应商查询。请求 GET /esims/usage/{iccid} curl https://api.fx-port.com/api/v1/esims/usage/8900000540463077957 \ -H "Authorization: Bearer fxp_test_YOUR_KEY"响应 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 状态码为 200
- PASS $.data.data.status 等于 ACTIVE
- PASS $.data.data.remaining 存在
列出充值套餐
这个调用列出当前可以添加到该 eSIM 的套餐。列表为空表示这个 eSIM 目前无法充值。充值用的
package_id请取自这个列表,不要取自原订单。请求 GET /esims/topup-packages/{iccid} curl https://api.fx-port.com/api/v1/esims/topup-packages/8900000540463077957 \ -H "Authorization: Bearer fxp_test_YOUR_KEY"响应 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 } ] } }示例已截取显示
- PASS 状态码为 200
- PASS $.data.data 包含 2 项
- PASS $.data.data[0].type 等于 topup
创建充值
创建充值需要该列表中的
package_id和 ICCID。在正式环境中,只有供应商接受后才会从余额扣款。在沙盒中,响应里的balance为零,不会扣款。请求 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" }'响应 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 状态码为 200
- PASS $.environment 等于 sandbox
- PASS $.balance_debited 等于 false
- PASS $.data.balance.current 等于 0
读取充值记录
充值记录列出某个 eSIM 的充值,按时间倒序。充值不在预订列表中,所以要到这里查找。
请求 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"响应 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 状态码为 200
- PASS $.data[0].type 等于 topup
检查设备,再读取您的预订
销售之前,先确认旅客的手机支持 eSIM。销售之后,读取您的预订,并确认某个预订编号属于您。批量用量列在侧边栏中。
查看设备列表
兼容设备调用返回已知支持 eSIM 的设备,含
brand、model和os。销售之前,请先查找旅客的手机。请求 GET /esims/compatible-devices curl https://api.fx-port.com/api/v1/esims/compatible-devices \ -H "Authorization: Bearer fxp_test_YOUR_KEY"响应 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 状态码为 200
- PASS $.success 等于 true
- PASS $.data[0].model 存在
列出您的预订
预订列表返回您代理商的 eSIM 订单,按时间倒序。这个请求通过
limit只取一条预订,pagination说明共有多少订单匹配,以及后面是否还有下一页。请求 GET /esims/bookings curl 'https://api.fx-port.com/api/v1/esims/bookings?limit=1' \ -H "Authorization: Bearer fxp_test_YOUR_KEY"响应 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 状态码为 200
- PASS $.data 包含 1 项
- PASS $.pagination.has_more 等于 true
核对归属
预订属于您的代理商时,归属核对返回
is_owner为 true。其他代理商的预订和不存在的预订得到的响应相同,所以这个调用无法用来探测预订编号。请求 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"响应 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 状态码为 200
- PASS $.is_owner 等于 true
批量读取用量
一次调用读取
iccids中列出的多个 eSIM 的用量,每个 ICCID 返回一条,各带自己的status。请始终传入iccids。请求 GET /esims/usage/bulk curl 'https://api.fx-port.com/api/v1/esims/usage/bulk?iccids=8900000540463077957' \ -H "Authorization: Bearer fxp_test_YOUR_KEY"响应 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 状态码为 200
- PASS $.data[0].status 等于 success
各种失败情形及对应的处理方式
所有失败共用同一种响应体,errors 的第一项带有稳定的 code。这几个订单调用是故意让它失败的,原因各不相同,应对方式也不同。
用只读密钥下单
用只读密钥调用下单接口,会在读取请求体之前被拒绝,返回
403与read_write_permission_required。请换成读写密钥。请求 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" }'响应 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 状态码为 403
- PASS 错误码为 read_write_permission_required
- PASS $.errors[0].type 等于 permission_error
邮箱地址无效
不是邮箱地址的值会被拒绝,返回
400与invalid_email,field指向email。重新下单之前请先修正该值:同样的请求会以同样的方式失败。请求 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" }'响应 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 状态码为 400
- PASS 错误码为 invalid_email
- PASS $.errors[0].field 等于 email
重复订单
对正在处理中的套餐再次下单,会被拒绝,返回
409与duplicate_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" }'响应 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 状态码为 409
- PASS 错误码为 duplicate_request
- PASS $.errors[0].type 等于 conflict
请求过多
超过每分钟限制时,调用会被拒绝,返回
429与rate_limit_exceeded,Retry-After头说明需要等待多少秒。之后请按退避策略重试。请求 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" }'响应 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 状态码为 429
- PASS 错误码为 rate_limit_exceeded
- PASS 响应头 Retry-After 存在
- PASS $.errors[0].details.retry_after 等于 12
从套餐目录到旅客联网,五步完成
对接路径很短:读取套餐目录,创建订单,把 eSIM 交给旅客,跟踪用量,流量用完时充值。只有下单和充值会产生变更,其余调用都是只读的。
读取一个目的地的套餐目录
列出某个国家的套餐,价格已按您的代理商币种计算,并记下旅客所选套餐的 package_id。请始终传入 country:完整目录的返回内容非常庞大,且不支持分页。套餐 ID 请从响应中读取,不要写死在代码里,因为目录会变化。
请求
curl 'https://api.fx-port.com/api/v1/esims/packages?country=france&type=local' \
-H "Authorization: Bearer fxp_test_YOUR_KEY"响应 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_id,并附上旅客的邮箱地址和手机号。一个订单只创建一张 eSIM,在正式环境中会从代理商余额扣款。请保存响应中的 booking_id 和 ICCID,之后每次调用都要用到其中之一。
请求
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"
}'响应 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
}| 字段 | 含义 |
|---|---|
booking_id | 订单在 FX-Port 的参考编号。请保存,用于获取安装数据、归属校验和您自己的记录。 |
data.iccid | eSIM 的标识符。查询用量、充值和获取安装说明都以 ICCID 为依据。 |
data.sim.qrcode data.sim.qrcode_url | 二维码所编码的激活字符串,以及二维码图片的链接。 |
data.sim.direct_apple_installation_url data.sim.sharing | 在 iPhone 上直接开始安装的链接,以及一个分享页面,旅客可凭访问码在该页面打开 eSIM。获取安装数据的接口还会补充 Android 的链接。 |
PENDING- 开头,待供应商返回后,可通过获取安装数据的接口取得数据。把 eSIM 交给旅客
响应中包含二维码及其激活字符串,您可以在自己的页面中展示。FX-Port 还会请供应商把二维码和分享链接发送到旅客的邮箱,您无需自行转发。之后可再次调用获取安装数据的接口,取得同样的数据以及安装步骤。
请求
curl https://api.fx-port.com/api/v1/esims/bookings/FX-1793546602916/setup-instructions \
-H "Authorization: Bearer fxp_test_YOUR_KEY"响应 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
}
}跟踪用量
按 ICCID 读取 eSIM 的剩余流量和到期时间。响应最多缓存 15 分钟,适合用于页面展示或提醒,不适合当作计量表。
请求
curl https://api.fx-port.com/api/v1/esims/usage/8900000540463077957 \
-H "Authorization: Bearer fxp_test_YOUR_KEY"响应 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"
}
}
}流量用完时充值
先列出可添加到这张 eSIM 的套餐,再用其中一个创建充值。如果列表为空,说明这张 eSIM 目前无法充值,旅客需要一张新的 eSIM。
请求
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"
}'响应 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
}价格、佣金与您的余额
每个套餐都带有金额字段,由服务器按您的代理商币种计算。请求体中的价格会被忽略,因此订单始终按服务器算出的价格创建。
| 字段 | 含义 |
|---|---|
base_price price | 未计入您佣金的套餐价格。它不是从您余额中扣除的金额。 |
total_price retail_price | 向旅客收取的价格,已含您的佣金。 |
balance_debit_amount b2b_price | 在正式环境下单该套餐时,从代理商余额中扣除的金额。请直接读取,不要自行计算。 |
agency_commission commission_rate | 您的佣金比例,以文本和数字两种形式给出。预订记录则以金额形式返回您的佣金。 |
以目录示例为例,旅客支付 468.84 DZD。在正式环境下单会从余额扣除 418.60 DZD,差额 50.24 DZD 即为您的佣金。
费率因代理商而异
每个套餐都会返回佣金比例,各代理商的比例并不相同。签约的企业代理商的佣金比例可能为零,此时总价等于扣款金额。
直接读取字段,不要自行推算
扣款金额可能高于基础价格,因为其中包含适用于您代理商的特定金额。因此请读取 balance_debit_amount,不要用 base_price 推算。price 一词在目录中指基础价格,在订单、充值套餐和安装数据中却指零售价:统一使用 base_price 和 total_price,就不会产生歧义。
按您的代理商币种计价
无论供应商使用哪种币种,所有金额都已换算为您的代理商币种。响应中同时给出供应商币种和汇率,您可以自行核对汇率,无需再次换算。
售后:用量、充值与安装数据
销售并不随下单结束。以下调用让旅客保持联网,且仅适用于您的代理商在与密钥相同的环境中订购的 eSIM。
查询用量及其时效
用量接口返回一张 eSIM 的剩余流量、总流量、状态和到期时间,一次调用最多可查询 50 张 eSIM。为避免触及供应商的速率限制,FX-Port 会把上一次的响应保留最多 15 分钟,所以数据可能有这么旧。传入 force=true 可再次向供应商查询,例如刚充值之后。不要每次请求都这样做,因为供应商会限制用量的查询频率。如果供应商无法响应,您会拿到最近一次已知的数据,并标明数据已过时。
充值:先询问,再创建
充值需要两次调用。先列出可添加到这张 eSIM 的套餐,再用返回的套餐 ID 之一创建充值。请勿沿用原订单的套餐 ID,也不要留存充值套餐备用:旅客想充值时再重新查询。充值是为 eSIM 增加流量,不会替换剩余流量,因此请用 force=true 从用量接口读取结果。
无法充值的情况
供应商决定由哪些移动运营商服务某个国家,少数情况下会更换运营商。此时 eSIM 在到期前仍可继续使用,但其套餐无法再充值,充值套餐列表会返回为空。如果供应商拒绝充值,您的余额不会被动用,该次尝试会保存为一条失败的预订。响应为 supplier_error,您可以据此做出处理。
安装数据与安装步骤
获取安装数据的接口会在您需要时返回订单的二维码、激活数据、安装链接和安装步骤。安装说明接口则按操作系统返回步骤,语言由您指定。这些步骤是供应商提供的通用文字,请与订单的二维码放在一起展示。两者都只能展示给持有该 eSIM 的旅客。
预订、充值记录与归属校验
预订列表返回您的代理商在密钥所属环境中的 eSIM 订单,按时间由新到旧排列。可按预订编号、旅客姓名、邮箱地址、套餐名称或 ICCID 搜索。充值不在该列表中,请通过某张 eSIM 的充值记录查看。在围绕某个预订编号构建链接或页面之前,可用归属校验确认它是否属于您。其他代理商的预订和不存在的预订会得到相同的响应。
全部 eSIM 接口,含 OpenAPI、Postman 与代码示例
eSIM API 在同一个 Base URL 下共有 12 个接口。点击任意一行即可打开该调用的参考页面,其中有请求字段、响应、错误,以及 9 种语言的示例。
| 接口 | 用途 | 密钥 |
|---|---|---|
GET /api/v1/esims/packages | 列出某个国家或地区的套餐,价格已按您代理商的币种计算。请务必传入国家:完整目录非常大,且不分页。 | 只读 |
GET /api/v1/esims/compatible-devices | 已知支持 eSIM 的设备,供旅客购买前核对。 | 只读 |
POST /api/v1/esims/orders | 根据套餐为旅客创建一个 eSIM,并在供应商处开通。在正式环境中会从您的代理商余额扣款。 | 读写 |
GET /api/v1/esims/topup-packages/{iccid} | 当前可以添加到某一个具体 eSIM 的套餐。列表为空表示目前无法充值。 | 只读 |
POST /api/v1/esims/topups | 为已有的 eSIM 添加充值套餐。在正式环境中,只有供应商接受后才会从余额扣款。 | 读写 |
GET /api/v1/esims/usage/{iccid} | 一个 eSIM 的剩余流量、总额度、状态和到期时间。响应的数据最多可能有 15 分钟的延迟。 | 只读 |
GET /api/v1/esims/usage/bulk | 一次调用查询最多 50 个 eSIM 的用量,每个 ICCID 一条,各带自己的状态。 | 只读 |
GET /api/v1/esims/bookings | 您代理商的 eSIM 订单,按时间倒序,支持搜索和分页。充值不在此列表中。 | 只读 |
GET /api/v1/esims/bookings/{iccid}/topup-history | 某一个 eSIM 的充值记录,按时间倒序。没有充值时列表为空。 | 只读 |
GET /api/v1/esims/bookings/{booking_id}/verify | 在围绕某个预订编号构建链接或页面之前,确认它属于您的代理商。 | 只读 |
GET /api/v1/esims/bookings/{booking_id}/setup-instructions | 订单的二维码、激活数据、安装链接和安装步骤,以 JSON 返回。 | 只读 |
GET /api/v1/esims/instructions/{iccid} | 某个 ICCID 的分步安装说明,按操作系统分类,语言由您指定。 | 只读 |
OpenAPI 3.1
一个文件描述了所有接口和所有字段。您可以据此生成客户端、导入 API 工具,或交给编程助手使用。
Postman 集合
eSIM 集合涵盖全部 12 个接口,文件夹按销售的各个阶段划分。集合中不含密钥,请填入您自己的密钥。
9 种语言的代码示例
每个参考页面都以 cURL、Node.js、Python、PHP、Go、Java、C#、Swift 和 Kotlin 给出请求示例,由 OpenAPI 描述生成。每个 eSIM 请求在这些语言中都有对应示例。
面向 AI 助手的文档
llms.txt 和 llms-full.txt 以纯文本形式向 AI 助手提供文档;在任意文档页面的地址后加上 .md,即可以 Markdown 格式阅读。
为生产环境而建:密钥、错误、重试与缓存
eSIM 对接是否可靠,要在生产环境中看身份验证、失败处理、重试、缓存和速率限制。以下是各方面需要了解的内容。
一个 Base URL,两类密钥
每个请求都在 Authorization 请求头中以 Bearer 令牌的形式携带密钥。前缀表明密钥适用的环境:沙盒为 fxp_test_,正式环境为 fxp_live_。两者访问同一个 Base URL,每个 JSON 响应都会在 environment 中说明所属环境。密钥应保存在您的服务器上,不要放进浏览器或应用。FX-Port 只保存每个密钥的哈希值,所以丢失的密钥无法再次显示。密钥的变更可能需要几分钟才能生效。
哪种密钥可以做哪些调用
只读密钥可以列出套餐、检查设备、查询用量和读取预订。创建订单或充值需要读写密钥,只读密钥尝试这样做会收到 read_write_permission_required。任何密钥都可以锁定到一组 IP 地址,来自其他地址的调用会收到 ip_not_allowed。凡是代码只负责展示结果的地方,请使用只读密钥。
所有失败使用同一种错误格式
失败的调用会返回 errors 数组。其第一项包含稳定的 code、说明该如何应对的 type,以及出错的 field。请判断错误码,不要依赖错误信息文本。机票、酒店、eSIM 和账户共用的错误列表共有 180 个错误码。供应商拒绝订单或充值时,响应为 supplier_error,并带有供应商自己的 HTTP 状态。不会扣款,并会保存一条失败的预订。刚重启之后,目录和设备列表可能返回 202,且 success 为 false:请等待几秒后再次请求。
重试时避免重复下单
FX-Port 没有幂等键,因此也没有可发送的 Idempotency-Key 请求头。读取类请求可以放心重复;创建订单或充值则不行。FX-Port 会用 409 和 duplicate_request 拒绝明显的重复请求:即为一个仍在处理中的套餐再次下单。在正式环境中,如果同一套餐在您的代理商最近一笔已确认订单后的 60 秒内再次下单,无论旅客是谁,也会被拒绝。订单通常几秒内返回,但供应商较慢时可能需要久得多。如果调用超时或连接中断,请不要立即重新提交订单,因为 eSIM 可能已经创建。请用旅客的邮箱地址,通过预订列表的 search 参数查找,只有确认没有创建时才重新下单。
没有 Webhook:需要时主动查询
无论在沙盒还是正式环境,FX-Port 都不会调用您的任何 URL。下单后会直接返回 eSIM,若供应商尚未返回,则返回 PENDING- 占位符。其他信息需要时由您主动查询:安装数据、用量或预订列表。
缓存与速度
用量最多缓存 15 分钟,充值套餐列表缓存几分钟,加上 force=true 可跳过缓存。不带筛选的完整目录响应非常大且不分页,所以请传入 country,并通过 Accept-Encoding: gzip 接收压缩数据。套餐标题按 locale 参数指定的语言返回;首次请求英语以外的语言会较慢,因为文本要在此时获取,之后才会缓存。
速率限制
限制按每个密钥、每分钟分两组计数,每个响应都带有 ratelimit-limit 和 ratelimit-remaining,让您在被拒绝之前先放慢请求。收到 429 后,请等待 Retry-After 给出的秒数,再按指数退避加抖动重试。同一密钥或同一代理商同时进行的订单或充值过多,会返回 concurrency_limit_exceeded。
| 调用类型 | 每分钟 |
|---|---|
| 创建订单和充值 | 1,200 |
| 其他所有 eSIM 接口 | 6,000 |
目前,同时进行的订单和充值数量上限处于观察模式:只计数,不强制执行,因此暂时不会因此拒绝任何请求,正式强制执行的日期会在更新日志中公布。请把这些数字当作当前的策略,而不是承诺。沙盒密钥的每分钟计数与正式密钥相同,所以压力测试请分时段安排,不要在循环中反复运行。
命名与版本
请求字段使用 snake_case,例如 first_name 和 agent_id。早期的写法仍然可用:user_id 会被当作 agent_id 读取,并附带一条 warnings;customer_email 会被当作 email 接受,且不附带警告。许多响应会把旧键与其 snake_case 对应项以相同的值并列保留,例如 order_id 与 code;加上 ?naming=snake 则只返回 snake_case 键。版本号位于路径中,即 /api/v1:不兼容的变更会使用新的前缀,而新增字段和写法属于向后兼容的追加。
先在免费沙盒中开始,再上线
本页的所有内容目前都能在沙盒中运行。沙盒密钥可自助获取,免费,而且绝不会动用您的真实余额。
创建账户
注册 FX-Port 账户。沙盒密钥免费,沙盒预订不会产生任何实际费用。
创建沙盒密钥
在仪表盘的 API 页面创建以
fxp_test_开头的密钥。只读密钥可以搜索和读取数据;读写密钥还可以创建测试预订。发送第一个请求
只有一个 Base URL,密钥以 Bearer 令牌的形式放在
Authorization请求头中。快速入门会带您完成第一次调用。准备就绪后切换到正式环境
换上
fxp_live_密钥即可。正式环境在您的代理商获批后可用;在此之前,API 会返回403 agency_not_active。真实预订从已充值的余额中支付。
用 cURL 发出的第一个目录请求。文档用每一种受支持的语言都给出了同样的请求。
请求
curl 'https://api.fx-port.com/api/v1/esims/packages?country=france&type=local' \
-H "Authorization: Bearer fxp_test_YOUR_KEY"沙盒能做什么
在沙盒中下单,会在供应商处创建一个真实的测试 eSIM,激活码是虚拟的,因此无法安装到手机上。不会扣款,响应里也会明确说明:balanceDebited、transactionCreated 和 payoutCreated 全部为 false。充值响应中的余额为零。用量是固定的:新 eSIM 显示完整流量额度,状态为 ACTIVE。沙盒预订只对沙盒密钥可见。
只有正式环境才能测试的内容
余额检查、重复检查和余额锁只在正式环境运行。因此,返回 402 与 insufficient_balance、在 60 秒内再次订购同一套餐时返回 409、以及返回 503 与 balance_lock_timeout 这几种响应,在沙盒中永远不会出现。请对照错误码列表来编写它们的处理逻辑。
正式上线
正式密钥与代理商审批挂钩:在您的代理商通过审批之前,正式密钥会返回 agency_not_active,而沙盒密钥仍可继续使用。正式订单从已充值的代理商余额中支付。上线时更换的是密钥而不是代码,因为沙盒和正式环境共用同一个 Base URL。
谁在用 eSIM API 搭建业务
不同的使用者侧重同一组调用中的不同部分。
销售行程的旅行社
旅行社在机票和酒店之外一并销售 eSIM。下单时使用旅客本人的邮箱,由供应商发送二维码和分享链接;旅客询问时,旅行社再读取用量。每个套餐都会返回佣金比例,agent_id 则记录是贵代理商的哪位用户下的单。
OTA 与交易平台
交易平台首先搭建浏览页面:一次一个国家,本地套餐或区域套餐,每个套餐的网络和流量额度,以及旅客购买前的设备检查。列出旅客所有 eSIM 的页面,一次调用最多可读取其中 50 个的用量。
旅游科技与企业团队
旅游科技团队和企业差旅部门会自建预订工具。上手所需的一切都是公开的:OpenAPI 文件、Postman 集合,以及 AI 编程助手可以读取的纯文本文档,所以第一个原型就基于真实的接口约定。
eSIM API 常见问题
FX-Port eSIM API 是什么,适合谁使用?
它是一个 REST 接口,让旅行社、OTA 或转售商通过一次对接销售旅行流量 eSIM。您可以列出某个目的地的套餐,价格按代理商币种显示,并为旅客下单。随后把二维码交给旅客,跟踪用量并充值。全部采用基于 HTTPS 的 JSON,并有 OpenAPI 文件描述每一个接口。
有免费沙盒吗?如何获取密钥?
有。注册 FX-Port 账户,打开仪表盘里的 API 页面,创建沙盒密钥即可,您可以自助完成,不收费。在沙盒中下单会创建一个带虚拟激活码的测试 eSIM,无法安装到手机上,也不会从您的余额扣款。沙盒和正式环境共用同一个 Base URL,所以您测试的代码就是上线的代码。
上线需要满足什么条件?
您的代理商必须通过审批,正式密钥才能使用。在此之前正式密钥会被拒绝,沙盒密钥仍可继续使用。正式订单从已充值的代理商余额中支付,沙盒绝不会动用余额;任何密钥都可以锁定到一组 IP 地址。切换时只需更换密钥,每个响应都会注明它来自哪个环境。
可以销售哪些国家和套餐?
套餐覆盖 190 个以上的国家,区域套餐归在各自的地区下。套餐在流量额度、有效期、网络以及能否充值方面各不相同。请从目录响应中读取这些字段和套餐 ID,不要写死在代码里。请一次只查询一个国家,因为完整目录的响应非常大。另有单独的设备列表,可在销售前核对旅客的手机。
旅客如何收到 eSIM?
订单响应中包含二维码、它的激活字符串、适用于 iPhone 的安装链接和一个分享页面,您可以在自己的页面中展示。eSIM 创建后,FX-Port 还会请供应商把二维码和分享链接发到旅客的邮箱。如果供应商尚未返回 eSIM,订单依然存在,之后可通过安装数据调用获取这些数据。
价格、佣金和我的余额之间是什么关系?
每个套餐都以您代理商的币种带有这些金额字段:计入佣金前的基础价格、向旅客收取的总价、从您余额扣除的金额,以及您的佣金比例。您的佣金等于总价减去扣款金额。请直接读取这些字段,不要自行重新计算,因为扣款金额因代理商而异。在沙盒中不会扣款;在正式环境中,扣款金额会从您的代理商余额中划出。
eSIM 可以充值吗?怎么查看还剩多少?
可以,分两步调用:先列出可以添加到该 eSIM 的套餐,再用其中一个创建充值。充值并不总是可行,因为供应商可能更换某个国家的运营商。这时列表为空,意味着旅客需要一个新的 eSIM。用量最多缓存 15 分钟,所以请把它当作近期数据,一次调用最多可读取 50 个 eSIM。
失败如何返回?有 Webhook 吗?
失败以统一格式返回,并带有稳定的错误码;您的程序应该判断的是这个错误码,而不是消息文本。读取类请求可以放心地按退避策略重复。创建订单或充值则不同:系统没有幂等键,所以超时之后请先查询该预订,再决定是否重试。FX-Port 会拒绝明显的重复订单,例如在正式环境中 60 秒内对同一套餐下了两次单。系统没有 Webhook。
可以通过 API 取消 eSIM 订单或退款吗?
不可以。API 没有用于取消 eSIM 订单或退款的接口。每个订单都会创建且只创建一个 eSIM,所以请在发送之前核对套餐和旅客信息。如果供应商拒绝了订单,则不会扣款,并会保存一条失败的预订。如果某个具体订单出了问题,请联系开发团队,并附上它的预订编号。
开始需要准备什么?有哪些工具可用?
您需要一个沙盒密钥和一个 HTTP 客户端。文档另外提供 OpenAPI 3.1 文件、Postman 集合、9 种语言的代码示例,以及供 AI 助手读取的纯文本导出。请把密钥保存在您的服务器上,不要放在浏览器或应用里。耗时更长的是您自己的结账流程、向旅客交付二维码以及错误处理。
FX-Port 提供的其他 API
eSIM 通常伴随着一次出行。机票和酒店与 eSIM 使用同样的密钥、同样的错误响应体和同样的约定,它们的预订也记录在同一个账户历史中。
搜索、报价并预订机票,支付保留订单,取消订单,查询机票。
查找酒店、查询价格、预先预订(pre-book)与预订,查看凭证并取消。
- 接送机 API预览
为接送机报价、预订、跟踪和取消。
不想写代码?代理商也可以在 FX-Port 仪表盘中销售 eSIM。 在仪表盘中销售 eSIM