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

# Card use cases

> Four flows — issuing a controlled card, topping up, responding to suspected fraud, and reconciling spending.

<Note>
  Issuance cannot currently complete for a business client. These flows are written against the service as built; see [the introduction](/services/cards/introduction#the-gates).
</Note>

## 1. Issue a card with limits

Spending controls are set once, at issuance, and cannot be changed afterwards. Choose them deliberately.

```ts theme={null}
const { card, operation } = await post('/v1/cards', {
  customerId: customer.id,
  type: 'virtual',
  brand: 'mastercard',
  currency: 'USD',
  externalReference: `card_issue_${request.id}`,
  label: `${customer.name} — subscriptions`,
  debitAccountId: usdAccountId,
  amount: '10000',                    // $100.00, converted if the account is not USD
  spendingControls: {
    transactionAmountLimit: '2500',   // $25.00 a go
    dailyAmountLimit: '5000',         // $50.00 a day
    dailyTransactionCount: 5,
  },
});

// creating → active happens at the provider. Do not block on it.
await store(request.id, { cardId: card.id, operationId: operation.id, status: card.status });
```

Then let the webhook tell you it is ready:

```ts theme={null}
onEvent('card.updated', async (data) => {
  if (data.status === 'active') await notifyHolderCardReady(data.id);
});
```

Three limits, three different jobs: `transactionAmountLimit` caps a single purchase, `dailyAmountLimit` caps a day's spending, and `dailyTransactionCount` caps how many attempts a day. A subscription card wants a tight per-transaction cap; a travel card wants a daily one.

## 2. Top up, in any currency

Funding is always denominated in USD. What changes is whether you need a quote — issuance quotes for itself, topping up does not.

```ts theme={null}
async function topUp(cardId: string, account: Account, usdMinorUnits: string, reference: string) {
  let fxQuoteId: string | undefined;

  if (account.currency !== 'USD') {
    const quote = await post('/v1/card-fx-quotes', {
      operation: 'funding',
      accountId: account.id,
      cardId,
      amountUsd: usdMinorUnits,
    });
    fxQuoteId = quote.id;     // expiresAt is provider-set; use it immediately
  }

  return post(`/v1/cards/${cardId}/fund`, {
    externalReference: reference,
    accountId: account.id,
    amountUsd: usdMinorUnits,
    fxQuoteId,
  });
}
```

Sending a quote for a USD account is refused (`card_fx_quote_not_required`), and omitting one for a non-USD account is refused (`card_fx_quote_required`). There is no tolerant middle. Issuance is the exception: it quotes for itself and takes no `fxQuoteId` at all.

## 3. Suspected fraud

Freeze first, ask questions second. Freezing is instant, reversible, and safe to call on an already-frozen card.

```ts theme={null}
await post(`/v1/cards/${cardId}/freeze`, { externalReference: `card_freeze_${incidentId}` });
```

The card stops authorising merchants immediately. It still accepts funding and withdrawal, which matters for the next step:

```ts theme={null}
// Get the money out while the card is frozen.
const card = await get(`/v1/cards/${cardId}`);
if (card.balance !== '0') {
  await post(`/v1/cards/${cardId}/withdraw`, {
    externalReference: `card_sweep_${incidentId}`,
    accountId: recoveryAccountId,
    amountUsd: card.balance,
  });
}
```

<Warning>
  A withdrawal for the full `balance` can be refused: only the **available** balance can leave, and an authorisation the card is still holding is not available. Withdraw what settles, and terminate to sweep the rest — termination waits for holds to clear.
</Warning>

If it turns out to be nothing, unfreeze. If it does not:

```ts theme={null}
await post(`/v1/cards/${cardId}/terminate`, {
  externalReference: `card_terminate_${cardId}`,
  destinationAccountId: recoveryAccountId,
});
```

Termination freezes the card, moves it to `terminating`, and sweeps whatever is left — including money released later by a clearing hold. It cannot be undone.

## 4. Reconcile spending

Two views, and you want both.

```bash theme={null}
# What the card did, refreshed from the provider.
curl --silent --url "$SAKAWAVE_BASE_URL/v1/cards/$CARD_ID/transactions" \
  --header "Authorization: Bearer $SAKAWAVE_API_KEY" \
| jq '[.[] | {status, direction, amountFormatted, merchantName, merchantCountry, createdAt}]'
```

```bash theme={null}
# What your ledger recorded.
curl --silent --url "$SAKAWAVE_BASE_URL/v1/transactions?pageSize=100" \
  --header "Authorization: Bearer $SAKAWAVE_API_KEY" \
| jq '[.items[] | select(.relatedOperation.type == "card_payment")]'
```

| Question | Where |
| - | - |
| What did this card spend? | Card transactions |
| Did it hit our books? | Ledger transactions, `relatedOperation.type == "card_payment"` |
| Did our funding land? | The card's `operations`, `type: "funding"` |
| What is on the card now? | The card's `balance` |

Card transactions are **not paginated** and take no filters — every payment for the card comes back. For a card with heavy use, cache what you have already processed and diff on `id`.

A `reversed` payment stays in the list rather than disappearing, and a refund arrives as a separate `credit`. Reconciliation should sum signed amounts rather than assuming everything is a debit.

## 5. Disposable cards

A disposable card is spent once and then over.

```ts theme={null}
const { card } = await post('/v1/cards', {
  customerId: customer.id,
  type: 'disposable',
  brand: 'mastercard',
  currency: 'USD',
  externalReference: `card_single_${orderId}`,
  label: `Order ${orderId}`,
  debitAccountId: usdAccountId,
  amount: exactOrderAmount,
  // No spendingControls — a disposable card rejects them.
});
```

Fund it with exactly the amount required: after its first payment the card moves to `used`, which is terminal, and anything left over then needs terminating to recover. The single-use nature *is* the spending control, which is why the field is refused.

## What you cannot do here

* **Change a label or spending controls after issuance.** Both are fixed. Issue a new card.
* **Issue a physical card**, or one in any currency but USD.
* **Reach the card's balance account.** It is hidden; funding and withdrawal are the only doors.
* **Reopen a terminated card**, or recover its number.
* **Filter or page card transactions.**


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