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.
Getting started
- Create an account at /register/. It is anonymous: you get a login key, nothing is asked.
- Add balance on Billing, by card or crypto. API orders are paid from it.
- Create an API key at /user/api/. It is shown once; store it server-side.
- 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.
| code | HTTP | meaning |
|---|---|---|
missing_api_key | 401 | No key in the request. |
invalid_api_key | 401 | Unknown or revoked key. |
insufficient_balance | 402 | The account balance does not cover the order. details.required and details.balance say by how much. |
package_not_found | 404 | No sellable package with that id. |
esim_not_found | 404 | No eSIM with that id on this account. |
order_not_found | 404 | No order with that id on this account. |
topup_not_found | 404 | The top-up id is not offered for this eSIM. |
not_found | 404 | Unknown endpoint. |
reference_conflict | 409 | The reference was already used for a different order. |
order_refunded | 409 | The supplier could not fulfil the order; the amount is back on the balance. |
esim_not_cancellable | 409 | The eSIM has been installed, or is past the point of cancellation. |
not_ready | 409 | The eSIM has no activation code yet. |
no_webhook | 409 | No webhook URL is configured. |
invalid_parameter | 422 | A parameter is missing or out of range; the message says which. |
topup_not_supported | 422 | This eSIM does not take top-ups. |
cancel_not_supported | 422 | This eSIM cannot be cancelled online. |
invalid_webhook_url | 422 | The webhook URL is not a public http(s) URL. |
invalid_json | 400 | The request body is not a JSON object. |
rate_limited | 429 | Over the per-minute limit; wait for Retry-After. |
provider_unavailable | 502 | The supplier did not answer; try again shortly. |
provider_error | 502 | The supplier rejected the operation; nothing was charged. |
order_failed | 502 | The order failed and was not charged. |
internal_error | 500 | Our 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
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}
}Response
{ "openapi": "3.0.3", ... }Endpoints: Account
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/"}
}| parameter | in | type | meaning |
|---|---|---|---|
page | query | integer | Page number, from 1. |
per_page | query | integer | 1 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
| parameter | in | type | meaning |
|---|---|---|---|
type | query | string | country or region. |
popular | query | boolean | 1 for the popular destinations only. |
q | query | string | Name 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}
}| parameter | in | type | meaning |
|---|---|---|---|
location | query | string | Location code from /locations, e.g. JP or EU-42. |
country | query | string | ISO country code: that country's own plans plus every regional bundle covering it. |
type | query | string | fixed (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_capable | query | boolean | 1 only plans that can be topped up, 0 only single-use plans. |
exclude_hk_exit | query | boolean | 1 to drop plans whose traffic exits through Hong Kong. |
q | query | string | Text search on name and destination. |
updated_since | query | string | Unix time or ISO 8601: only plans changed since then. |
page | query | integer | Page number, from 1. |
per_page | query | integer | 1 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}
}| parameter | in | type | meaning |
|---|---|---|---|
id * | path | string | Package id from /packages. |
days | query | integer | Daily 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
| parameter | in | type | meaning |
|---|---|---|---|
package_id * | body | string | Package id from /packages. |
days | body | integer | Daily plans only: between validity.min_days and max_days. |
reference | body | string | Your own id for this order, up to 64 characters, unique per account. |
quote_only | body | boolean | true 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"
}}
}| parameter | in | type | meaning |
|---|---|---|---|
status | query | string | processing, completed, failed or refunded. |
type | query | string | esim or topup. |
page | query | integer | Page number. |
per_page | query | integer | 1 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}
}| parameter | in | type | meaning |
|---|---|---|---|
id * | path | integer | Order 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
| parameter | in | type | meaning |
|---|---|---|---|
status | query | string | provisioning, ready, active, suspended, depleted, expired, cancelled or revoked. |
iccid | query | string | Find by ICCID. |
page | query | integer | Page number. |
per_page | query | integer | 1 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}
}| parameter | in | type | meaning |
|---|---|---|---|
id * | path | string | eSIM id, e.g. ESM-5D38FE28 |
refresh | query | boolean | 1 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"
}
}| parameter | in | type | meaning |
|---|---|---|---|
id * | path | string | eSIM id, e.g. ESM-5D38FE28 |
refresh | query | boolean | 1 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"}}
}| parameter | in | type | meaning |
|---|---|---|---|
id * | path | string | eSIM id, e.g. ESM-5D38FE28 |
size | query | integer | Pixel size, 96 to 1024, default 256. |
format | query | string | svg (default) or json. |
Response
<svg xmlns="http://www.w3.org/2000/svg" ...>| parameter | in | type | meaning |
|---|---|---|---|
id * | path | string | eSIM 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"
}
}| parameter | in | type | meaning |
|---|---|---|---|
id * | path | string | eSIM 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}
}| parameter | in | type | meaning |
|---|---|---|---|
id * | path | string | eSIM id, e.g. ESM-5D38FE28 |
topup_id * | body | string | Top-up id from GET /esims/{id}/topups. |
reference | body | string | Your 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"
}}
}| parameter | in | type | meaning |
|---|---|---|---|
id * | path | string | eSIM id, e.g. ESM-5D38FE28 |
Response
{
"ok": true,
"data": {"refunded": "11.90", "currency": "USD", "esim": {"id": "ESM-5D38FE28", "status": "cancelled", "...": "..."}}
}Endpoints: Webhook
Response
{
"ok": true,
"data": {"url": "https://example.com/simbear", "secret_prefix": "whsec_3f9a...", "events": ["order.completed", "topup.completed", "esim.updated", "esim.usage_alert", "ping"]}
}| parameter | in | type | meaning |
|---|---|---|---|
url * | body | string | Where 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."}
}Response
{
"ok": true,
"data": {"url": null}
}Response
{
"ok": true,
"data": {"queued": true, "note": "Delivered within a minute; see GET /webhook/deliveries."}
}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.
| status | meaning |
|---|---|
provisioning | Paid and ordered; the supplier has not handed over the profile yet. Usually seconds; poll the eSIM or wait for esim.updated. |
ready | Profile issued, not yet installed on a device. install.* carries everything needed. |
active | Installed and in use (or activated). |
suspended | Suspended by the network. |
depleted | Data allowance used up; a top-up revives it where topup_capable is true. |
expired | Validity ended. |
cancelled | Cancelled before installation; the purchase was refunded to the balance. |
revoked | Withdrawn 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
- QR code.
GET /esims/{id}/qrreturns an SVG you can show or print; or encodeinstall.lpain your own QR library. The QR is single-use: once a phone has installed the profile, scanning again fails. - One-tap links.
install.apple_url(iOS 17.4 and later) andinstall.android_urlopen the phone's eSIM installer directly, no camera needed. Send the one matching the customer's platform. - Manual entry.
install.smdp_addressandinstall.matching_idfor phones that ask for them separately. - Tell them to turn on data roaming for the new line and to select it for mobile data. It is the one setting that stops most "installed but no internet" tickets. Where
install.apnis set and the phone did not configure it, it goes in the line's APN settings. - Watch the status.
esim.updatedwebhooks (orGET /esims/{id}) tell you when it is installed and active;esim.usage_alertat 50, 80 and 90 percent of the data and 24 hours before expiry lets you offer a top-up in time.
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.
| event | when | data |
|---|---|---|
order.completed | an eSIM order you placed has an eSIM (also after a 202, once the supplier confirmed) | order, esim |
topup.completed | a top-up was applied | order, esim |
esim.updated | the status moved: provisioning to ready, ready to active, active to depleted or expired, and so on | esim, changes {field: [old, new]} |
esim.usage_alert | 50, 80 or 90 percent of the data is used, or the eSIM expires within 24 hours | esim, alert {kind: data|expiry, threshold_percent} |
ping | you called POST /webhook/test | message |
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 keyAPI version 1.0. Changes are additive: new fields and endpoints may appear, existing ones keep their meaning.