Orders
An order buys one package, one or more times. Each unit becomes one eSIM. The order total is charged to your partner balance when the order is created, at the package’s price at that moment.
| Method | Path | What it does |
|---|---|---|
POST |
/v1/orders |
Buy one or more eSIMs. |
GET |
/v1/orders/{id} |
Get one order. |
GET |
/v1/orders |
List your orders, newest first. |
Order status
Section titled “Order status”| Status | Meaning | Final |
|---|---|---|
processing |
The order is accepted and charged. The eSIMs are still being issued. esims lists the ones issued so far. |
No |
completed |
Every eSIM was issued. | Yes |
partially_completed |
Some eSIMs were issued and some were not. esims holds the ones that were, and refunded holds the amount returned to your balance. |
Yes |
failed |
No eSIM was issued. refunded holds the amount returned to your balance. |
Yes |
cancelled |
The order was cancelled in the Business portal or by eSIMify support, before its eSIMs were used. refunded holds the amount returned. No webhook is sent. |
Yes |
An order in processing moves to one of the final statuses on its own. You are told by the order.completed or order.failed webhook, or you can fetch the order again. Most orders finish within a few minutes. An order that makes no progress for 15 minutes is settled automatically: the eSIMs that were not issued are refunded.
order.completed is sent for completed and for partially_completed orders. Always read status and count esims: a partially completed order holds fewer eSIMs than quantity, and refunded shows what went back to your balance.
A supplier failure in live does not give an error response. It gives an order with the status failed or partially_completed, answered with 201.
Top-ups are not orders. They are not in this list. See Top-ups.
Orders your staff place in the Business portal appear here too, and send the same events. Such an order can hold several packages. Then items lists them and the top-level package_id and quantity are null.
eSIMify does not email or message your customer. Delivery is yours. See Delivering eSIMs.
Create an order
Section titled “Create an order”POST /v1/orders
Buys quantity eSIMs of one package.
The call waits up to about 20 seconds for the eSIMs. Set your HTTP client timeout to at least 30 seconds. If the eSIMs are not all issued by then, the order comes back as processing and finishes in the background. This is more likely for large quantities.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | string | Yes | 1 to 255 characters. Identifies this purchase on your side. Use a new one for each new order. See Idempotency. |
package_id |
body | string | Yes | The exact id of the package to buy, such as pkg_10482. |
quantity |
body | integer | No | How many eSIMs, from 1 to 50. Default 1. It must be a JSON number: "2" is refused. |
customer_ref |
body | string | No | Your own reference for the sale, up to 120 characters. It is copied to the order and to each eSIM, and you can filter by it. |
customer |
body | object | No | Optional details of the traveller. They are shown to your staff on the order in the Business portal. They are not returned by the API, and eSIMify never uses them to contact the traveller. |
customer.name |
body | string | No | The traveller’s name, up to 120 characters. |
customer.email |
body | string | No | The traveller’s email address, up to 254 characters. It must be one valid address. |
curl -X POST "$ESIMIFY_API_URL/v1/orders" \ -H "Authorization: Bearer $ESIMIFY_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: booking-84512" \ -d '{ "package_id": "pkg_10482", "quantity": 1, "customer_ref": "booking-84512" }'const res = await fetch(`${process.env.ESIMIFY_API_URL}/v1/orders`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.ESIMIFY_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'booking-84512', }, body: JSON.stringify({ package_id: 'pkg_10482', quantity: 1, customer_ref: 'booking-84512', }),});const order = await res.json();<?php$ch = curl_init(getenv('ESIMIFY_API_URL') . '/v1/orders');curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('ESIMIFY_API_KEY'), 'Content-Type: application/json', 'Idempotency-Key: booking-84512', ], CURLOPT_POSTFIELDS => json_encode([ 'package_id' => 'pkg_10482', 'quantity' => 1, 'customer_ref' => 'booking-84512', ]),]);$order = json_decode(curl_exec($ch), true);$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);import osimport requests
res = requests.post( os.environ["ESIMIFY_API_URL"] + "/v1/orders", headers={ "Authorization": "Bearer " + os.environ["ESIMIFY_API_KEY"], "Idempotency-Key": "booking-84512", }, json={ "package_id": "pkg_10482", "quantity": 1, "customer_ref": "booking-84512", }, timeout=30,)order = res.json()Response
Section titled “Response”201 Created with the new Order.
{ "id": "ord_test_3f9a1c2b7d4e", "status": "completed", "mode": "test", "package_id": "pkg_10482", "quantity": 1, "items": [ { "package_id": "pkg_10482", "quantity": 1 } ], "customer_ref": "booking-84512", "total": { "amount": "249.00", "currency": "INR" }, "refunded": { "amount": "0.00", "currency": "INR" }, "balance_after": { "amount": "999751.00", "currency": "INR" }, "created": "2026-10-02T09:14:07Z", "esims": [ { "iccid": "8999999000000012345", "status": "ready" } ]}If you send the same Idempotency-Key and the same body again, you get 200 OK, the same order, and the header Idempotent-Replayed: true. Nothing is bought twice. The replay is the first response as it was sent: fetch the order for its current state. This also applies to an order that failed: to try again, use a new Idempotency-Key.
Check status before you deliver. Most orders are completed in the same call. An order can also come back as processing:
{ "id": "ord_test_91b7e04c5a2d", "status": "processing", "mode": "test", "package_id": "pkg_10482", "quantity": 1, "items": [ { "package_id": "pkg_10482", "quantity": 1 } ], "customer_ref": "test_processing-84513", "total": { "amount": "249.00", "currency": "INR" }, "refunded": { "amount": "0.00", "currency": "INR" }, "balance_after": { "amount": "999751.00", "currency": "INR" }, "created": "2026-10-02T09:14:07Z", "esims": []}Errors
Section titled “Errors”| Status | Code | When |
|---|---|---|
400 |
invalid_request |
package_id is missing, quantity is not a whole number from 1 to 50, customer_ref or customer.name is longer than 120 characters, customer.email is not a valid address, a text field has control characters, the Idempotency-Key is longer than 255 characters, or the body is not a JSON object. param names the field when one field is at fault. |
400 |
idempotency_key_required |
A POST was sent without an Idempotency-Key header. |
402 |
insufficient_balance |
Your balance does not cover the order. Nothing was bought. Add funds and send the same request again with the same key. |
404 |
package_not_found |
No package you can sell has this id. |
409 |
idempotency_key_reused |
This Idempotency-Key was already used with a different request body. |
409 |
request_in_progress |
A request with the same Idempotency-Key is still being processed. Wait a moment and send it again. |
503 |
supplier_unavailable |
Sandbox only: returned for the test_supplier_down trigger. In live, a supplier failure gives an order with the status failed and a full refund. Retry a 503 with the same Idempotency-Key. |
Any endpoint can also return the common errors.
Get an order
Section titled “Get an order”GET /v1/orders/{id}
Returns one order with the eSIMs issued for it.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string | Yes | The order id, such as ord_test_3f9a1c2b7d4e. |
curl "$ESIMIFY_API_URL/v1/orders/ord_test_3f9a1c2b7d4e" \ -H "Authorization: Bearer $ESIMIFY_API_KEY"const res = await fetch(`${process.env.ESIMIFY_API_URL}/v1/orders/ord_test_3f9a1c2b7d4e`, { headers: { Authorization: `Bearer ${process.env.ESIMIFY_API_KEY}` },});const order = await res.json();<?php$ch = curl_init(getenv('ESIMIFY_API_URL') . '/v1/orders/ord_test_3f9a1c2b7d4e');curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('ESIMIFY_API_KEY'), ],]);$order = json_decode(curl_exec($ch), true);$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);import osimport requests
res = requests.get( os.environ["ESIMIFY_API_URL"] + "/v1/orders/ord_test_3f9a1c2b7d4e", headers={"Authorization": "Bearer " + os.environ["ESIMIFY_API_KEY"]}, timeout=30,)order = res.json()Response
Section titled “Response”200 OK with an Order.
{ "id": "ord_test_3f9a1c2b7d4e", "status": "completed", "mode": "test", "package_id": "pkg_10482", "quantity": 3, "items": [ { "package_id": "pkg_10482", "quantity": 3 } ], "customer_ref": "booking-84512", "total": { "amount": "747.00", "currency": "INR" }, "refunded": { "amount": "0.00", "currency": "INR" }, "balance_after": { "amount": "999253.00", "currency": "INR" }, "created": "2026-10-02T09:14:07Z", "esims": [ { "iccid": "8999999000000012345", "status": "ready" }, { "iccid": "8999999000000012346", "status": "ready" }, { "iccid": "8999999000000012347", "status": "ready" } ]}esims lists only the units that were issued. Fetch each one with GET /v1/esims/{iccid} for its install details.
Errors
Section titled “Errors”| Status | Code | When |
|---|---|---|
404 |
order_not_found |
No order has this id for your account in this mode. A live key cannot see sandbox orders, and a test key cannot see live ones. Ids are exact and lower case. |
Any endpoint can also return the common errors.
List orders
Section titled “List orders”GET /v1/orders
Returns your orders, newest first. Top-ups are not included.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
status |
query | string | No | One order status: processing, completed, partially_completed, failed or cancelled. |
customer_ref |
query | string | No | Orders with exactly this customer_ref. Case matters. |
created_after |
query | string | No | ISO 8601 date or timestamp. Orders created at or after it. |
created_before |
query | string | No | ISO 8601 date or timestamp. Orders created before it, not including that instant. |
limit |
query | integer | No | 1 to 100. Default 25. |
cursor |
query | string | No | The next_cursor of the previous page. |
curl "$ESIMIFY_API_URL/v1/orders?customer_ref=booking-84512" \ -H "Authorization: Bearer $ESIMIFY_API_KEY"const res = await fetch(`${process.env.ESIMIFY_API_URL}/v1/orders?customer_ref=booking-84512`, { headers: { Authorization: `Bearer ${process.env.ESIMIFY_API_KEY}` },});const orders = await res.json();<?php$ch = curl_init(getenv('ESIMIFY_API_URL') . '/v1/orders?customer_ref=booking-84512');curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('ESIMIFY_API_KEY'), ],]);$orders = json_decode(curl_exec($ch), true);$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);import osimport requests
res = requests.get( os.environ["ESIMIFY_API_URL"] + "/v1/orders", headers={"Authorization": "Bearer " + os.environ["ESIMIFY_API_KEY"]}, params={"customer_ref": "booking-84512"}, timeout=30,)orders = res.json()Response
Section titled “Response”200 OK with a list of Order objects.
{ "data": [ { "id": "ord_test_3f9a1c2b7d4e", "status": "completed", "mode": "test", "package_id": "pkg_10482", "quantity": 1, "items": [ { "package_id": "pkg_10482", "quantity": 1 } ], "customer_ref": "booking-84512", "total": { "amount": "249.00", "currency": "INR" }, "refunded": { "amount": "0.00", "currency": "INR" }, "balance_after": { "amount": "999751.00", "currency": "INR" }, "created": "2026-10-02T09:14:07Z", "esims": [ { "iccid": "8999999000000012345", "status": "ready" } ] } ], "has_more": false, "next_cursor": null}The two time filters make a half-open window: created_after is included and created_before is not. Two windows that share a boundary, such as one day and the next, never miss an order and never return one twice. A value without a zone is read as UTC. created in the response is given to the second, while the filters compare to the millisecond, so to page through new orders use cursor, not the created of the last order you saw.
Errors
Section titled “Errors”| Status | Code | When |
|---|---|---|
400 |
invalid_request |
A query parameter is not valid, for example an unknown status, a timestamp that is not ISO 8601, or a date that does not exist. param names it. |
Any endpoint can also return the common errors.

