Skip to main content
POST
Open an account

What you may open

Three checks run in order, and each has its own failure:
  1. The currency must be open for issuance. A currency Sakawave has switched off answers 409 currency_capability_unavailable.
  2. A crypto account needs tier 2. Below it, the denial answers 403 with reason, currentTier, and requiredTier — and no code.
  3. 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.
Your own fiat account is not checked against your tier, your verification, or your international access. It opens in any supported currency, at any tier. Verification and international access are checked when you issue its details; the tier is checked when money moves, and a non-NGN account sends money or takes deposits through its details only at tier 3. Because customers are always tier 1, a customer can never hold a crypto wallet.
Opening an account emits an account.created webhook carrying this response body.

Error codes

Authorizations

Authorization
string
header
required

A secret API key, sent as Authorization: Bearer sk_test_… in sandbox or Bearer sk_live_… in live.

Body

application/json
name
string
required

Your own label. Trimmed on the way in.

Required string length: 1 - 120
type
enum<string>
required

Both behave identically — transactable, no interest, no lock. savings is a bucket you choose, not different treatment.

Available options:
operational,
savings
currency
enum<string>
required
Available options:
NGN,
USD,
GBP,
EUR,
GHS,
ZAR,
UGX,
CAD,
MXN,
AED,
BTC,
ETH,
USDT,
USDC
customerId
string

Supply it and the account becomes that customer’s wallet. Omit it and the account belongs to your business.

Required string length: 26
Pattern: ^[0-7][0-9A-HJKMNP-TV-Z]{25}$
Example:

"01K2Z3AB4C5D6E7F8G9HJKMNPQ"

metadata
object

Free-form JSON stored and returned verbatim. Never send anything here that you would not want in a support conversation.

Response

Successful response.

id
string
required

A 26-character ULID.

Required string length: 26
Pattern: ^[0-7][0-9A-HJKMNP-TV-Z]{25}$
Example:

"01K2Z3AB4C5D6E7F8G9HJKMNPQ"

name
string
required

Your own label for the account. Not shown to anyone paying into it.

type
enum<string>
required

What the account is for. operational and savings behave identically; the rest are platform-managed.

Available options:
operational,
savings,
settlement,
collection,
fee,
revenue,
suspense
category
enum<string>
required

Follows from the currency; it is never chosen.

Available options:
fiat,
crypto
status
enum<string>
required

Only active can move money. frozen is a compliance or provider hold. closed is terminal.

Available options:
active,
inactive,
frozen,
closed
currency
enum<string>
required
Available options:
NGN,
USD,
GBP,
EUR,
GHS,
ZAR,
UGX,
CAD,
MXN,
AED,
BTC,
ETH,
USDT,
USDC
balance
string
required

An integer number of the asset’s smallest unit. Use this value for arithmetic.

Pattern: ^-?\d+$
Example:

"2500000"

balanceFormatted
string
required

The same figure rendered for display. Never use it in a calculation.

tag
string
required

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.

Pattern: ^[A-Z]{2}\d{10}$
canDebit
boolean
required

Whether money may leave. Held separately from status: an account can be stopped in one direction only.

canCredit
boolean
required

Whether money may arrive.

isWalletAccount
boolean
required

True when the account belongs to one of your customers rather than to your business.

transferReady
boolean
required

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.

customerId
string | null
required

The owning customer, when this is a wallet account.

Required string length: 26
Pattern: ^[0-7][0-9A-HJKMNP-TV-Z]{25}$
Example:

"01K2Z3AB4C5D6E7F8G9HJKMNPQ"

metadata
object | null
required

Whatever you stored at creation, returned unchanged. Sakawave never reads it.

closedAt
string<date-time> | null
required
Example:

"2026-08-14T09:00:00.000Z"

closureReason
enum<string> | null
required
Available options:
client_request,
no_longer_needed,
duplicate,
compliance
createdAt
string<date-time>
required
Example:

"2026-08-14T09:00:00.000Z"

updatedAt
string<date-time>
required
Example:

"2026-08-14T09:00:00.000Z"