Quickstart
Six requests take you from an empty terminal to an eSIM profile a phone can install. Everything below runs against sandbox, where the money is imaginary and nothing reaches a supplier — the base URL, the routes and the payloads are identical to live.
Before you start
- A sandbox key. If you already have a SimWay partner account, mint one yourself in the cabinet — both roles may issue sandbox keys, and the secret is shown once. If you do not, email [email protected]: there is no signup form, because opening an account is a decision a person makes and a form in front of it only hides the wait.
- A server. This API emits no CORS headers at all, deliberately, so a key cannot be used from browser JavaScript. Every call below belongs on your backend.
- Nothing else. No SDK, no signing, no OAuth exchange. A bearer header and an HTTP client.
The base URL is https://api.simway.pro/partner/v1. It sits outside the consumer api/v1 prefix so that a v2 can ship one day without disturbing anything.
0. Check we are up
The one route that takes no credential and is subject to no rate limit. Point your monitoring at it — it answers 503 rather than a cheerful 200 when the database is unreachable, so an uptime check configured with nothing but a URL does the right thing.
1. Confirm the key
Authentication is Authorization: Bearer <key> and nothing else. A key looks like this, and its shape is fixed so your own configuration check can be exact:
GET /me is the fastest way to prove a credential works and to see what it is allowed to do. Read rate_limits from here rather than hard-coding the table from the rate-limits page: it moves with your tier without us redeploying anything.
2. Find something to sell
GET /plans is the whole catalogue, priced for your account. Filter it down — an unknown query parameter is a 400 rather than an ignore, so a typo tells you instead of quietly returning everything.
Three prices travel together on every row and the third is the one that matters: list_price_cents is what a consumer pays us, your_price_cents is what you pay, and discount_bps is the rate that price actually represents on that plan — which is not always your headline tier rate. The pricing page explains when and why.
Pages default to 50 rows and cap at 200. Follow next_cursor until has_more is false. There is no total count anywhere on this API.
3. Price it before you buy it
A dry run. It deducts nothing, provisions nothing and reserves nothing, and it tells you whether the balance would clear. Calling it before every order is the cheapest habit on this API, because the alternative — discovering the balance is short at order time — costs you an idempotency key permanently.
4. Order
The only endpoint that spends money, and the only one that requires an Idempotency-Key. The money moves synchronously and the profiles do not: by the time you hold the response, your balance is debited and the statement row exists, and provisioning is running in the background.
5. Poll until it is done
There are no webhooks on this API. Polling is the delivery mechanism, and the order tells you the interval so you do not have to invent one.
Typical end to end is about 5 seconds. The hard ceiling on one provisioning run is 92 seconds, which is why the endpoint answers 202 rather than making you wait — and why an order that will never provision takes a few minutes to say so. The lifecycle, including partial delivery and refunds.
6. Deliver the profile
There is nothing left to fetch: each entry in esims[] from step 5 is already the whole profile, install payload and all — the object below is what you are holding. Read it again by ICCID when your own support process needs it later, which is the identifier a customer writing in will have.
The activation object holds every route onto a handset: a scannable QR as a data URL, an LPA: activation code, the SM-DP+ address and matching id for manual entry, and an Apple universal link that installs the profile in one tap on iOS. What each field is for.
Conventions worth reading once
| Convention | Detail |
|---|---|
| Case | Requests and responses are snake_case throughout. Your own metadata blob is the exception — it is echoed back verbatim, inner keys untouched. |
| Times | RFC 3339 UTC to the second, with a Z suffix: 2026-08-17T09:14:22Z. Never a local time and never a bare offset. |
| Money | Integer cents, USD, always. There is no other billing currency and no float anywhere in a price. |
| Absent values | A field that is null means null. A field that has no value is omitted from the response entirely rather than sent as null. |
| Byte counters | data_used_bytes and data_total_bytes are JSON numbers on this API. They are strings on the consumer API — if you have integrated both, they are not the same type. |
| Unknown fields | Refused, not ignored. An unrecognised query parameter or body field is a 400 naming it. |
| Lists | { data, has_more, next_cursor }. Default 50, maximum 200, no total count anywhere. |
| Request ids | Every response carries X-Request-Id, including 401s. Send your own and we echo it back if it is 8–64 characters of [A-Za-z0-9_-]. |
Id prefixes
Every id names its own kind, and an id of the wrong kind is refused as a 400 naming the parameter rather than 404’d — a client bug that says what it is instead of looking like a missing record.
| Prefix | Names |
|---|---|
pt_ | a partner account |
po_ | a partner order |
pl_ | a plan |
pk_ | an API key |
led_ | a ledger entry |
dep_ | a deposit |
req_ | one request (echoed as X-Request-Id) |
The whole surface, at a glance
Sixteen routes. Fifteen need a key; one does not.
| Endpoint | What it does | |
|---|---|---|
| GET | /health | Liveness, without a credential. |
| GET | /me | Who this credential belongs to, and what it may do. |
| GET | /balance | Prepaid funds, and the headline discount they buy. |
| GET | /ledger | Every movement of money, newest first. |
| POST | /deposits | Mint a crypto invoice. It credits the float when it settles. |
| GET | /deposits | Your funding history, newest first — and where to pay what is open. |
| GET | /deposits/{id} | One deposit, including where to pay it. |
| GET | /plans | The whole catalogue, priced for you. |
| GET | /plans/{id} | Resolve a stored plan id, including a delisted one. |
| GET | /plans/{id}/price | What an order would cost, and whether it would clear. |
| GET | /destinations | Every country you can sell, with your own entry price. |
| POST | /orders | The only endpoint that spends money. Always answers 202. |
| GET | /orders | Your orders, newest first, scoped to this key’s mode. |
| GET | /orders/{id} | Where an accepted order actually ends up. |
| GET | /esims | Every profile you have bought. |
| GET | /esims/{iccid} | Everything needed to install it, and how much is left. |
Where to go next
Why these pages are English only
The rest of simway.pro is published in eight languages. This section is not, and the reason is worth stating rather than leaving as an oversight.
- The site’s translation files are typed against the English one, so exactly the same key set is required in all eight. A reference this size would be thousands of translated strings before the first deploy, and it would break the build again every time an endpoint gained a parameter.
- The content resists translation where it counts. Field names, enum values, error codes, HTTP semantics and JSON bodies stay English whatever the surrounding prose says — a translated page would be English payload inside translated wrapping, which is harder to keep truthful and worse to read than the English it replaced.
- One URL per page also means no
hreflangset to get wrong. Declaring eight translations that do not exist is a reliable way to have the whole set discarded, including the ones on the consumer catalogue that do real work.
If you would rather read this as one flat document — or hand it to a coding assistant — the whole reference is at /partners/llms-full.txt, generated from the same source as these pages.