curl --request POST \
--url https://sandbox-api.sakawave.example/v1/esims \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"packageSlug": "united-states-5gb-30d-pkgus5gb",
"quantity": 1,
"debitAccountId": "01K2Z3AB4D5E6F7G8H9JKMNPQR",
"fxQuoteId": "01K2Z3AB4W5X6Y7Z8ABCDEFGHJ",
"externalReference": "esim_20260829_001"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
packageSlug: 'united-states-5gb-30d-pkgus5gb',
quantity: 1,
debitAccountId: '01K2Z3AB4D5E6F7G8H9JKMNPQR',
fxQuoteId: '01K2Z3AB4W5X6Y7Z8ABCDEFGHJ',
externalReference: 'esim_20260829_001'
})
};
fetch('https://sandbox-api.sakawave.example/v1/esims', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://sandbox-api.sakawave.example/v1/esims"
payload = {
"packageSlug": "united-states-5gb-30d-pkgus5gb",
"quantity": 1,
"debitAccountId": "01K2Z3AB4D5E6F7G8H9JKMNPQR",
"fxQuoteId": "01K2Z3AB4W5X6Y7Z8ABCDEFGHJ",
"externalReference": "esim_20260829_001"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"id": "01K2Z3AB4S5T6V7W8XYZABCDEF",
"kind": "purchase",
"status": "completed",
"package": {
"id": "01K2Z3AB4C5D6E7F8GHJKMNPQR",
"code": "PKG-US-5GB",
"name": "United States 5GB"
},
"quantity": 1,
"customerId": null,
"price": {
"amount": "999",
"currency": "USD",
"display": "9.99"
},
"paid": {
"amount": "1568430",
"currency": "NGN",
"display": "15,684.30"
},
"fee": {
"amount": "0",
"currency": "NGN",
"display": "0.00"
},
"refunded": {
"amount": "0",
"currency": "NGN",
"display": "0.00"
},
"rate": "1570.000000000000",
"externalReference": "esim_20260829_001",
"failureReason": null,
"profileIds": [
"01K2Z3AB4R5S6T7V8WXYZABCDE"
],
"completedAt": "2026-08-29T10:30:12.000Z",
"createdAt": "2026-08-29T10:30:00.000Z",
"updatedAt": "2026-08-29T10:30:12.000Z"
}{
"statusCode": 400,
"message": "Must be a valid identifier.",
"code": "validation_failed",
"errors": [
{
"message": "Must be a valid identifier.",
"field": "accountId",
"code": "custom",
"path": [
"accountId"
]
}
]
}{
"statusCode": 401,
"code": "INVALID_API_KEY",
"message": "Invalid or inactive API credentials."
}{
"statusCode": 404,
"code": "resource_not_found",
"message": "Resource not found."
}{
"statusCode": 409,
"code": "resource_conflict",
"message": "The request conflicts with the current resource state."
}{
"statusCode": 429,
"message": "Too many API requests. Try again later.",
"code": "api_key_rate_limit_exceeded",
"retryAfterSeconds": 17
}{
"statusCode": 500,
"code": "internal_server_error",
"message": "An unexpected error occurred."
}{
"statusCode": 503,
"code": "provider_unavailable",
"message": "We could not issue an account number just now. Try again shortly."
}Buy an eSIM
Debits the account and orders the eSIM. The debit is immediate; the profile usually arrives within seconds.
curl --request POST \
--url https://sandbox-api.sakawave.example/v1/esims \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"packageSlug": "united-states-5gb-30d-pkgus5gb",
"quantity": 1,
"debitAccountId": "01K2Z3AB4D5E6F7G8H9JKMNPQR",
"fxQuoteId": "01K2Z3AB4W5X6Y7Z8ABCDEFGHJ",
"externalReference": "esim_20260829_001"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
packageSlug: 'united-states-5gb-30d-pkgus5gb',
quantity: 1,
debitAccountId: '01K2Z3AB4D5E6F7G8H9JKMNPQR',
fxQuoteId: '01K2Z3AB4W5X6Y7Z8ABCDEFGHJ',
externalReference: 'esim_20260829_001'
})
};
fetch('https://sandbox-api.sakawave.example/v1/esims', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://sandbox-api.sakawave.example/v1/esims"
payload = {
"packageSlug": "united-states-5gb-30d-pkgus5gb",
"quantity": 1,
"debitAccountId": "01K2Z3AB4D5E6F7G8H9JKMNPQR",
"fxQuoteId": "01K2Z3AB4W5X6Y7Z8ABCDEFGHJ",
"externalReference": "esim_20260829_001"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"id": "01K2Z3AB4S5T6V7W8XYZABCDEF",
"kind": "purchase",
"status": "completed",
"package": {
"id": "01K2Z3AB4C5D6E7F8GHJKMNPQR",
"code": "PKG-US-5GB",
"name": "United States 5GB"
},
"quantity": 1,
"customerId": null,
"price": {
"amount": "999",
"currency": "USD",
"display": "9.99"
},
"paid": {
"amount": "1568430",
"currency": "NGN",
"display": "15,684.30"
},
"fee": {
"amount": "0",
"currency": "NGN",
"display": "0.00"
},
"refunded": {
"amount": "0",
"currency": "NGN",
"display": "0.00"
},
"rate": "1570.000000000000",
"externalReference": "esim_20260829_001",
"failureReason": null,
"profileIds": [
"01K2Z3AB4R5S6T7V8WXYZABCDE"
],
"completedAt": "2026-08-29T10:30:12.000Z",
"createdAt": "2026-08-29T10:30:00.000Z",
"updatedAt": "2026-08-29T10:30:12.000Z"
}{
"statusCode": 400,
"message": "Must be a valid identifier.",
"code": "validation_failed",
"errors": [
{
"message": "Must be a valid identifier.",
"field": "accountId",
"code": "custom",
"path": [
"accountId"
]
}
]
}{
"statusCode": 401,
"code": "INVALID_API_KEY",
"message": "Invalid or inactive API credentials."
}{
"statusCode": 404,
"code": "resource_not_found",
"message": "Resource not found."
}{
"statusCode": 409,
"code": "resource_conflict",
"message": "The request conflicts with the current resource state."
}{
"statusCode": 429,
"message": "Too many API requests. Try again later.",
"code": "api_key_rate_limit_exceeded",
"retryAfterSeconds": 17
}{
"statusCode": 500,
"code": "internal_server_error",
"message": "An unexpected error occurred."
}{
"statusCode": 503,
"code": "provider_unavailable",
"message": "We could not issue an account number just now. Try again shortly."
}Statuses
| Status | Meaning |
|---|---|
provisioning | Paid for, and being set up with the network. Poll, or wait for esim.order.completed. |
completed | The eSIM exists. profileIds names it. This is the success state. |
failed | The network could not issue it. |
refunded | Failed, and the money has been returned in full. |
partially_refunded | Fewer eSIMs were issued than paid for; the difference has been returned. |
Paying from any currency
The plan is priced in USD, but the account may hold anything. A non-USD account needs anfxQuoteId from a quote; a USD account needs nothing. paid on the response is what actually left the account.
Idempotency
externalReference is the key, and reusing it returns the existing order whatever its status. A retry after a network failure is therefore safe: it returns the original order rather than buying a second eSIM.
Error codes
| Status | code | Cause |
|---|---|---|
409 | esim_fx_quote_required | The account is not USD and no usable quote was supplied, or the quote expired, was already spent, or the price moved. Quote again. |
409 | esim_account_ineligible | The account is inactive, cannot be debited, or is a customer wallet. |
409 | insufficient_funds | The account does not hold enough. |
404 | esim_package_unavailable | The plan has been withdrawn since you read the catalog. |
401 | INVALID_API_KEY | The key is missing, invalid, inactive, expired, from the other environment, or blocked by its IP allowlist. |
429 | api_key_rate_limit_exceeded | The per-minute limit for this key was exceeded. |
503 | esim_feature_disabled | eSIM is not enabled on this deployment. |
503 | esim_catalog_unavailable | The catalog is empty and could not be refreshed from the provider. |
503 | esim_provider_unavailable | The eSIM provider could not be reached. Nothing was charged. |
Authorizations
A secret API key, sent as Authorization: Bearer sk_test_… in sandbox or Bearer sk_live_… in live.
Body
Provide exactly one of packageSlug or packageId.
A 26-character ULID.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
Your idempotency key.
1 - 120200A 26-character ULID.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
1 <= x <= 10Required when the account is not USD. From the quote.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
Assigns the eSIM to one of your customers.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
Response
Successful response.
A 26-character ULID.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
purchase, topup completed is the success state. A failure refunds itself.
provisioning, completed, failed, refunded, partially_refunded Show child attributes
Show child attributes
x >= 1A 26-character ULID.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
The USD list price.
Show child attributes
Show child attributes
What was debited, in the paying account’s own currency.
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
The eSIMs this order allocated.
A 26-character ULID.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"2026-08-14T09:00:00.000Z"
"2026-08-14T09:00:00.000Z"
"2026-08-14T09:00:00.000Z"