> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sakawave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Cards setup and configuration

> Feature gates, the shared idempotency namespace, provider dependence, and the webhooks that report spending.

## Prerequisites

Three things must be true before an issuance call can succeed, and they fail differently.

<Steps>
  <Step title="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`.
  </Step>

  <Step title="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`.
  </Step>

  <Step title="The funding account is one of yours">
    Active, debitable, and **not** a customer wallet. A wallet is `403 card_client_account_required`.
  </Step>
</Steps>

<Warning>
  Step 2 cannot be satisfied today. Customers are always tier 1. See [the introduction](/services/cards/introduction#the-gates).
</Warning>

## 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.

```ts theme={null}
// Wrong: the second call returns the issuance operation, not a funding one.
await post('/v1/cards', { externalReference: 'card_ada', /* … */ });
await post(`/v1/cards/${cardId}/fund`, { externalReference: 'card_ada', /* … */ });
```

Namespace your references by operation:

```ts theme={null}
`card_issue_${cardRequestId}`
`card_fund_${cardId}_${fundingId}`
`card_withdraw_${cardId}_${withdrawalId}`
`card_terminate_${cardId}`
```

### Retrying is the opposite of transfers

| | Transfer | Card issuance |
| - | - | - |
| Reference reused after success | Returns the existing one | Returns the existing one |
| Reference reused after **failure** | Returns the **failure** | **Re-drives** the issuance |

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.

| Failure | Meaning |
| - | - |
| `503 card_provider_unavailable` | The provider could not be reached. Retry with the same reference. |
| `503 card_credentials_unavailable` | The provider did not return a PAN or CVV. |
| `503 invalid_card_fx_quote` | The provider returned an incomplete or already-expired quote. |

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

| Event | When |
| - | - |
| `card.created` | A card was issued |
| `card.updated` | Status or controls changed — including `creating` → `active` |
| `card.transaction.created` | A payment was authorised |
| `card.transaction.completed` | A payment settled |
| `card.transaction.failed` | A payment was declined or failed |
| `card.transaction.reversed` | An authorisation was reversed |

`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](/services/cards/card-programs-and-pan-handling)
* [ ] `503` treated as retryable with the same reference


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.