Quote a purchase
curl --request POST \
--url https://sandbox-api.sakawave.example/v1/esims/quote \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"packageSlug": "united-states-5gb-30d-pkgus5gb",
"quantity": 1,
"debitAccountId": "01K2Z3AB4D5E6F7G8H9JKMNPQR"
}
'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'
})
};
fetch('https://sandbox-api.sakawave.example/v1/esims/quote', 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/quote"
payload = {
"packageSlug": "united-states-5gb-30d-pkgus5gb",
"quantity": 1,
"debitAccountId": "01K2Z3AB4D5E6F7G8H9JKMNPQR"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"package": {
"slug": "united-states-5gb-30d-pkgus5gb",
"name": "United States 5GB"
},
"quantity": 1,
"price": {
"amount": "999",
"currency": "USD",
"display": "9.99"
},
"fee": {
"amount": "0",
"currency": "USD",
"display": "0.00"
},
"payable": {
"amount": "1568430",
"currency": "NGN",
"display": "15,684.30"
},
"rate": "1570.000000000000",
"fxQuoteId": "01K2Z3AB4W5X6Y7Z8ABCDEFGHJ",
"expiresAt": "2026-08-29T10:32:00.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."
}eSIM
Quote a purchase
What a plan costs from a particular account, holding the exchange rate when that account is not USD.
POST
/
v1
/
esims
/
quote
Quote a purchase
curl --request POST \
--url https://sandbox-api.sakawave.example/v1/esims/quote \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"packageSlug": "united-states-5gb-30d-pkgus5gb",
"quantity": 1,
"debitAccountId": "01K2Z3AB4D5E6F7G8H9JKMNPQR"
}
'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'
})
};
fetch('https://sandbox-api.sakawave.example/v1/esims/quote', 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/quote"
payload = {
"packageSlug": "united-states-5gb-30d-pkgus5gb",
"quantity": 1,
"debitAccountId": "01K2Z3AB4D5E6F7G8H9JKMNPQR"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"package": {
"slug": "united-states-5gb-30d-pkgus5gb",
"name": "United States 5GB"
},
"quantity": 1,
"price": {
"amount": "999",
"currency": "USD",
"display": "9.99"
},
"fee": {
"amount": "0",
"currency": "USD",
"display": "0.00"
},
"payable": {
"amount": "1568430",
"currency": "NGN",
"display": "15,684.30"
},
"rate": "1570.000000000000",
"fxQuoteId": "01K2Z3AB4W5X6Y7Z8ABCDEFGHJ",
"expiresAt": "2026-08-29T10:32:00.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."
}Moves no money and commits you to nothing. It exists so the figure you show a customer is the figure that leaves their account.
USD accounts need no quote
When the account is already USD,payable is simply price plus fee, and fxQuoteId, rate and expiresAt are all null. You may buy without quoting at all.
Any other currency does
The catalog is USD; the account is not. The quote holds a rate for about two minutes and returns anfxQuoteId. Send that id when you buy — without it the purchase is refused.
A quote is bound to one account, one plan, and one price. It cannot be re-pointed at a different account, and it can back exactly one order.
Error codes
| Status | code | Cause |
|---|---|---|
404 | account_not_found | No such account for this business. |
409 | esim_account_ineligible | The account is inactive, cannot be debited, or is a customer wallet. |
503 | esim_fx_quote_unavailable | The rate provider could not be reached. |
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
application/json
Provide exactly one of packageSlug or packageId.
Any active, debitable account you own, in any currency.
Required string length:
26Pattern:
^[0-7][0-9A-HJKMNP-TV-Z]{25}$Example:
"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
Maximum string length:
200A 26-character ULID.
Required string length:
26Pattern:
^[0-7][0-9A-HJKMNP-TV-Z]{25}$Example:
"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
Required range:
1 <= x <= 10Response
Successful response.
Show child attributes
Show child attributes
Required range:
x >= 1Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Units of the paying currency per USD. Null when the account is already USD.
Send this when buying. Null when no conversion is needed.
Required string length:
26Pattern:
^[0-7][0-9A-HJKMNP-TV-Z]{25}$Example:
"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
Example:
"2026-08-14T09:00:00.000Z"