eSIM
Provision and manage eSIM profiles through the gateway.
The eSIM product lets you browse a catalog of data plans, buy them, and read back the resulting eSIM profiles — with their activation material — through the same gateway you already use.
All eSIM endpoints are /v1/esim/* requests authenticated with your key id and secret — see Authentication.
Responses, pagination, errors and retry behaviour follow the shared Conventions and Errors rules — this page covers only what is specific to eSIM.
Every endpoint, with its full request and response schema, is in the eSIM API Reference.
A typical flow
Browse the catalog
Plans are returned at your selling prices. Browse them directly, or start from countries and regions:
await api('GET', '/v1/esim/plans', { query: { region: 'Global' } });
await api('GET', '/v1/esim/countries');Each plan carries a planId — a pln_-prefixed reference — and that is what you buy with.
Buy a plan
await api('POST', '/v1/esim/orders', {
body: {
planId: 'pln_9OOXVUPmNmI_MPiVZuWLcO7Q-id5EJsV1fae-tZihg',
},
});planId is the reference from the catalog, sent back exactly as given. It must match pln_…; a bare 24‑hex database id is refused. It is the only field — the body takes nothing else, and a stray property is rejected.
Funds are reserved and captured, and the call returns 202 with an orderId. Once the
order reaches delivered it carries the profiles it produced, each with the iccid that
addresses it — and the activation credential, read in the next step.
The eSIM is delivered in the response, not by email. There is no email or other
out-of-band delivery: you read the activation credential from the order (next step). The
order status (delivered, below) tells you the eSIM is ready — nothing else is sent.
Each call to this endpoint buys a plan. If it times out or fails without a clear
answer, do not resend it — check GET /v1/esim/orders first to see
whether an order was created, and only then decide whether to try again.
Read the activation credential
Poll the order until it is delivered, then read the credential from the profile:
const order = await api('GET', `/v1/esim/orders/${orderId}`);
const { iccid } = order.data.esims[0];
const { data } = await api('GET', `/v1/esim/profiles/${iccid}`);
const { activationCode, appleInstallUrl } = data;
// activationCode → "LPA:1$…" — install this, or render it as a QR code
// appleInstallUrl → installs the profile in one tap on iOSA profile is addressed by its ICCID. That is the value in the URL above, and it is
published on the order's esims[] and on every row of
GET /v1/esim/profiles. There is no separate profile id to look up.
The credential is a bearer credential. Treat activationCode as one: it installs the
eSIM, and anyone holding it can. A client that lost one can read it again from the profile.
Every response carrying it is sent Cache-Control: no-store, private, and it is kept out
of the gateway's own logs. Be clear about what that buys you: the header instructs caches
and proxies not to store the response, and well-behaved ones obey — but it is a request,
not enforcement, and it says nothing about what happens once the body reaches you. Your own
logs, your APM traces, a response cache in your framework, a service worker, or a browser
devtools export will all keep it unless you stop them. Handle it like a password on your
side of the wire.
We do not return a qrCodeUrl — no image is hosted for you. The activationCode is
everything a QR encodes, so generate the image client-side from that string if you need
one.
Errors follow the standard gateway error format. The codes each endpoint can return are listed on that endpoint in the API Reference.
Order statuses
| Status | Meaning | What to do |
|---|---|---|
pending | Accepted, not yet being provisioned. | Poll. |
processing | Being provisioned. | Poll. |
delivered | Every eSIM is ready; the order's esims[] carry the ICCID and activation code. | Install. |
partially_delivered | Some eSIMs were delivered, some failed. | Check the esims array. |
failed | Nothing was delivered. | You were not charged for undelivered items. Retry or contact support. |
refund_pending | A return of the charge is under review. | Wait for the decision. |
refunded | The charge was returned. | Nothing. |
cancelled | The order was cancelled before it was provisioned. | Nothing. You were not charged. |
requires_review | The order needs a human. | Contact support with the orderId. |
Filtering the list by status
GET /v1/esim/orders takes a status filter, and it uses the names above — the
same ones the orders carry. Five of them are accepted:
await api('GET', '/v1/esim/orders', { query: { status: 'refund_pending' } });| Filter | Selects |
|---|---|
pending, processing | Orders still being provisioned. |
delivered | Orders whose eSIMs are ready. |
refund_pending | Orders with a refund under review. |
refunded | Orders whose charge was returned. |
pending and processing are not separated by the filter — both return every
in-progress order, and each row's own status tells you which of the two it is.
Filter client-side on that field if you need one and not the other.
The remaining four — partially_delivered, failed, cancelled and
requires_review — appear on an order you fetch by id, but the list collection does
not serve them, so they are rejected as filters rather than answering an empty list
that would read as "you have none". Read those from
GET /v1/esim/orders/{orderId}.
Any other value is 400 VALIDATION_ERROR with fields: ["query.status"].
When things go wrong
| You see | Meaning | Do |
|---|---|---|
400 VALIDATION_ERROR | A field is wrong; fields says which. | Fix it. |
409 PRODUCT_UNAVAILABLE | The plan cannot be sold right now. | Choose another, or retry later. |
409 INSUFFICIENT_BALANCE | Not enough balance. | Top up, then send the purchase again. Nothing was charged, so nothing is duplicated. |
409 CONFLICT | The order is not in a state that allows this. | Read the order back and check its status. |
404 NOT_FOUND | No such order or eSIM — or not yours. | Check the id. |
There is no cancel or top-up operation on the eSIM client API. An order's status
tells you where it got to — including cancelled, which an order can reach through your
panel — but this API offers no verb to cancel one, and no way to buy a top-up. Orders
carry no actions block and profiles no top-up eligibility flag, because an eligibility
flag for an operation you cannot call is a branch you would write and never be able to
take. See the eSIM API Reference for the operations that do exist.
Full schemas are in the eSIM API Reference.