API v1 · Stable

eSIM API for your business

Plug global travel eSIMs into your product with a single REST call. Prepay a wallet, hit POST /purchase, get a QR code back. No merchant onboarding, no per-order checkout, no telecom deals.

Instant provisioning

QR + activation code returned in seconds via REST.

Prepaid wallet

Top up once, then every purchase is one API call.

One price, one plan

USD-only. No FX, no surge, no tiers.

Overview

The esimreseller.io API is a wallet-funded, REST/JSON eSIM procurement service. You top up a USD balance from your reseller panel; every POST /purchase call debits it and provisions an eSIM through our upstream carrier. Prices are USD-only and shown as a single number per plan — what you see is what you pay.

Base URL: https://esimreseller.io/api/v1
Content type: application/json
Currency: USD.

Authentication

Every request needs an Authorization: Bearer sk_live_… header. Create keys in your reseller panel under API & Wallet. Keys carry read and/or write scopes (default: both). Rotate any key with the Revoke button in the panel and mint a new one — no downtime.

Authorization: Bearer sk_live_your_key_here

Rate limits. 60 write requests / minute per key (purchase); 120 requests / minute per key on read endpoints. When exceeded, the API returns 429 with a retry_after_seconds field.
Key safety. Never commit keys to source control. Never expose them in a browser bundle — keys carry full purchase authority for your wallet.

Quickstart

  1. Create an API key in the reseller panel.
  2. Top up your wallet (min $50) with the Top up button.
  3. List the catalog to find a product_id.
  4. POST a purchase, get a QR back.

1. List the catalog

curl https://esimreseller.io/api/v1/catalog \
  -H "Authorization: Bearer sk_live_…"
// JavaScript
const r = await fetch("https://esimreseller.io/api/v1/catalog", {
  headers: { Authorization: "Bearer sk_live_…" },
});
const { items } = await r.json();

2. Buy an eSIM

curl -X POST https://esimreseller.io/api/v1/purchase \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "product_id": "b3c…-uuid",
    "quantity": 1,
    "customer_email": "buyer@example.com",
    "reference": "order-42"
  }'

Response (201 Created):

{
  "orders": [{
    "order_number": "ES-8F2A1C",
    "status": "fulfilled",
    "esim": {
      "qr_code_url": "https://…/qr.png",
      "activation_code": "LPA:1$smdp.example$ABC123…",
      "smdp_address": "smdp.example",
      "iccid": "89012345678901234567"
    }
  }],
  "total_usd": 8.50,
  "balance_usd": 91.50
}

GET /catalog

List every plan available to your account, priced per unit.

Query params: country (ISO 3166 alpha-2, e.g. SA) — filter to plans covering that country.

Response fields

FieldTypeDescription
product_idstring (uuid)Pass to /purchase.
namestringHuman-readable plan name.
regionstringRegion label or comma-separated country codes.
countriesstring[]ISO alpha-2 codes covered.
data_amount_mbintegerBundle size in MB. Null when unlimited.
data_unlimitedbooleanTrue for uncapped plans.
validity_daysintegerDays from first activation.
networksobject[]Local carriers used, when known.
price_usdnumberYour unit price. All-in.

POST /purchase

Debits your wallet and provisions quantity eSIMs. Returns as soon as the upstream carrier confirms; if a QR isn't ready in the initial response, poll GET /orders/:order_number.

Request body

FieldTypeDescription
product_idstring (uuid), requiredFrom /catalog.
quantityinteger 1–10, default 1Number of eSIMs to order.
customer_emailstring, optionalEnd-customer email (recorded on the order).
referencestring ≤128, optionalYour internal reference — echoed back in order metadata.

Headers

FieldTypeDescription
AuthorizationBearer sk_live_…, requiredAPI key.
Idempotency-Keystring ≤128, recommendedReplaying the same key returns the original response and never re-debits.

Response fields

FieldTypeDescription
orders[]object[]One entry per unit ordered.
orders[].order_numberstringUse with /orders/:order_number for polling.
orders[].statusstringpaid | fulfilled | failed. Later reads may also return refunded.
orders[].esimobject | nullQR + activation fields when ready, otherwise null.
total_usdnumberDebited from your wallet.
balance_usdnumberWallet balance after this call.

Idempotent retry example

Network dropped mid-response? Replay with the same key — you get the original result, no second charge.

# 1st call — succeeds server-side but response lost
curl -X POST https://esimreseller.io/api/v1/purchase -H "Idempotency-Key: 6f8e…" …
# 2nd call, same key — same 200 response, no new order/debit
curl -X POST https://esimreseller.io/api/v1/purchase -H "Idempotency-Key: 6f8e…" …

GET /orders/:order_number

Poll for QR/activation details.

curl https://esimreseller.io/api/v1/orders/ES-8F2A1C \
  -H "Authorization: Bearer sk_live_…"
{
  "order": {
    "order_number": "ES-8F2A1C",
    "status": "fulfilled",
    "esim": {
      "qr_code_url": "https://…/qr.png",
      "activation_code": "LPA:1$smdp.example$ABC…",
      "smdp_address": "smdp.example",
      "iccid": "89012…"
    },
    "product": { "name": "Japan 5 GB / 30d" },
    "created_at": "2026-07-01T12:00:00Z"
  }
}

Order statuses

FieldTypeDescription
pendingstringCreated, payment not confirmed yet (hosted checkout only).
paidstringPaid; eSIM is being provisioned. esim is null until it is ready.
fulfilledstringProvisioned. esim carries the QR and activation details.
failedstringPayment failed or expired. Nothing was charged.
refundedstringRefunded by our team. Wallet purchases are credited back (see the refund ledger type); esim is no longer valid.

GET /wallet

{ "balance_usd": 91.50, "currency": "USD", "low_balance_threshold_usd": 25 }

GET /wallet/transactions

Paged ledger, newest first. Query params: limit (1–200, default 50), before (ISO timestamp for pagination).

{
  "items": [
    { "type": "purchase", "amount_usd": -8.50, "balance_after_usd": 91.50,
      "order_number": "ES-8F2A1C", "note": null,
      "created_at": "2026-07-01T12:00:01Z" },
    { "type": "topup", "amount_usd": 100, "balance_after_usd": 100,
      "order_number": null, "note": "Stripe top-up",
      "created_at": "2026-07-01T11:58:00Z" }
  ],
  "has_more": false,
  "next_before": null
}

Transaction types

FieldTypeDescription
topupstringWallet top-up (positive amount_usd).
purchasestringDebit for a POST /purchase call (negative amount_usd, order_number set).
refundstringCredit back for a refunded order (positive amount_usd, order_number set). At most one per order.
adjustmentstringManual balance correction by our team.

Webhooks

Get notified instead of polling. Register an HTTPS endpoint with POST /webhooks; the signing_secret is returned once. Manage endpoints with GET /webhooks, POST /webhooks/:id (update url / events / is_active) and DELETE /webhooks/:id.

curl -X POST https://esimreseller.io/api/v1/webhooks \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/hooks/esim", "events": ["esim.delivered", "order.failed", "order.refunded"] }'

Events

FieldTypeDescription
order.createdeventA hosted-checkout order was created. data: order_number, product_id, customer_email, checkout_url.
order.paideventPayment confirmed (or wallet debited). data: order_number, amount_usd.
order.fulfilledeventProvisioning was accepted upstream. data: order_number.
order.failedeventProvisioning could not complete automatically; our team is reviewing. data: order_number, reason (customer-safe text).
esim.deliveredeventThe eSIM is ready. data: order_number, qr_code_url, activation_code, smdp_address, iccid.
order.refundedeventThe order was refunded. data: order_number, amount_usd, reseller_profit_usd.

Delivery & signature

Each delivery is a POST with a JSON body { "id", "event", "data", "created_at" } and headers X-Webhook-Event, X-Webhook-Id (stable across retries, use it to dedupe) and X-Webhook-Signature in the form t=<unix seconds>,v1=<hex> where v1 = HMAC-SHA256(signing_secret, `${t}.${raw body}`). Respond with any 2xx within 10 seconds; anything else is retried with backoff (up to 8 attempts over about three days).

// Node.js verification
import { createHmac, timingSafeEqual } from "node:crypto";
const [t, v1] = sigHeader.split(",").map((p) => p.split("=")[1]);
const expected = createHmac("sha256", SIGNING_SECRET).update(`${t}.${rawBody}`).digest("hex");
const ok = timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(v1, "hex"));

Errors

All errors follow the same JSON shape:

{ "error": { "code": "…", "message": "…" } }
FieldTypeDescription
400 invalid_requestobjectBody failed validation. See message + issues[].
401 unauthorizedobjectMissing/invalid/revoked API key.
402 insufficient_fundsobjectWallet too low. Body includes balance_usd + required_usd.
403 insufficient_scopeobjectKey is missing the required scope (read or write).
404 not_found / product_not_foundobjectResource does not exist.
409 replayobjectNot thrown — replays return 200 with the original result. See Idempotency.
429 rate_limitedobjectSlow down. Body includes retry_after_seconds.
5xxobjectTransient upstream failure — retry with backoff.

402 insufficient_funds example

{
  "error": {
    "code": "insufficient_funds",
    "message": "Your wallet balance is too low for this purchase. Top up in your reseller panel.",
    "balance_usd": 3.20,
    "required_usd": 8.50
  }
}

Polling for eSIM details

Most purchases return the QR in the initial POST /purchase response. When they don't, theesim field is null and status is paid — pollGET /orders/:order_number with backoff until esim is populated (usually within a few seconds).

async function waitForEsim(orderNumber) {
  for (const wait of [500, 1000, 2000, 4000, 8000, 15000]) {
    await new Promise(r => setTimeout(r, wait));
    const r = await fetch("https://esimreseller.io/api/v1/orders/" + orderNumber, {
      headers: { Authorization: "Bearer sk_live_…" }
    });
    const { order } = await r.json();
    if (order.esim?.qr_code_url) return order.esim;
  }
  throw new Error("eSIM not ready — contact support");
}

Changelog

  • v1.1 — Refunds: new order status refunded, webhook event order.refunded, wallet transaction type refund. Webhooks documented.
  • v1.0 — Initial release: catalog, purchase, orders, wallet, transactions.

Ready to build?

Grab a key and top up in one place.

Open the API panel