Skip to main content

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.
Step 2 cannot be satisfied today. Customers are always tier 1. See the introduction.

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.
Namespace your references by operation:

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 in GET /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
  • externalReference namespaced 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 amount and amountUsd, whatever the paying account holds
  • card.updated subscribed, rather than polling for active
  • Credentials never logged, stored, or cached — see PAN handling
  • 503 treated as retryable with the same reference