Top-ups
A top-up adds a package to an eSIM your customer already has, so they do not need to install a new one. The price of the package is charged to your partner balance. eSIMify sends nothing to your customer about it.
| Method | Path | What it does |
|---|---|---|
GET |
/v1/esims/{iccid}/topup-packages |
List the packages this eSIM can be topped up with. |
POST |
/v1/esims/{iccid}/topups |
Add one of those packages to the eSIM. |
GET |
/v1/esims/{iccid}/topups |
List the top-ups of an eSIM, newest first. |
GET |
/v1/topups/{id} |
Get one top-up. |
Not every eSIM can be topped up. Always list the top-up packages for the eSIM before you offer one.
How a top-up behaves
Section titled “How a top-up behaves”- A top-up adds the package’s data and validity to the eSIM.
GET /v1/esims/{iccid}shows the newdata.total_mb,validity_daysandexpires_atat once. - A
depletedeSIM becomesactiveagain. AreadyeSIM staysready: its validity still starts on first use. - An
expiredorcancelledeSIM cannot be topped up. - A top-up can be
completed,processingorfailed. Checkstatusin the response. Afailedtop-up added nothing, and the amount was returned to your balance. It is still answered with201. topup.completedis sent for a completed top-up. A failed one sends no event.- After a completed top-up,
esim.usage_80andesim.usage_100can be sent again for the new allowance. - Top-ups are not orders. They are not in
GET /v1/orders. List them withGET /v1/esims/{iccid}/topups. - While a top-up is running, the eSIM cannot be cancelled, and an eSIM that is being cancelled cannot be topped up.
List top-up packages
Section titled “List top-up packages”GET /v1/esims/{iccid}/topup-packages
Returns the packages that can be added to this eSIM, with your price for each. The list is not paginated and holds at most 200 packages. An empty data array, or 409 esim_not_topupable, means there is nothing to offer.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
iccid |
path | string | Yes | The ICCID of the eSIM. |
curl "$ESIMIFY_API_URL/v1/esims/8999999000000012345/topup-packages" \ -H "Authorization: Bearer $ESIMIFY_API_KEY"const res = await fetch(`${process.env.ESIMIFY_API_URL}/v1/esims/8999999000000012345/topup-packages`, { headers: { Authorization: `Bearer ${process.env.ESIMIFY_API_KEY}` },});const packages = await res.json();<?php$ch = curl_init(getenv('ESIMIFY_API_URL') . '/v1/esims/8999999000000012345/topup-packages');curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('ESIMIFY_API_KEY'), ],]);$packages = 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/esims/8999999000000012345/topup-packages", headers={"Authorization": "Bearer " + os.environ["ESIMIFY_API_KEY"]}, timeout=30,)packages = res.json()Response
Section titled “Response”200 OK with Package objects.
{ "data": [ { "id": "pkg_10482", "name": "Thailand 1 GB 7 days", "type": "country", "country": "TH", "countries": [ "TH" ], "data_mb": 1024, "unlimited": false, "validity_days": 7, "price": { "amount": "249.00", "currency": "INR" }, "retail_price": { "amount": "349.00", "currency": "INR" }, "topup": true } ]}Errors
Section titled “Errors”| Status | Code | When |
|---|---|---|
404 |
esim_not_found |
No eSIM has this ICCID for your account in this mode. |
409 |
esim_not_topupable |
The eSIM is expired or cancelled. |
Any endpoint can also return the common errors.
Create a top-up
Section titled “Create a top-up”POST /v1/esims/{iccid}/topups
Adds a package to the eSIM and charges its price to your balance.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
iccid |
path | string | Yes | The ICCID of the eSIM. |
Idempotency-Key |
header | string | Yes | 1 to 255 characters. Use a new one for each top-up. See Idempotency. |
package_id |
body | string | Yes | A package id from this eSIM’s top-up packages. |
customer_ref |
body | string | No | Your own reference for the top-up, up to 120 characters. |
curl -X POST "$ESIMIFY_API_URL/v1/esims/8999999000000012345/topups" \ -H "Authorization: Bearer $ESIMIFY_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: booking-84512-topup-1" \ -d '{ "package_id": "pkg_10482", "customer_ref": "booking-84512-topup-1" }'const res = await fetch(`${process.env.ESIMIFY_API_URL}/v1/esims/8999999000000012345/topups`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.ESIMIFY_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'booking-84512-topup-1', }, body: JSON.stringify({ package_id: 'pkg_10482', customer_ref: 'booking-84512-topup-1', }),});const topup = await res.json();<?php$ch = curl_init(getenv('ESIMIFY_API_URL') . '/v1/esims/8999999000000012345/topups');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-topup-1', ], CURLOPT_POSTFIELDS => json_encode([ 'package_id' => 'pkg_10482', 'customer_ref' => 'booking-84512-topup-1', ]),]);$topup = 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/esims/8999999000000012345/topups", headers={ "Authorization": "Bearer " + os.environ["ESIMIFY_API_KEY"], "Idempotency-Key": "booking-84512-topup-1", }, json={ "package_id": "pkg_10482", "customer_ref": "booking-84512-topup-1", }, timeout=30,)topup = res.json()Response
Section titled “Response”201 Created with a Topup. A replay answers 200 OK.
{ "id": "top_test_7a1c9e02b4d6", "status": "completed", "iccid": "8999999000000012345", "package_id": "pkg_10482", "customer_ref": "booking-84512-topup-1", "total": { "amount": "249.00", "currency": "INR" }, "balance_after": { "amount": "999502.00", "currency": "INR" }, "created": "2026-10-04T06:30:12Z"}Check status. completed means the data was added. failed means the mobile network provider did not apply the top-up: nothing was added and the amount was returned to your balance. processing means it is still being applied: fetch it again with GET /v1/topups/{id}.
Sending the same Idempotency-Key and body again returns the same top-up with the header Idempotent-Replayed: true. The data is added once. That is also true for a failed top-up: to try again, use a new Idempotency-Key.
A topup.completed webhook is sent when the top-up is done. Fetch the eSIM afterwards to see its new data values.
In the sandbox, the customer_ref triggers work on top-ups too.
Errors
Section titled “Errors”| Status | Code | When |
|---|---|---|
400 |
invalid_request |
package_id is missing, customer_ref is longer than 120 characters or has control 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 top-up. Nothing was added. |
404 |
esim_not_found |
No eSIM has this ICCID for your account in this mode. |
404 |
package_not_found |
package_id is not one of this eSIM’s top-up packages. That covers an id that does not exist, and a real package this eSIM cannot take. |
409 |
esim_not_topupable |
The eSIM is expired or cancelled, or it is being cancelled. |
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 top-up the mobile network provider does not apply comes back as a Topup with the status failed. |
Any endpoint can also return the common errors.
List the top-ups of an eSIM
Section titled “List the top-ups of an eSIM”GET /v1/esims/{iccid}/topups
Returns every top-up of one eSIM, newest first, whatever its status. Use it to reconcile: a top-up whose response you lost is here.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
iccid |
path | string | Yes | The ICCID of the eSIM. |
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/esims/8999999000000012345/topups?limit=10" \ -H "Authorization: Bearer $ESIMIFY_API_KEY"const res = await fetch(`${process.env.ESIMIFY_API_URL}/v1/esims/8999999000000012345/topups?limit=10`, { headers: { Authorization: `Bearer ${process.env.ESIMIFY_API_KEY}` },});const topups = await res.json();<?php$ch = curl_init(getenv('ESIMIFY_API_URL') . '/v1/esims/8999999000000012345/topups?limit=10');curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('ESIMIFY_API_KEY'), ],]);$topups = 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/esims/8999999000000012345/topups", headers={"Authorization": "Bearer " + os.environ["ESIMIFY_API_KEY"]}, params={"limit": "10"}, timeout=30,)topups = res.json()Response
Section titled “Response”200 OK with a list of Topup objects.
{ "data": [ { "id": "top_test_7a1c9e02b4d6", "status": "completed", "iccid": "8999999000000012345", "package_id": "pkg_10482", "customer_ref": "booking-84512-topup-1", "total": { "amount": "249.00", "currency": "INR" }, "balance_after": { "amount": "999502.00", "currency": "INR" }, "created": "2026-10-04T06:30:12Z" } ], "has_more": false, "next_cursor": null}Errors
Section titled “Errors”| Status | Code | When |
|---|---|---|
400 |
invalid_request |
limit or cursor is not valid. param names it. |
404 |
esim_not_found |
No eSIM has this ICCID for your account in this mode. |
Any endpoint can also return the common errors.
Get a top-up
Section titled “Get a top-up”GET /v1/topups/{id}
Returns one top-up.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string | Yes | The top-up id, such as top_test_7a1c9e02b4d6. |
curl "$ESIMIFY_API_URL/v1/topups/top_test_7a1c9e02b4d6" \ -H "Authorization: Bearer $ESIMIFY_API_KEY"const res = await fetch(`${process.env.ESIMIFY_API_URL}/v1/topups/top_test_7a1c9e02b4d6`, { headers: { Authorization: `Bearer ${process.env.ESIMIFY_API_KEY}` },});const topup = await res.json();<?php$ch = curl_init(getenv('ESIMIFY_API_URL') . '/v1/topups/top_test_7a1c9e02b4d6');curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('ESIMIFY_API_KEY'), ],]);$topup = 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/topups/top_test_7a1c9e02b4d6", headers={"Authorization": "Bearer " + os.environ["ESIMIFY_API_KEY"]}, timeout=30,)topup = res.json()Response
Section titled “Response”200 OK with a Topup.
{ "id": "top_test_7a1c9e02b4d6", "status": "completed", "iccid": "8999999000000012345", "package_id": "pkg_10482", "customer_ref": "booking-84512-topup-1", "total": { "amount": "249.00", "currency": "INR" }, "balance_after": { "amount": "999502.00", "currency": "INR" }, "created": "2026-10-04T06:30:12Z"}Errors
Section titled “Errors”| Status | Code | When |
|---|---|---|
404 |
topup_not_found |
No top-up has this id for your account in this mode. A live key cannot see sandbox top-ups, and a test key cannot see live ones. |
Any endpoint can also return the common errors.

