ECHOROAM WHOLESALE
API Documentation
Everything you need to buy eSIMs programmatically: catalog, orders, top-ups, cancellations, wallet balance and signed webhooks. The API is JSON over HTTPS with a prepaid USD wallet.
1. Base URL & Authentication
All requests go to:
https://echoroam.com/api/open/v1
Create an API key in your wholesale dashboard → API & Webhooks. Send it as a Bearer token on every request. Keys are shown once at creation — store them securely.
Authorization: Bearer wk_live_xxxxxxxxxxxxxxxxxxxxxxxx
| Error | Meaning |
|---|---|
401 UNAUTHORIZED | Missing, invalid or revoked API key |
403 SUSPENDED | Account suspended — contact support |
2. Response Envelope & Error Codes
Every response has the same envelope:
{
"success": true, // or false
"errorCode": null, // machine-readable string when success=false
"errorMsg": null, // human-readable detail when success=false
"obj": { ... } // payload (null on error)
}| errorCode | HTTP | Meaning |
|---|---|---|
INSUFFICIENT_BALANCE | 402 | Wallet balance too low — top up first |
PACKAGE_NOT_FOUND | 404 | Unknown packageCode / plan slug |
ORDER_NOT_FOUND | 404 | No order with that id/reference (or not yours) |
CANNOT_CANCEL | 409 | eSIM already activated/used, or order not in a cancellable state |
ORDER_FAILED / TOPUP_FAILED | 409 | Upstream fulfillment failed — wallet fully refunded |
INVALID_JSON / MISSING_PACKAGE | 400 | Request validation failed |
3. Idempotency
Every wallet-affecting call (order/create, esim/topup, esim/cancel) accepts a transactionId. Retrying with the same transactionId returns the original result — no duplicate charge, ever. Use unique ids per logical operation (e.g. your order id). Wallet entries are additionally guarded by unique idempotency keys at the ledger level, so a wallet debit or refund can never post twice.
4. Endpoints
POST /package/list
List the wholesale catalog or query specific plans. Prices are in 1/10000 USD units (e.g. 68900 = US$6.89).
| Field | Type | Description |
|---|---|---|
packageCode / slug | string | Query a specific plan. Automatically enriches the response with locationNetworkList, networks, and breakoutIp. |
packageCodes | string[] | Query a list of specific plan slugs (up to 50), all enriched with carrier networks and IP breakouts. |
locationCode | string | Destination filter — e.g. "japan", "europe". |
includeNetworks | boolean | Set to true on page queries (up to 25 plans) to enrich each plan with supported networks and breakouts. |
pageSize | number | Optional, default 500, max 500. |
pageNum | number | Optional, 1-based page number. |
curl -X POST https://echoroam.com/api/open/v1/package/list \
-H "Authorization: Bearer wk_live_xxx" -H "Content-Type: application/json" \
-d '{"packageCode":"JP_10_30"}'{
"success": true, "errorCode": null, "errorMsg": null,
"obj": {
"total": 1,
"packageList": [{
"packageCode": "JP_10_30", // plan slug — use in order/create
"slug": "JP_10_30",
"name": "Japan 10GB 30Days",
"price": 68900, // US$6.89 (1/10000 USD)
"priceUsd": 6.89,
"currencyCode": "USD",
"volume": 10737418240, // bytes
"duration": 30,
"durationUnit": "DAY",
"dataType": 1, // 1 = total bundle, 2 = per-day
"location": "Japan",
"locationCode": "Japan",
"speed": "4G/5G",
"breakoutIp": "Singapore", // traffic exit location
"ipExport": "Singapore",
"supportTopUpType": 2, // 1 = Non-reloadable, 2 = Data reloadable, 3 = Days reloadable
"supportTopUpDesc": "Data Reloadable for same area within validity",
"topupSupported": true,
"locationNetworkList": [
{
"locationName": "Japan",
"locationCode": "JP",
"operatorList": [
{ "operatorName": "NTT Docomo", "networkType": "4G/5G" },
{ "operatorName": "SoftBank", "networkType": "4G" }
]
}
]
}]
}
}POST /package/detail
Query detailed network specifications and IP breakouts for a specific plan. Also available via GET /package/detail?packageCode=....
| Field | Type | Description |
|---|---|---|
packageCode / slug * | string | Plan slug (e.g. "US_10_30"). |
curl -X POST https://echoroam.com/api/open/v1/package/detail \
-H "Authorization: Bearer wk_live_xxx" -H "Content-Type: application/json" \
-d '{"packageCode":"US_10_30"}'{
"success": true, "errorCode": null, "errorMsg": null,
"obj": {
"packageCode": "US_10_30",
"name": "United States 10GB 30Days",
"destination": "United States",
"dataGB": 10,
"days": 30,
"speed": "4G/5G",
"priceUsd": 8.50,
"price": 85000,
"breakoutIp": "United States", // Traffic exit IP location
"ipExport": "United States",
"topupSupported": true,
"supportTopUpType": 2, // 1 = Non-reloadable, 2 = Data reloadable, 3 = Days reloadable
"supportTopUpLabel": "Data Reloadable for same area within validity",
"networks": [
{
"country": "United States",
"operators": [
{ "name": "AT&T", "type": "5G" },
{ "name": "T-Mobile", "type": "5G" }
]
}
],
"locationNetworkList": [
{
"locationName": "United States",
"locationCode": "US",
"operatorList": [
{ "operatorName": "AT&T", "networkType": "5G" },
{ "operatorName": "T-Mobile", "networkType": "5G" }
]
}
],
"coverageCountries": [{ "country": "United States", "code": "US" }],
"activationNote": "Install within 180 days of purchase; validity starts on first network connection."
}
}POST /esim/order/create
Buy an eSIM with wallet credit. Fulfillment is usually seconds; poll esim/query or subscribe to webhooks for completion.
| Field | Type | Description |
|---|---|---|
packageCode * | string | Plan slug from /package/list (e.g. "JP_10_30"). |
transactionId | string | Your unique reference. Retries with the same id return the same order (idempotent). |
curl -X POST https://echoroam.com/api/open/v1/esim/order/create \
-H "Authorization: Bearer wk_live_xxx" -H "Content-Type: application/json" \
-d '{"packageCode":"JP_10_30","transactionId":"my-order-0001"}'{
"success": true, "errorCode": null, "errorMsg": null,
"obj": {
"orderNo": "B26100702130008", // order reference for esim/query
"orderId": "dfd1bf0a-…", // EchoRoam Wholesale order id
"transactionId": "my-order-0001",
"packageCode": "JP_10_30",
"status": "pending", // pending → fulfilled
"price": 68900,
"currencyCode": "USD"
}
}POST /esim/query
Fetch order + eSIM detail (QR code, LPA activation string, usage, status). Identifies the order by any of:
orderNo | string | The orderNo from order/create. |
orderId | string | The EchoRoam Wholesale order id. |
iccid | string | The eSIM's ICCID (returns your most recent matching order). |
{
"success": true,
"obj": {
"orderNo": "B26100702130008",
"orderId": "dfd1bf0a-…",
"transactionId": "my-order-0001",
"status": "fulfilled",
"packageCode": "JP_10_30",
"planName": "Japan 10GB 30Days",
"price": 68900, "currencyCode": "USD",
"createdAt": "2026-10-07T02:13:00.000Z",
"esim": {
"iccid": "8965012603310682656",
"imsi": "525016235337331",
"qrCodeUrl": "https://p.qrsim.net/xxx.png",
"shortUrl": "https://p.qrsim.net/xxx",
"ac": "LPA:1$rsp-eu.simlessly.com$97EF83…", // GSMA activation string
"smdpAddress": "rsp-eu.simlessly.com",
"activationCode": "97EF83…",
"esimStatus": "IN_USE",
"smdpStatus": "ENABLED",
"totalVolume": 10737418240,
"orderUsage": 2147483648,
"expiredTime": "2026-11-06T00:00:00+00:00"
}
}
}POST /esim/topup
Two modes. List mode: pass { list: true, ... } with an order identifier to enumerate the top-up packages available for that eSIM (prices in 1/10000 USD).Apply mode: pass packageCode from that list to debit the wallet and apply it.
| Field | Type | Description |
|---|---|---|
list | boolean | Set true to list eligible packages for the target eSIM instead of applying one. |
packageCode * | string | Apply mode: the top-up packageCode from list mode. |
orderNo / orderId / iccid | string | One identifier for the target eSIM. |
transactionId | string | Your unique reference (idempotent). |
# List eligible top-ups
curl -X POST .../esim/topup -H "Authorization: Bearer wk_live_xxx" \
-d '{"list":true,"iccid":"8965012606160373110"}'
# Apply one
curl -X POST .../esim/topup -H "Authorization: Bearer wk_live_xxx" \
-d '{"packageCode":"TOPUP_JC018","iccid":"8965012606160373110","transactionId":"t-001"}'POST /esim/cancel
Cancel an eSIM and refund the wallet. Only unactivated, unused eSIMs can be cancelled(supplier rule, verified live before cancelling). Activated or partially-used eSIMs returnCANNOT_CANCEL.
orderNo / orderId | string | Identifier for the order (same rules as esim/query). |
{
"success": true,
"obj": { "orderNo": "B26100702130008", "status": "cancelled", "refunded": 4400 }
}GET /balance
Wallet balance (USD) and the 10 most recent ledger entries.
curl https://echoroam.com/api/open/v1/balance -H "Authorization: Bearer wk_live_xxx"
5. Webhooks
Subscribe in dashboard → API & Webhooks. We POST signed JSON to your URL. Current event types: esim.fulfilled, webhook.test.
Verifying signatures
Every delivery carries an EchoRoam-Signature header in Stripe format:t=<unix seconds>,v1=<hmac> where the HMAC isHMAC-SHA256(secret, "t" + "." + rawBody). Reject timestamps older than 5 minutes.
// Node.js
import crypto from "crypto";
function verifySignature(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map(kv => kv.split("=")));
if (Math.abs(Date.now()/1000 - Number(parts.t)) > 300) return false;
const expected = crypto.createHmac("sha256", secret)
.update(parts.t + "." + rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
# Python
import hmac, hashlib, time
def verify(raw_body, header, secret):
parts = dict(kv.split("=") for kv in header.split(","))
if abs(time.time() - int(parts["t"])) > 300: return False
expected = hmac.new(secret.encode(), f'{parts["t"]}.{raw_body}'.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])Delivery & retries
Respond with any 2xx to acknowledge. Failed deliveries retry with exponential backoff (2m, 4m, 8m… up to 8 attempts). Event ids are unique — dedupe by id.
{
"id": "evt_9f1c…",
"type": "esim.fulfilled",
"created_at": "2026-10-07T03:00:00.000Z",
"data": {
"order_id": "dfd1bf0a-…",
"customer_txn_id": "my-order-0001",
"plan_slug": "JP_10_30",
"plan_name": "Japan 10GB 30Days",
"iccid": "8965012603310682656",
"price_usd": 6.89
}
}6. Wallet rules
| Currency | Wallets, plans and orders are in USD. Card top-ups are charged in AUD (EchoRoam is an Australian company) at the daily ECB rate, shown before payment. |
| Top-up limits | US$50 minimum for your first top-up; US$100 – US$10,000 for subsequent top-ups. Prepaid only — no credit terms. |
| Price list export | Download the full wholesale catalog (5,000+ plans) in CSV format: Download EchoRoam_Wholesale_Pricing.csv. |
| Refunds | Failed fulfillments and cancelled (unactivated) eSIMs refund automatically to the wallet, instantly. |
| Atomicity | Debits are balance-checked atomically; every wallet movement appears in the ledger with an idempotency key. |
Questions: [email protected] · Prices refresh with upstream supplier changes (at most a few hours).