curl --request POST \
--url https://sandbox-api.sakawave.example/v1/accounts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Ada NGN wallet",
"type": "operational",
"currency": "NGN",
"customerId": "01K2Z3AB4C5D6E7F8G9HJKMNPQ",
"metadata": {
"cohort": "sandbox-tutorial"
}
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Ada NGN wallet',
type: 'operational',
currency: 'NGN',
customerId: '01K2Z3AB4C5D6E7F8G9HJKMNPQ',
metadata: {cohort: 'sandbox-tutorial'}
})
};
fetch('https://sandbox-api.sakawave.example/v1/accounts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://sandbox-api.sakawave.example/v1/accounts"
payload = {
"name": "Ada NGN wallet",
"type": "operational",
"currency": "NGN",
"customerId": "01K2Z3AB4C5D6E7F8G9HJKMNPQ",
"metadata": { "cohort": "sandbox-tutorial" }
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"id": "01K2Z3AB4D5E6F7G8H9JKMNPQR",
"name": "Ada NGN wallet",
"type": "operational",
"category": "fiat",
"status": "active",
"currency": "NGN",
"balance": "2500000",
"balanceFormatted": "25000.00",
"tag": "AA0271849366",
"canDebit": true,
"canCredit": true,
"isWalletAccount": true,
"transferReady": true,
"customerId": "01K2Z3AB4C5D6E7F8G9HJKMNPQ",
"metadata": {
"cohort": "sandbox-tutorial"
},
"closedAt": null,
"closureReason": null,
"createdAt": "2026-08-14T09:02:00.000Z",
"updatedAt": "2026-08-14T09:02: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": 403,
"message": "International wallets for your customers are not available yet. Your own approval cannot be used for a customer wallet.",
"code": "international_customer_wallet_not_supported",
"currency": "USD"
}{
"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."
}Open an account
Opens an account for your business, or — when customerId is supplied — a wallet account for that customer. The account opens active with a zero balance and a freshly minted transfer tag. It cannot yet send or receive externally: issue a detail first.
curl --request POST \
--url https://sandbox-api.sakawave.example/v1/accounts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Ada NGN wallet",
"type": "operational",
"currency": "NGN",
"customerId": "01K2Z3AB4C5D6E7F8G9HJKMNPQ",
"metadata": {
"cohort": "sandbox-tutorial"
}
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Ada NGN wallet',
type: 'operational',
currency: 'NGN',
customerId: '01K2Z3AB4C5D6E7F8G9HJKMNPQ',
metadata: {cohort: 'sandbox-tutorial'}
})
};
fetch('https://sandbox-api.sakawave.example/v1/accounts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://sandbox-api.sakawave.example/v1/accounts"
payload = {
"name": "Ada NGN wallet",
"type": "operational",
"currency": "NGN",
"customerId": "01K2Z3AB4C5D6E7F8G9HJKMNPQ",
"metadata": { "cohort": "sandbox-tutorial" }
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"id": "01K2Z3AB4D5E6F7G8H9JKMNPQR",
"name": "Ada NGN wallet",
"type": "operational",
"category": "fiat",
"status": "active",
"currency": "NGN",
"balance": "2500000",
"balanceFormatted": "25000.00",
"tag": "AA0271849366",
"canDebit": true,
"canCredit": true,
"isWalletAccount": true,
"transferReady": true,
"customerId": "01K2Z3AB4C5D6E7F8G9HJKMNPQ",
"metadata": {
"cohort": "sandbox-tutorial"
},
"closedAt": null,
"closureReason": null,
"createdAt": "2026-08-14T09:02:00.000Z",
"updatedAt": "2026-08-14T09:02: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": 403,
"message": "International wallets for your customers are not available yet. Your own approval cannot be used for a customer wallet.",
"code": "international_customer_wallet_not_supported",
"currency": "USD"
}{
"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."
}What you may open
Three checks run in order, and each has its own failure:- The currency must be open for issuance. A currency Sakawave has switched off answers
409 currency_capability_unavailable. - A crypto account needs tier 2. Below it, the denial answers
403withreason,currentTier, andrequiredTier— and nocode. - A customer wallet must be NGN. Any other currency answers
403 international_customer_wallet_not_supported, even when your own business is approved for it. Your approval does not extend to your customers.
account.created webhook carrying this response body.Error codes
| Status | code | Cause |
|---|---|---|
400 | validation_failed | A field failed validation — including a type outside operational and savings. |
403 | international_customer_wallet_not_supported | A customer wallet was requested in a currency other than NGN. |
403 | — (reason: feature_not_available) | A crypto account was requested below tier 2. |
404 | customer_not_found | The customerId is not one of your customers. |
404 | client_unavailable | The key authenticated, but its business no longer exists. |
409 | currency_capability_unavailable | This currency is not currently open for account issuance. |
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. |
Authorizations
A secret API key, sent as Authorization: Bearer sk_test_… in sandbox or Bearer sk_live_… in live.
Body
Your own label. Trimmed on the way in.
1 - 120Both behave identically — transactable, no interest, no lock. savings is a bucket you choose, not different treatment.
operational, savings NGN, USD, GBP, EUR, GHS, ZAR, UGX, CAD, MXN, AED, BTC, ETH, USDT, USDC Supply it and the account becomes that customer’s wallet. Omit it and the account belongs to your business.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
Free-form JSON stored and returned verbatim. Never send anything here that you would not want in a support conversation.
Response
Successful response.
A 26-character ULID.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
Your own label for the account. Not shown to anyone paying into it.
What the account is for. operational and savings behave identically; the rest are platform-managed.
operational, savings, settlement, collection, fee, revenue, suspense Follows from the currency; it is never chosen.
fiat, crypto Only active can move money. frozen is a compliance or provider hold. closed is terminal.
active, inactive, frozen, closed NGN, USD, GBP, EUR, GHS, ZAR, UGX, CAD, MXN, AED, BTC, ETH, USDT, USDC An integer number of the asset’s smallest unit. Use this value for arithmetic.
^-?\d+$"2500000"
The same figure rendered for display. Never use it in a calculation.
The account’s public transfer handle: two letters and ten digits, the last a check digit. Platform-minted and unique across Sakawave, so another business can pay this account by tag.
^[A-Z]{2}\d{10}$Whether money may leave. Held separately from status: an account can be stopped in one direction only.
Whether money may arrive.
True when the account belongs to one of your customers rather than to your business.
True once the account is bound to a payment provider. A freshly opened account is false until its first detail is issued, and cannot yet send or receive externally.
The owning customer, when this is a wallet account.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
Whatever you stored at creation, returned unchanged. Sakawave never reads it.
"2026-08-14T09:00:00.000Z"
client_request, no_longer_needed, duplicate, compliance "2026-08-14T09:00:00.000Z"
"2026-08-14T09:00:00.000Z"