Reseller API

Sell SimBear eSIMs from your own shop, bot or app. One REST API lists the catalogue, buys and delivers eSIMs, tops them up, reports usage and calls you back when something changes. It is the same balance, the same eSIMs and the same anonymity as the website: a key is your whole identity.

Create an API key OpenAPI 3 document

Getting started

  1. Create an account at /register/. It is anonymous: you get a login key, nothing is asked.
  2. Add balance on Billing, by card or crypto. API orders are paid from it.
  3. Create an API key at /user/api/. It is shown once; store it server-side.
  4. Call the API. Base URL https://simbear.io/reseller/v1.
# who am I, what can I spend
curl https://simbear.io/reseller/v1/account -H "Authorization: Bearer sb_live_..."

# plans for Japan
curl "https://simbear.io/reseller/v1/packages?location=JP" -H "Authorization: Bearer sb_live_..."

# buy one; the response carries the QR code data and install links
curl https://simbear.io/reseller/v1/orders -H "Authorization: Bearer sb_live_..." \
  -H "Content-Type: application/json" \
  -d '{"package_id": "JP_10_30", "reference": "your-order-4471"}'

Authentication

Every request except GET /ping and GET /openapi.json carries the key in a header: Authorization: Bearer sb_live_…, or X-API-Key: sb_live_… if your HTTP client cannot set Authorization. There are no sessions and no cookies. A revoked key gets 401 invalid_api_key from the next request on. Keys never expire on their own; rotate them from /user/api/ whenever you like, and keep them out of client-side code.

Money and prices

Prices are in USD with two decimals, as strings, so nothing rounds in transit. Each package shows price.amount, what you pay, and price.list_price, what the website charges; the difference is your discount_percent, set per account by SimBear. Daily plans (plan_type: daily, and unlimited plans, which are priced per day too) show the per-day price; the order costs that times days, and GET /packages/{id}?days=N or quote_only on the order give the exact figure.

An order debits the balance when it is accepted. If the supplier cannot deliver, the amount returns to the balance automatically and the order shows status: refunded. A top-up that the supplier rejects is never charged. An eSIM that was never installed can be cancelled for a full refund to the balance (POST /esims/{id}/cancel). With insufficient balance the API answers 402 insufficient_balance and tells you how much is missing.

Idempotency

Give every order and top-up a reference: your own id, up to 64 characters, unique within your account. Repeating a request with the same reference returns the existing order with HTTP 200 instead of buying again, which makes retries after a timeout safe. Reusing a reference for a different package answers 409 reference_conflict.

Responses, paging, time

Every JSON response is an envelope: {"ok": true, "data": …}, with "meta": {"page", "per_page", "total", "pages"} on lists, or {"ok": false, "error": {"code", "message", "details"}}. Lists take page (from 1) and per_page (1 to 200, default 50). Times are ISO 8601 in UTC. Data volumes are bytes. The two image-like endpoints, the QR code and the OpenAPI document, return their own content type without the envelope.

Errors

HTTP status says the class, error.code the exact reason. Treat unknown codes as retryable when the status is 5xx and as fatal otherwise.

codeHTTPmeaning
missing_api_key401No key in the request.
invalid_api_key401Unknown or revoked key.
insufficient_balance402The account balance does not cover the order. details.required and details.balance say by how much.
package_not_found404No sellable package with that id.
esim_not_found404No eSIM with that id on this account.
order_not_found404No order with that id on this account.
topup_not_found404The top-up id is not offered for this eSIM.
not_found404Unknown endpoint.
reference_conflict409The reference was already used for a different order.
order_refunded409The supplier could not fulfil the order; the amount is back on the balance.
esim_not_cancellable409The eSIM has been installed, or is past the point of cancellation.
not_ready409The eSIM has no activation code yet.
no_webhook409No webhook URL is configured.
invalid_parameter422A parameter is missing or out of range; the message says which.
topup_not_supported422This eSIM does not take top-ups.
cancel_not_supported422This eSIM cannot be cancelled online.
invalid_webhook_url422The webhook URL is not a public http(s) URL.
invalid_json400The request body is not a JSON object.
rate_limited429Over the per-minute limit; wait for Retry-After.
provider_unavailable502The supplier did not answer; try again shortly.
provider_error502The supplier rejected the operation; nothing was charged.
order_failed502The order failed and was not charged.
internal_error500Our fault. Retry; contact support with the time if it persists.

Rate limits

Each key may make 120 requests per minute by default; X-RateLimit-Limit and X-RateLimit-Remaining are on every response, and 429 rate_limited comes with Retry-After. The catalogue changes a few times a day at most: cache /locations and /packages for an hour, or poll /packages?updated_since=…. Need more? Ask support.

Endpoints: Service

GET/pingno key needed
Service check. Works without a key. With one, confirms that it is valid and returns the account id.
Response
{
  "ok": true,
  "data": {"service": "SimBear Reseller API", "version": "1.0", "time": "2026-10-20T02:00:00+00:00", "docs": "https://simbear.io/reseller/", "openapi": "https://simbear.io/reseller/v1/openapi.json", "authenticated": true, "account_id": 207}
}
GET/openapi.jsonno key needed
OpenAPI 3 document. Machine-readable version of this reference, for client generators and API tools.
Response
{ "openapi": "3.0.3", ... }

Endpoints: Account

GET/account
Balance, discount and key details. The balance is what orders are paid from. Add funds (card or crypto) on the Billing page of the account that owns the key.
Response
{
  "ok": true,
  "data": {"account_id": 207, "balance": "250.00", "currency": "USD", "discount_percent": 15, "esims": {"total": 12, "live": 9}, "key": {"id": 3, "prefix": "sb_live_3f9a1c2e", "label": "shop backend", "created_at": "2026-10-20T00:00:00+00:00", "rate_limit_per_minute": 120}, "webhook": {"url": "https://example.com/simbear"}, "top_up_balance_at": "https://simbear.io/user/billing/"}
}
GET/transactions
Balance ledger. Every movement on the balance: eSIM orders and top-ups (debit), balance top-ups and refunds (credit). Newest first.
parameterintypemeaning
pagequeryintegerPage number, from 1.
per_pagequeryinteger1 to 200, default 50.
Response
{
  "ok": true,
  "data": [{"id": 321, "type": "esim", "status": "completed", "amount": "11.90", "currency": "USD", "esim_id": "ESM-5D38FE28", "direction": "debit", "payment_method": "balance", "created_at": "2026-10-20T01:37:23+00:00", "completed_at": "2026-10-20T01:37:24+00:00"}],
  "meta": {"page": 1, "per_page": 50, "total": 1, "pages": 1}
}

Endpoints: Catalogue

GET/locations
Countries and regions with plans. Every destination that currently has at least one sellable plan, with the plan count and the cheapest price at your discount.
parameterintypemeaning
typequerystringcountry or region.
popularqueryboolean1 for the popular destinations only.
qquerystringName or code search.
Response
{
  "ok": true,
  "data": [{"code": "JP", "name": "Japan", "type": "country", "popular": true, "plans": 31, "from_price": "2.55"}, {"code": "EU-42", "name": "Europe (42 areas)", "type": "region", "popular": true, "plans": 48, "from_price": "3.40"}],
  "meta": {"total": 263}
}
GET/packages
List plans. The full sellable catalogue, paginated. Filter by destination, plan type or text. Prices are at your discount; plans with validity.per_day true show the per-day price and take days on the order. Use updated_since to sync only what changed.
parameterintypemeaning
locationquerystringLocation code from /locations, e.g. JP or EU-42.
countryquerystringISO country code: that country's own plans plus every regional bundle covering it.
typequerystringfixed (a data bucket with a set validity), daily (a per-day allowance, you pick the days), unlimited (no data cap; some are per day, some have a set validity: see validity.per_day).
topup_capablequeryboolean1 only plans that can be topped up, 0 only single-use plans.
exclude_hk_exitqueryboolean1 to drop plans whose traffic exits through Hong Kong.
qquerystringText search on name and destination.
updated_sincequerystringUnix time or ISO 8601: only plans changed since then.
pagequeryintegerPage number, from 1.
per_pagequeryinteger1 to 200, default 50.
Response
{
  "ok": true,
  "data": [{
    "id": "JP_10_30",
    "name": "Japan 10GB 30Days",
    "location": {"code": "JP", "name": "Japan"},
    "coverage": ["JP"],
    "plan_type": "fixed",
    "data": {"bytes": 10737418240, "display": "10 GB", "unlimited": false, "per_day": false},
    "validity": {"per_day": false, "days": 30, "unit": "day"},
    "price": {"amount": "11.90", "currency": "USD", "per_day": false, "list_price": "14.00", "discount_percent": 15},
    "speed": "4G/5G",
    "exit_ip_country": "JP",
    "fair_use": {"policy": null, "speed_kbps": null},
    "topup_capable": true,
    "activation": {"policy": "first_use", "install_within_days": 180},
    "operator": null,
    "networks": [{"country": "JP", "operators": [{"name": "SoftBank", "network": "5G"}]}],
    "description": null,
    "updated_at": "2026-10-20T01:00:12+00:00"
  }],
  "meta": {"page": 1, "per_page": 50, "total": 31, "pages": 1}
}
GET/packages/{id}
One plan. A single plan by id. For daily plans add ?days=N to get an exact quote.
parameterintypemeaning
id *pathstringPackage id from /packages.
daysqueryintegerDaily plans: number of days to quote.
Response
{
  "ok": true,
  "data": {
    "id": "JP_10_30",
    "name": "Japan 10GB 30Days",
    "location": {"code": "JP", "name": "Japan"},
    "coverage": ["JP"],
    "plan_type": "fixed",
    "data": {"bytes": 10737418240, "display": "10 GB", "unlimited": false, "per_day": false},
    "validity": {"per_day": false, "days": 30, "unit": "day"},
    "price": {"amount": "11.90", "currency": "USD", "per_day": false, "list_price": "14.00", "discount_percent": 15},
    "speed": "4G/5G",
    "exit_ip_country": "JP",
    "fair_use": {"policy": null, "speed_kbps": null},
    "topup_capable": true,
    "activation": {"policy": "first_use", "install_within_days": 180},
    "operator": null,
    "networks": [{"country": "JP", "operators": [{"name": "SoftBank", "network": "5G"}]}],
    "description": null,
    "updated_at": "2026-10-20T01:00:12+00:00"
  }
}

Endpoints: Orders

POST/orders
Buy an eSIM. Charges the balance and orders the eSIM. 201: the eSIM exists and install details are in the response (status ready), or it is provisioning and will be ready within seconds. 202: the supplier has not confirmed yet; poll GET /orders/{id} or wait for the order.completed webhook. The reference makes the call idempotent: repeating it returns the same order (200) instead of buying twice.
parameterintypemeaning
package_id *bodystringPackage id from /packages.
daysbodyintegerDaily plans only: between validity.min_days and max_days.
referencebodystringYour own id for this order, up to 64 characters, unique per account.
quote_onlybodybooleantrue returns the price without buying.
Request body
{"package_id": "JP_10_30", "reference": "cust-4471-1"}
Response
{
  "ok": true,
  "data": {"order": {
    "id": 321,
    "type": "esim",
    "status": "completed",
    "amount": "11.90",
    "currency": "USD",
    "esim_id": "ESM-5D38FE28",
    "package_code": "PMJ9AFZEP",
    "days": null,
    "reference": "cust-4471-1",
    "note": null,
    "created_at": "2026-10-20T01:37:23+00:00",
    "completed_at": "2026-10-20T01:37:24+00:00"
  }, "esim": {
    "id": "ESM-5D38FE28",
    "status": "ready",
    "raw_status": "GOT_RESOURCE",
    "smdp_status": "RELEASED",
    "installed": false,
    "order_id": 321,
    "package": {"id": "JP_10_30", "name": "Japan 10GB 30Days", "location": {"code": "JP", "name": "Japan"}},
    "days": null,
    "data": {"unlimited": false, "total_bytes": 10737418240, "used_bytes": 0, "remaining_bytes": 10737418240, "updated_at": "2026-10-20T01:50:59+00:00"},
    "validity": {"days": 30, "activated_at": null, "expires_at": "2027-04-18T01:37:24+00:00"},
    "install": {
      "lpa": "LPA:1$rsp.example.net$ABCDEF0123456789",
      "smdp_address": "rsp.example.net",
      "matching_id": "ABCDEF0123456789",
      "qr_svg_url": "https://simbear.io/reseller/v1/esims/ESM-5D38FE28/qr",
      "qr_image_url": "https://...png",
      "apple_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA%3A1%24...",
      "android_url": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA%3A1%24...",
      "apn": "internet", "apn_type": null, "pin": "1234", "puk": "12345678"
    },
    "iccid": "89...",
    "topup_capable": true,
    "cancellable": true,
    "created_at": "2026-10-20T01:37:23+00:00",
    "updated_at": "2026-10-20T01:50:59+00:00"
  }}
}
GET/orders
List orders. eSIM purchases and top-ups, newest first.
parameterintypemeaning
statusquerystringprocessing, completed, failed or refunded.
typequerystringesim or topup.
pagequeryintegerPage number.
per_pagequeryinteger1 to 200.
Response
{
  "ok": true,
  "data": [{
    "id": 321,
    "type": "esim",
    "status": "completed",
    "amount": "11.90",
    "currency": "USD",
    "esim_id": "ESM-5D38FE28",
    "package_code": "PMJ9AFZEP",
    "days": null,
    "reference": "cust-4471-1",
    "note": null,
    "created_at": "2026-10-20T01:37:23+00:00",
    "completed_at": "2026-10-20T01:37:24+00:00"
  }],
  "meta": {"page": 1, "per_page": 50, "total": 1, "pages": 1}
}
GET/orders/{id}
One order, with its eSIM. Poll this after a 202. status moves from processing to completed (esim present) or refunded (the money is back on the balance).
parameterintypemeaning
id *pathintegerOrder id.
Response
{
  "ok": true,
  "data": {"order": {
    "id": 321,
    "type": "esim",
    "status": "completed",
    "amount": "11.90",
    "currency": "USD",
    "esim_id": "ESM-5D38FE28",
    "package_code": "PMJ9AFZEP",
    "days": null,
    "reference": "cust-4471-1",
    "note": null,
    "created_at": "2026-10-20T01:37:23+00:00",
    "completed_at": "2026-10-20T01:37:24+00:00"
  }, "esim": {
    "id": "ESM-5D38FE28",
    "status": "ready",
    "raw_status": "GOT_RESOURCE",
    "smdp_status": "RELEASED",
    "installed": false,
    "order_id": 321,
    "package": {"id": "JP_10_30", "name": "Japan 10GB 30Days", "location": {"code": "JP", "name": "Japan"}},
    "days": null,
    "data": {"unlimited": false, "total_bytes": 10737418240, "used_bytes": 0, "remaining_bytes": 10737418240, "updated_at": "2026-10-20T01:50:59+00:00"},
    "validity": {"days": 30, "activated_at": null, "expires_at": "2027-04-18T01:37:24+00:00"},
    "install": {
      "lpa": "LPA:1$rsp.example.net$ABCDEF0123456789",
      "smdp_address": "rsp.example.net",
      "matching_id": "ABCDEF0123456789",
      "qr_svg_url": "https://simbear.io/reseller/v1/esims/ESM-5D38FE28/qr",
      "qr_image_url": "https://...png",
      "apple_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA%3A1%24...",
      "android_url": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA%3A1%24...",
      "apn": "internet", "apn_type": null, "pin": "1234", "puk": "12345678"
    },
    "iccid": "89...",
    "topup_capable": true,
    "cancellable": true,
    "created_at": "2026-10-20T01:37:23+00:00",
    "updated_at": "2026-10-20T01:50:59+00:00"
  }}
}

Endpoints: eSIMs

GET/esims
List eSIMs. Every eSIM on the account, newest first.
parameterintypemeaning
statusquerystringprovisioning, ready, active, suspended, depleted, expired, cancelled or revoked.
iccidquerystringFind by ICCID.
pagequeryintegerPage number.
per_pagequeryinteger1 to 200.
Response
{
  "ok": true,
  "data": [{
    "id": "ESM-5D38FE28",
    "status": "ready",
    "raw_status": "GOT_RESOURCE",
    "smdp_status": "RELEASED",
    "installed": false,
    "order_id": 321,
    "package": {"id": "JP_10_30", "name": "Japan 10GB 30Days", "location": {"code": "JP", "name": "Japan"}},
    "days": null,
    "data": {"unlimited": false, "total_bytes": 10737418240, "used_bytes": 0, "remaining_bytes": 10737418240, "updated_at": "2026-10-20T01:50:59+00:00"},
    "validity": {"days": 30, "activated_at": null, "expires_at": "2027-04-18T01:37:24+00:00"},
    "install": {
      "lpa": "LPA:1$rsp.example.net$ABCDEF0123456789",
      "smdp_address": "rsp.example.net",
      "matching_id": "ABCDEF0123456789",
      "qr_svg_url": "https://simbear.io/reseller/v1/esims/ESM-5D38FE28/qr",
      "qr_image_url": "https://...png",
      "apple_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA%3A1%24...",
      "android_url": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA%3A1%24...",
      "apn": "internet", "apn_type": null, "pin": "1234", "puk": "12345678"
    },
    "iccid": "89...",
    "topup_capable": true,
    "cancellable": true,
    "created_at": "2026-10-20T01:37:23+00:00",
    "updated_at": "2026-10-20T01:50:59+00:00"
  }],
  "meta": {"page": 1, "per_page": 50, "total": 12, "pages": 1}
}
GET/esims/{id}
One eSIM. Status, usage and install details. A provisioning eSIM is refreshed from the supplier on every call; add ?refresh=1 to force a refresh otherwise.
parameterintypemeaning
id *pathstringeSIM id, e.g. ESM-5D38FE28
refreshqueryboolean1 to re-read status and usage from the supplier now.
Response
{
  "ok": true,
  "data": {
    "id": "ESM-5D38FE28",
    "status": "ready",
    "raw_status": "GOT_RESOURCE",
    "smdp_status": "RELEASED",
    "installed": false,
    "order_id": 321,
    "package": {"id": "JP_10_30", "name": "Japan 10GB 30Days", "location": {"code": "JP", "name": "Japan"}},
    "days": null,
    "data": {"unlimited": false, "total_bytes": 10737418240, "used_bytes": 0, "remaining_bytes": 10737418240, "updated_at": "2026-10-20T01:50:59+00:00"},
    "validity": {"days": 30, "activated_at": null, "expires_at": "2027-04-18T01:37:24+00:00"},
    "install": {
      "lpa": "LPA:1$rsp.example.net$ABCDEF0123456789",
      "smdp_address": "rsp.example.net",
      "matching_id": "ABCDEF0123456789",
      "qr_svg_url": "https://simbear.io/reseller/v1/esims/ESM-5D38FE28/qr",
      "qr_image_url": "https://...png",
      "apple_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA%3A1%24...",
      "android_url": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA%3A1%24...",
      "apn": "internet", "apn_type": null, "pin": "1234", "puk": "12345678"
    },
    "iccid": "89...",
    "topup_capable": true,
    "cancellable": true,
    "created_at": "2026-10-20T01:37:23+00:00",
    "updated_at": "2026-10-20T01:50:59+00:00"
  }
}
GET/esims/{id}/usage
Usage. Data used and remaining. Refreshed from the supplier when older than 5 minutes (or with ?refresh=1). Unlimited plans report used bytes only.
parameterintypemeaning
id *pathstringeSIM id, e.g. ESM-5D38FE28
refreshqueryboolean1 to force a refresh.
Response
{
  "ok": true,
  "data": {"id": "ESM-5D38FE28", "status": "active", "data": {"unlimited": false, "total_bytes": 10737418240, "used_bytes": 2415919104, "remaining_bytes": 8321499136, "updated_at": "2026-10-21T09:12:00+00:00"}, "validity": {"days": 30, "activated_at": "2026-10-20T14:02:11+00:00", "expires_at": "2026-11-19T14:02:11+00:00"}}
}
GET/esims/{id}/qr
QR code (SVG). The install QR code as an SVG image (image/svg+xml), encoding the LPA string. ?format=json returns {"lpa", "svg"} inside the usual envelope instead.
parameterintypemeaning
id *pathstringeSIM id, e.g. ESM-5D38FE28
sizequeryintegerPixel size, 96 to 1024, default 256.
formatquerystringsvg (default) or json.
Response
<svg xmlns="http://www.w3.org/2000/svg" ...>
POST/esims/{id}/refresh
Refresh from the supplier. Re-reads status, usage and install data upstream and returns the eSIM.
parameterintypemeaning
id *pathstringeSIM id, e.g. ESM-5D38FE28
Response
{
  "ok": true,
  "data": {
    "id": "ESM-5D38FE28",
    "status": "ready",
    "raw_status": "GOT_RESOURCE",
    "smdp_status": "RELEASED",
    "installed": false,
    "order_id": 321,
    "package": {"id": "JP_10_30", "name": "Japan 10GB 30Days", "location": {"code": "JP", "name": "Japan"}},
    "days": null,
    "data": {"unlimited": false, "total_bytes": 10737418240, "used_bytes": 0, "remaining_bytes": 10737418240, "updated_at": "2026-10-20T01:50:59+00:00"},
    "validity": {"days": 30, "activated_at": null, "expires_at": "2027-04-18T01:37:24+00:00"},
    "install": {
      "lpa": "LPA:1$rsp.example.net$ABCDEF0123456789",
      "smdp_address": "rsp.example.net",
      "matching_id": "ABCDEF0123456789",
      "qr_svg_url": "https://simbear.io/reseller/v1/esims/ESM-5D38FE28/qr",
      "qr_image_url": "https://...png",
      "apple_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA%3A1%24...",
      "android_url": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA%3A1%24...",
      "apn": "internet", "apn_type": null, "pin": "1234", "puk": "12345678"
    },
    "iccid": "89...",
    "topup_capable": true,
    "cancellable": true,
    "created_at": "2026-10-20T01:37:23+00:00",
    "updated_at": "2026-10-20T01:50:59+00:00"
  }
}
GET/esims/{id}/topups
Available top-ups. Top-up options for this eSIM at your discount. Only for eSIMs with topup_capable true.
parameterintypemeaning
id *pathstringeSIM id, e.g. ESM-5D38FE28
Response
{
  "ok": true,
  "data": [{"id": "TP10GB30", "name": "10GB 30Days", "data": "10 GB", "days": 30, "price": {"amount": "9.35", "currency": "USD"}}],
  "meta": {"total": 1}
}
POST/esims/{id}/topups
Buy a top-up. Charges the balance and applies the top-up. 201 with the order and the grown eSIM. If the supplier rejects it nothing is charged (502 provider_error). reference works as for orders.
parameterintypemeaning
id *pathstringeSIM id, e.g. ESM-5D38FE28
topup_id *bodystringTop-up id from GET /esims/{id}/topups.
referencebodystringYour own id, unique per account.
Request body
{"topup_id": "TP10GB30", "reference": "cust-4471-topup-1"}
Response
{
  "ok": true,
  "data": {"order": {"id": 322, "type": "topup", "status": "completed", "amount": "9.35", "currency": "USD", "esim_id": "ESM-5D38FE28", "package_code": "TP10GB30", "days": null, "reference": "cust-4471-topup-1", "note": null, "created_at": "2026-10-21T09:15:00+00:00", "completed_at": "2026-10-21T09:15:02+00:00"}, "esim": {
    "id": "ESM-5D38FE28",
    "status": "ready",
    "raw_status": "GOT_RESOURCE",
    "smdp_status": "RELEASED",
    "installed": false,
    "order_id": 321,
    "package": {"id": "JP_10_30", "name": "Japan 10GB 30Days", "location": {"code": "JP", "name": "Japan"}},
    "days": null,
    "data": {"unlimited": false, "total_bytes": 10737418240, "used_bytes": 0, "remaining_bytes": 10737418240, "updated_at": "2026-10-20T01:50:59+00:00"},
    "validity": {"days": 30, "activated_at": null, "expires_at": "2027-04-18T01:37:24+00:00"},
    "install": {
      "lpa": "LPA:1$rsp.example.net$ABCDEF0123456789",
      "smdp_address": "rsp.example.net",
      "matching_id": "ABCDEF0123456789",
      "qr_svg_url": "https://simbear.io/reseller/v1/esims/ESM-5D38FE28/qr",
      "qr_image_url": "https://...png",
      "apple_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA%3A1%24...",
      "android_url": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA%3A1%24...",
      "apn": "internet", "apn_type": null, "pin": "1234", "puk": "12345678"
    },
    "iccid": "89...",
    "topup_capable": true,
    "cancellable": true,
    "created_at": "2026-10-20T01:37:23+00:00",
    "updated_at": "2026-10-20T01:50:59+00:00"
  }}
}
POST/esims/{id}/cancel
Cancel an unused eSIM. Only eSIMs that were never installed (status ready, cancellable true). The purchase price returns to the balance.
parameterintypemeaning
id *pathstringeSIM id, e.g. ESM-5D38FE28
Response
{
  "ok": true,
  "data": {"refunded": "11.90", "currency": "USD", "esim": {"id": "ESM-5D38FE28", "status": "cancelled", "...": "..."}}
}

Endpoints: Webhook

GET/webhook
Current webhook. The URL this key delivers events to, if any.
Response
{
  "ok": true,
  "data": {"url": "https://example.com/simbear", "secret_prefix": "whsec_3f9a...", "events": ["order.completed", "topup.completed", "esim.updated", "esim.usage_alert", "ping"]}
}
PUT/webhook
Set the webhook URL. Public http(s) URL. Returns a new signing secret: store it, it is shown once. Setting a URL again rotates the secret.
parameterintypemeaning
url *bodystringWhere to POST events.
Request body
{"url": "https://example.com/simbear"}
Response
{
  "ok": true,
  "data": {"url": "https://example.com/simbear", "secret": "whsec_...", "events": ["order.completed", "topup.completed", "esim.updated", "esim.usage_alert", "ping"], "note": "Store the secret now; it is shown only once."}
}
DELETE/webhook
Remove the webhook. Stops deliveries for this key.
Response
{
  "ok": true,
  "data": {"url": null}
}
POST/webhook/test
Send a ping event. Queues a ping event, delivered within a minute.
Response
{
  "ok": true,
  "data": {"queued": true, "note": "Delivered within a minute; see GET /webhook/deliveries."}
}
GET/webhook/deliveries
Recent deliveries. The last 50 events with attempt counts and the last response.
Response
{
  "ok": true,
  "data": [{"id": "evt_91", "event": "order.completed", "esim_id": "ESM-5D38FE28", "attempts": 1, "delivered": true, "delivered_at": "2026-10-20T01:38:00+00:00", "last_status": 200, "last_error": null, "next_attempt_at": null, "created_at": "2026-10-20T01:37:24+00:00"}]
}

The eSIM object and statuses

An eSIM has an id like ESM-5D38FE28, a status from the table below, data (bytes used and remaining, unlimited: true for unlimited plans), validity (days, activated_at, expires_at) and, once issued, an install block: the lpa string the QR code encodes, the SM-DP+ address and matching id separately for manual entry, ready-made apple_url and android_url install links, the APN, and PIN/PUK where the supplier sets them. raw_status and smdp_status are the supplier's own words, for debugging.

statusmeaning
provisioningPaid and ordered; the supplier has not handed over the profile yet. Usually seconds; poll the eSIM or wait for esim.updated.
readyProfile issued, not yet installed on a device. install.* carries everything needed.
activeInstalled and in use (or activated).
suspendedSuspended by the network.
depletedData allowance used up; a top-up revives it where topup_capable is true.
expiredValidity ended.
cancelledCancelled before installation; the purchase was refunded to the balance.
revokedWithdrawn by the supplier.

Activation: with activation.policy: first_use the validity clock starts when the eSIM first connects; with install it starts at installation. install_within_days is how long an unused eSIM stays installable.

Delivering an eSIM to your customer

Webhooks

Set a public https URL with PUT /webhook (or on /user/api/) and SimBear POSTs a JSON event there whenever something happens to your eSIMs. The response gives you a signing secret once. Answer with any 2xx within 10 seconds; anything else is retried with exponential backoff (1, 2, 4 … minutes, up to 6 hours apart) for up to 10 attempts. GET /webhook/deliveries shows what was sent and what your server answered.

eventwhendata
order.completedan eSIM order you placed has an eSIM (also after a 202, once the supplier confirmed)order, esim
topup.completeda top-up was appliedorder, esim
esim.updatedthe status moved: provisioning to ready, ready to active, active to depleted or expired, and so onesim, changes {field: [old, new]}
esim.usage_alert50, 80 or 90 percent of the data is used, or the eSIM expires within 24 hoursesim, alert {kind: data|expiry, threshold_percent}
pingyou called POST /webhook/testmessage
POST /your/endpoint
Content-Type: application/json
X-SimBear-Event: esim.updated
X-SimBear-Delivery: evt_91
X-SimBear-Signature: t=1760926800,v1=5f1c…

{"id": "evt_91", "event": "esim.updated", "created_at": "2026-10-20T01:40:00+00:00", "attempt": 1,
 "data": {"esim": {…}, "changes": {"status": ["ready", "active"]}}}

Verify the signature. v1 is HMAC-SHA256 of t + "." + raw_body with your secret. Reject the delivery when it does not match or when t is more than five minutes old. Deliveries may repeat after a timeout on your side: use id to process each event once.

# PHP
[$t, $v1] = sscanf($_SERVER['HTTP_X_SIMBEAR_SIGNATURE'], 't=%d,v1=%s');
$body = file_get_contents('php://input');
$ok = hash_equals(hash_hmac('sha256', $t . '.' . $body, $secret), $v1) && abs(time() - $t) < 300;

# Python
t, v1 = (p.split('=', 1)[1] for p in request.headers['X-SimBear-Signature'].split(','))
expected = hmac.new(secret.encode(), f"{t}.".encode() + request.get_data(), hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, v1) and abs(time.time() - int(t)) < 300

# Node
const [t, v1] = req.get('X-SimBear-Signature').split(',').map(p => p.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)) && Math.abs(Date.now()/1000 - t) < 300;

Questions

What does the Reseller API cost?

Nothing. You pay the plan price, from your account balance, when you order. Resellers with volume get a percentage off list prices; ask support.

Do my customers need SimBear accounts?

No. The eSIMs belong to your account; you hand your customer the QR code or the install link from the API response.

How do I pay?

Top up the balance of the account that owns the key, by card or by crypto (Monero, Bitcoin, Litecoin, Ethereum, USDT), on the Billing page. Orders debit it.

Is there a sandbox?

Not yet. Every call is live. Use quote_only on POST /orders to price without buying, and the cheapest plan in /packages for an end-to-end test; an uninstalled eSIM can be cancelled for a full refund to the balance.

What identifies me in the API?

Only the key. No name, email or company is asked for. The request log keeps the method, path and status of each call for seven days and nothing else.

Ready?

Create a key, fund the balance, and your first eSIM is one POST away. Questions, higher limits or a reseller discount: open a ticket from your dashboard.

Create an API key

API version 1.0. Changes are additive: new fields and endpoints may appear, existing ones keep their meaning.