Prerequisites
Three things must be true before an issuance call can succeed, and they fail differently.1
Your business holds the card_issuance feature
A tier-2 requirement. Below it,
403 with reason: feature_not_available, currentTier, and requiredTier — and no code.2
The holder is an eligible customer
An individual customer at tier 2 or above, with a first name, last name, email, and phone number. Anything else is
404 card_holder_not_found.3
The funding account is one of yours
Active, debitable, and not a customer wallet. A wallet is
403 card_client_account_required.Idempotency
externalReference is the key, and it is shared across every card operation type — issuance, funding, withdrawal, and termination all draw on one namespace per business.
Retrying is the opposite of transfers
A card issuance that ended in
failed or funding_failed will genuinely retry when you resend the same reference — it even reverses a stranded debit first. Do not mint a new reference for a card retry; you will create a second card.
The same is not true of funding, withdrawal, or termination: those return the existing operation whatever its status.
Rate limits
Every card write is 120 per minute, counted once against your business and once against the key. The two reads — list and get — fall under the default 600.GET /v1/cards/{cardId}/transactions calls the provider on every request to refresh payments. It is a read, but not a cheap one; do not put it behind a UI that polls.
Provider dependence
Cards are the most provider-dependent service in the API. Issuance, freeze, unfreeze, termination, credentials, and the transaction refresh all make live provider calls.
Operations are asynchronous by design:
created → submitting → pending → completed or failed. A background reconciler chases anything left pending, so an operation that has not completed is not necessarily stuck.
Webhooks
card.updated is how you learn a card finished being issued. Subscribe rather than polling GET /v1/cards/{cardId} on a timer.
The card.transaction.* payloads are ledger transactions whose relatedOperation is a card_payment, carrying the card id, label, masked PAN, merchant name, and merchant country.
The hidden balance account
Each card owns a USD account that Sakawave creates and marks invisible. It never appears inGET /v1/accounts, cannot be transferred to or from, and exists so card money is ring-fenced from your operating balances.
Read a card’s balance from the card. Reconcile card spending through card transactions and card.transaction.* events, not by looking for an account.
Configuration checklist
- Business tier confirmed at 2 or above before writing issuance code
-
externalReferencenamespaced per operation type, since all four share one namespace - Card retries resend the same reference, unlike transfers
- Funding accounts are your own, never customer wallets
- USD minor units used for every
amountandamountUsd, whatever the paying account holds -
card.updatedsubscribed, rather than polling foractive - Credentials never logged, stored, or cached — see PAN handling
-
503treated as retryable with the same reference