新上線附有 OpenAPI 3.1 規格檔的 REST API 介面
適用旅行社、OTA 與轉售商的 eSIM API
透過單一 REST API 銷售旅遊 eSIM。依國家列出流量套餐,價格以貴旅行社的幣別顯示;為旅客下單、提供 QR 碼、追蹤用量並加購流量。Sandbox 與 Live 共用同一個 Base URL。先從免費的 Sandbox 金鑰開始;旅行社通過核准後,即可使用 Live 金鑰。
文件僅提供英文版本。
- 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, 讀取設定資料。
示範流程:內容主體取自文件中的範例,日期已順延以確保晚於今天,時間僅供示意。本頁不會送出任何請求。
找到套餐、下單、讀取設定資料
從單一國家的套餐開始,為旅客訂購一個,再讀取該訂單的設定資料。Sandbox 會建立啟用碼為假資料的測試 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。在 Sandbox 中,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,並回傳該訂單的 QR 碼、啟用資料、安裝連結與安裝步驟。只能顯示給持有該 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,並以 MB 回傳
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。在 Live 環境,供應商接受之後才會從餘額扣款。在 Sandbox 中,回應裡的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:完整目錄的回應非常龐大,而且沒有分頁。請從回應中讀取套餐編號,不要寫死在程式裡,因為目錄會變動。
請求
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,在 Live 環境會從旅行社餘額扣款。請保留回應中的 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 | QR 碼所編碼的啟用字串,以及 QR 碼圖片的連結。 |
data.sim.direct_apple_installation_url data.sim.sharing | 可在 iPhone 上開始安裝的連結,以及旅客可憑存取碼開啟 eSIM 的分享頁面。設定資料呼叫另外提供 Android 的連結。 |
PENDING- 開頭,待供應商提供後,即可從設定資料呼叫取得資料。把 eSIM 交給旅客
回應內含 QR 碼及其啟用字串,可顯示在您自己的畫面上。FX-Port 也會請供應商把 QR 碼與分享連結寄到旅客的電子郵件地址,您不必自行轉寄。設定資料呼叫之後可以再次取得相同資料,並附上安裝步驟。
請求
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 | 在 Live 環境訂購該套餐時,從旅行社餘額扣除的金額。請直接讀取,不要自行計算。 |
agency_commission commission_rate | 您的佣金費率,同時以文字與數字提供。訂單則改以金額回傳您的佣金。 |
在目錄範例中,旅客支付 468.84 DZD。在 Live 環境下單會從餘額扣除 418.60 DZD,差額 50.24 DZD 即為您的佣金。
費率依旅行社而定
每個套餐都會回傳佣金費率,而且各旅行社不盡相同。簽約的企業旅行社費率可能為零,此時總價等於扣款金額。
請讀取欄位,不要自行重算
扣款金額可能高於基本價格,因為其中包含專屬於貴旅行社的金額。因此請讀取 balance_debit_amount,切勿用 base_price 推算。price 一詞在目錄中指基本價格,但在訂單、加購套餐與設定資料中指零售價:請一律使用 base_price 與 total_price,就不會混淆。
以貴旅行社的幣別報價
不論供應商使用哪種貨幣,每筆金額都已換算為貴旅行社的幣別。回應也會顯示供應商幣別與匯率,方便您自行核對,不必再換算一次。
售後:用量、加購與設定資料
銷售不會在下單後結束。這些呼叫能讓旅客保持連線,且僅適用於貴旅行社在與金鑰相同環境中訂購的 eSIM。
讀取用量,以及資料的新舊程度
用量會回傳單張 eSIM 的剩餘流量、總量、狀態與到期日,一次呼叫最多可查詢 50 張。為了保護供應商的速率限制,FX-Port 會保留最近一次的回應最多 15 分鐘,所以數字可能有這麼舊。帶上 force=true 可重新向供應商查詢,例如剛加購之後。請勿每個請求都這麼做,因為供應商會限制讀取用量的頻率。若供應商無法回應,您會取得最後已知的數字,並標示為過期。
加購:先查詢,再建立
加購需要兩次呼叫。先列出可加購到這張 eSIM 的套餐,再用回傳的其中一個套餐編號建立加購。切勿沿用原訂單的套餐編號,也不要保存加購套餐留待日後使用:旅客想加購時請重新查詢。加購是為 eSIM 增加流量,不會取代剩餘的流量,因此請帶上 force=true,從用量讀取結果。
無法加購的情況
供應商決定由哪些行動網路業者服務一個國家,少數情況下會更換業者。此時 eSIM 仍可使用到有效期限屆滿,但它的套餐已無法再加購,加購套餐清單會是空的。若供應商拒絕加購,您的餘額不會變動,該次嘗試會儲存為失敗的訂單。回應為 supplier_error,您可以據此處理。
設定資料與安裝步驟
設定資料呼叫可隨時回傳訂單的 QR 碼、啟用資料、安裝連結與安裝步驟。安裝說明呼叫則依作業系統回傳步驟,並使用您指定的語言。這些步驟是供應商提供的通用文字,請與訂單的 QR 碼並列顯示。兩者都只能顯示給持有該 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,並向供應商開通。在 Live 環境會從旅行社餘額扣款。 | 讀寫 |
GET /api/v1/esims/topup-packages/{iccid} | 目前可加購到某張 eSIM 的套餐。清單為空表示目前無法加購。 | 唯讀 |
POST /api/v1/esims/topups | 為既有的 eSIM 加購套餐。在 Live 環境,供應商接受之後才會從餘額扣款。 | 讀寫 |
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 | 訂單的 QR 碼、啟用資料、安裝連結與安裝步驟,以 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_ 用於 Sandbox,fxp_live_ 用於 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 拒絕明顯的重複:即針對仍在處理中的套餐再次送出的訂單。在 Live 環境,若同一套餐在貴旅行社最近一筆已確認訂單後 60 秒內再次下單,也會被拒絕,不論旅客是誰。訂單通常幾秒內就會回應,但供應商較慢時可能需要久得多。如果呼叫逾時或連線中斷,請不要立刻重新送出訂單,因為 eSIM 可能已經建立。請用訂單清單的 search 參數,以旅客的電子郵件地址查詢,確定沒有建立時才再次下單。
沒有 Webhook:需要時再主動查詢
FX-Port 不會呼叫您的任何網址,無論在 Sandbox 或 Live 都一樣。訂單會直接回應 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 |
目前,同時進行的訂單與加購數量上限處於觀察模式:僅計數而不強制執行,因此尚不會因此拒絕任何請求,變更日誌會公告開始強制執行的日期。請將這些數字視為目前的政策,而非承諾。Sandbox 金鑰的每分鐘計數與 Live 金鑰相同,因此請排定負載測試,不要在迴圈中連續執行。
命名與版本
請求欄位採用 snake_case,例如 first_name 與 agent_id。舊寫法仍可使用:user_id 會被讀作 agent_id 並加上一筆 warnings,而 customer_email 會被接受為 email,不會附加警告。許多回應會在 snake_case 欄位旁保留值相同的舊鍵,例如 code 旁的 order_id,而 ?naming=snake 則只回傳 snake_case 鍵。版本位於路徑中,即 /api/v1:不相容的變更會使用新的前綴,新增欄位與寫法則屬於向下相容。
先在免費 Sandbox 開始,再上線
本頁的所有內容目前都能在 Sandbox 運作。Sandbox 金鑰可自行申請且免費,不會動用您的實際餘額。
建立帳號
註冊 FX-Port 帳號。Sandbox 金鑰免費提供,Sandbox 中的預訂不會產生任何實際費用。
建立 Sandbox 金鑰
在儀表板的 API 頁面建立以
fxp_test_開頭的金鑰。唯讀金鑰可搜尋與讀取資料;讀寫金鑰還能建立測試預訂。送出第一個請求
所有請求共用同一個 Base URL,金鑰以 Bearer 權杖的形式放在
Authorization標頭中。快速入門會帶您完成第一次呼叫。準備好後切換到 Live
改用
fxp_live_金鑰即可。旅行社通過核准後,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"Sandbox 環境的作用
在 Sandbox 下單,會在供應商端建立一張真實的測試 eSIM,啟用碼為假資料,因此無法安裝在手機上。不會扣款,回應也會說明:balanceDebited、transactionCreated 與 payoutCreated 全部為 false。加購回應的餘額為零。用量是固定的:新的 eSIM 顯示完整總量與 ACTIVE 狀態。Sandbox 的訂單只有 Sandbox 金鑰看得到。
只有 Live 能測試的項目
餘額檢查、重複檢查與餘額鎖定只在 Live 執行。因此 402 搭配 insufficient_balance、60 秒內重複訂購同一套餐的 409,以及 503 搭配 balance_lock_timeout,都不會在 Sandbox 出現。請依錯誤代碼清單撰寫這些情況的處理方式。
正式上線
Live 金鑰與旅行社核准綁定:在您的旅行社通過核准之前,Live 金鑰會回應 agency_not_active,而 Sandbox 金鑰仍可繼續使用。Live 訂單從已儲值的旅行社餘額支付。上線只需更換金鑰,不必更改程式碼,因為 Sandbox 與 Live 共用同一個 Base URL。
誰在用 eSIM API 開發
不同的使用者各自倚重同一組呼叫的不同部分。
銷售行程的旅行社
旅行社在機票與飯店之外一併銷售 eSIM。他們以旅客本人的電子郵件地址下單,由供應商寄出 QR 碼與分享連結,並在旅客詢問時讀取用量。佣金費率會隨每個套餐回傳,而 agent_id 會記錄是貴旅行社的哪位使用者下的單。
OTA 與市集平台
市集平台會先建立瀏覽頁面:一次一個國家、當地或區域套餐、每個套餐的網路與流量總量,以及旅客購買前的裝置檢查。列出旅客 eSIM 的畫面,可以一次呼叫讀取最多 50 張的用量。
旅遊科技與企業團隊
旅遊科技團隊與企業差旅部門會自行建立訂購工具。開始所需的一切都是公開的:OpenAPI 檔案、Postman 集合,以及 AI 程式開發助理可以讀取的純文字文件,讓第一個原型就以真實的合約為基礎。
eSIM API 常見問題
FX-Port eSIM API 是什麼?適合誰使用?
這是一個 REST API,讓旅行社、OTA 或轉售商透過一次串接銷售旅遊流量 eSIM。您可以列出某個目的地的套餐,價格以貴旅行社的幣別顯示,並為旅客訂購一個。您可以把 QR 碼交給旅客、追蹤用量並加購流量。一切都是透過 HTTPS 傳輸的 JSON,並有一份 OpenAPI 檔案描述每個端點。
有免費的 Sandbox 嗎?如何取得金鑰?
有。建立 FX-Port 帳號,開啟儀表板的 API 頁面並建立 Sandbox 金鑰;您可以自行完成,不需費用。在 Sandbox 下單會建立啟用碼為假資料的測試 eSIM,因此無法安裝在手機上,也不會從餘額扣款。Sandbox 與 Live 共用同一個 Base URL,所以您測試的程式碼就是上線的程式碼。
正式上線需要什麼?
旅行社必須先通過核准,Live 金鑰才能使用。在那之前,Live 金鑰會被拒絕,而 Sandbox 金鑰仍可繼續使用。Live 訂單從已儲值的旅行社餘額支付,Sandbox 絕不會動用該餘額,而且任何金鑰都可以鎖定在一份 IP 位址清單上。切換時只需更換金鑰;每個回應都會說明它來自哪個環境。
我可以銷售哪些國家與套餐?
提供 190 個以上國家的套餐,區域套餐則列在所屬區域下。各套餐的流量總量、有效期間、網路,以及能否加購都不相同。請從目錄回應中讀取這些欄位與套餐編號,不要寫死在程式裡。請一次查詢一個國家,因為完整目錄的回應非常龐大。另有獨立的裝置清單,可在銷售前檢查旅客的手機。
旅客如何收到 eSIM?
訂單回應內含 QR 碼、啟用字串、iPhone 的安裝連結與分享頁面,您可以顯示在自己的畫面上。eSIM 一建立,FX-Port 也會請供應商把 QR 碼與分享連結寄到旅客的電子郵件地址。若供應商尚未回傳 eSIM,訂單仍然成立,之後可從設定資料呼叫取得資料。
價格、佣金與我的餘額如何搭配?
每個套餐都以貴旅行社的幣別提供這些金額欄位:計入佣金前的基本價格、向旅客收取的總價、從餘額扣除的金額,以及您的佣金費率。您的佣金就是總價減去扣款金額。請讀取這些欄位,不要自行重算,因為扣款金額是依旅行社而定。在 Sandbox 不會扣款;在 Live,扣款金額會從旅行社餘額扣除。
可以為 eSIM 加購流量嗎?如何查看剩餘流量?
可以,需要兩次呼叫:先列出可加購到該張 eSIM 的套餐,再用其中一個建立加購。加購不一定都能進行,因為供應商可能更換某個國家的網路業者;此時清單為空,表示旅客需要新的 eSIM。用量最多快取 15 分鐘,請把它當作近期的數字,且一次呼叫最多可讀取 50 張 eSIM。
失敗如何回報?有 Webhook 嗎?
失敗會以統一格式回傳,並附上固定的錯誤代碼,您的程式應判斷該代碼,而不是訊息文字。讀取可以搭配退避安全地重複。建立訂單或加購則不同:沒有冪等鍵,所以逾時後請先查詢該訂單,再決定是否重試。FX-Port 會拒絕明顯的重複,例如在 Live 環境 60 秒內對同一套餐下兩次單。沒有 Webhook。
可以透過 API 取消或退款 eSIM 訂單嗎?
不行。API 沒有取消或退款 eSIM 訂單的端點。每筆訂單都只會建立一張 eSIM,因此送出前請先確認套餐與旅客資料。若供應商拒絕訂單,不會扣款,並會儲存一筆失敗的訂單。若特定訂單有問題,請寫信給開發團隊,並附上其訂單編號。
開始需要準備什麼?有哪些工具可用?
您需要一把 Sandbox 金鑰和一個 HTTP 用戶端。文件另外提供 OpenAPI 3.1 檔案、Postman 集合、9 種語言的程式碼範例,以及供 AI 助理使用的純文字匯出檔。請把金鑰放在您的伺服器上,絕不能放在瀏覽器或應用程式裡。比較花時間的部分是您自己的結帳流程、把 QR 碼送到旅客手上,以及錯誤處理。
FX-Port 提供的其他 API
eSIM 通常搭配旅程一起使用。機票與飯店使用與 eSIM 相同的金鑰、相同的錯誤內容與相同的慣例,其訂單也記錄在同一份帳號歷史中。
搜尋、報價與預訂機票,支付保留中的訂單、取消訂單並讀取機票。
搜尋飯店、查詢房價、預先預訂與預訂,讀取住宿憑證並取消訂單。
- 機場接送 API預覽
報價、預訂、追蹤與取消機場接送。
不想寫程式?旅行社也可以從 FX-Port 儀表板銷售 eSIM。 從儀表板銷售 eSIM