Skip to content
EchoRoam Wholesale

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
ErrorMeaning
401 UNAUTHORIZEDMissing, invalid or revoked API key
403 SUSPENDEDAccount 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)
}
errorCodeHTTPMeaning
INSUFFICIENT_BALANCE402Wallet balance too low — top up first
PACKAGE_NOT_FOUND404Unknown packageCode / plan slug
ORDER_NOT_FOUND404No order with that id/reference (or not yours)
CANNOT_CANCEL409eSIM already activated/used, or order not in a cancellable state
ORDER_FAILED / TOPUP_FAILED409Upstream fulfillment failed — wallet fully refunded
INVALID_JSON / MISSING_PACKAGE400Request 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).

FieldTypeDescription
packageCode / slugstringQuery a specific plan. Automatically enriches the response with locationNetworkList, networks, and breakoutIp.
packageCodesstring[]Query a list of specific plan slugs (up to 50), all enriched with carrier networks and IP breakouts.
locationCodestringDestination filter — e.g. "japan", "europe".
includeNetworksbooleanSet to true on page queries (up to 25 plans) to enrich each plan with supported networks and breakouts.
pageSizenumberOptional, default 500, max 500.
pageNumnumberOptional, 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=....

FieldTypeDescription
packageCode / slug *stringPlan 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.

FieldTypeDescription
packageCode *stringPlan slug from /package/list (e.g. "JP_10_30").
transactionIdstringYour 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:

orderNostringThe orderNo from order/create.
orderIdstringThe EchoRoam Wholesale order id.
iccidstringThe 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.

FieldTypeDescription
listbooleanSet true to list eligible packages for the target eSIM instead of applying one.
packageCode *stringApply mode: the top-up packageCode from list mode.
orderNo / orderId / iccidstringOne identifier for the target eSIM.
transactionIdstringYour 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 / orderIdstringIdentifier 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

CurrencyWallets, 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 limitsUS$50 minimum for your first top-up; US$100 – US$10,000 for subsequent top-ups. Prepaid only — no credit terms.
Price list exportDownload the full wholesale catalog (5,000+ plans) in CSV format: Download EchoRoam_Wholesale_Pricing.csv.
RefundsFailed fulfillments and cancelled (unactivated) eSIMs refund automatically to the wallet, instantly.
AtomicityDebits 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).