> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sakawave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# eSIM setup and configuration

> Accounts, the quote window, idempotency, and how failures return money on their own.

## Credentials and accounts

API key only. The paying account must be:

* **active**, and **debitable**
* **one of your own** — a customer wallet is refused
* **any currency you hold** — unlike bill payments, nothing is restricted to one

Anything else is `409 esim_account_ineligible`, one code for all three causes.

There is no tier gate on eSIM. If your key works and you hold an eligible account, the service is available — subject to it being enabled on the deployment at all, which surfaces as `503 esim_feature_disabled`.

## The catalog

Synchronised from the network and cached by Sakawave, refreshed a few times a day. If it is empty when you call, Sakawave refreshes inline; if that fails you get `503 esim_catalog_unavailable`.

Cache it your side too. Re-read before quoting if your copy is more than an hour old — a plan withdrawn between your catalog read and your order produces `404 esim_package_unavailable`.

<Note>
  Destination codes and plan slugs are stable in practice but not guaranteed. Store slugs against your own product records rather than treating them as permanent keys.
</Note>

## The quote window

A quote lasts about **two minutes** and is spent by the order that uses it.

```ts theme={null}
// Wrong: the rate ages while the customer reads the screen.
const quote = await quoteEsim(plan, account);
await showConfirmation(quote);          // customer goes to find their passport
await buy(quote.fxQuoteId);             // 409 esim_fx_quote_required

// Right: quote, confirm, buy — and re-quote if they were slow.
let quote = await quoteEsim(plan, account);
const confirmed = await showConfirmation(quote);
if (!confirmed) return;

if (Date.parse(quote.expiresAt) - Date.now() < 15_000) {
  quote = await quoteEsim(plan, account);   // show the new figure before charging
}
await buy(quote.fxQuoteId);
```

One `409 esim_fx_quote_required` covers every way a quote can be unusable: absent, expired, already spent, bound to a different account, or priced against a catalog figure that has since moved. In all of them the fix is the same — quote again, show the new figure, then buy.

<Warning>
  Re-quoting silently and charging the new figure defeats the point. If the price moved, the customer agreed to the old one; show them the new one before you take the money.
</Warning>

## Idempotency

`externalReference` is the key, unique per business. Reusing it returns the existing order **whatever its status** — including a failure.

That is the same contract as [transfers](/api-reference/transfers/create-a-transfer) and [bill payments](/api-reference/bill-payments/pay-a-bill), and deliberately not the re-driving one [cards](/api-reference/cards/issue-a-card) use. A genuine second attempt after a failure needs a fresh reference and a fresh quote.

Sakawave also sends the network a deterministic identifier of its own, so a retry inside Sakawave — after a timeout, say — can never allocate two eSIMs for one order.

## How failures return money

Nothing here requires you to claim a refund.

| What happened | What Sakawave does |
| - | - |
| The network refused the order | Full refund, order `refunded` |
| The network never answered | Retried for about two hours, then failed and refunded |
| Fewer eSIMs issued than paid for | Pro-rata refund, order `partially_refunded` |
| You cancel an unused eSIM | Full refund for that eSIM |
| You revoke a used eSIM | **Nothing.** The network has been paid. |

Every refund returns exactly what was debited, in the currency it was debited in. Sakawave absorbs any rate movement in between rather than passing it on.

## Provisioning and reconciliation

An order that comes back `provisioning` is being chased on your behalf. Sakawave asks the network on a short backoff for roughly two hours; if nothing resolves in that time the order fails and refunds itself.

The network distinguishes *"not yet"* from *"no"*, and so does Sakawave. While a profile is still being generated the network answers "the batchOrder has been getting resource" — which is progress, not a failure, and does not end the order. Only an actual refusal does.

You do not need to implement that loop. Take `esim.order.completed` and `esim.order.failed`, or poll the order while it is `provisioning`.

## Assigning to a customer

Pass `customerId` on the order and the eSIM belongs to that customer. It changes nothing about who is billed — your account is still debited — but it lets you list a customer's eSIMs, and it is what a support agent will search on.

## Rate limits

| Operation | Per minute |
| - | - |
| Catalog reads | Your key's default |
| Quote, buy, top up | 120 |
| Cancel, pause, resume | 60 |
| Revoke | 30 |
| Send SMS | **10** |

SMS reaches a real handset, which is why it is the tightest. It is not a notification channel.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.