> ## 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 programs and PAN handling

> Choosing a card type, setting controls that hold, and handling the one response in the API that contains a real card number.

This is the cards-specific integration guide: the design decisions you make once per program, and the rules around the most sensitive response Sakawave returns.

## Choosing a type

The choice is made at issuance and cannot be changed.

| | `virtual` | `disposable` |
| - | - | - |
| Payments | Many | One, then `used` |
| Spending controls | Supported | Rejected at creation |
| Refunds after use | Land on a terminal card | Land on a terminal card |
| Fits | Subscriptions, team spend, a customer's ongoing card | A single checkout, a one-off vendor payment |

A disposable card's single-use nature is its control, which is why sending `spendingControls` with one is a validation error rather than being ignored.

Two consequences of `used` being terminal: fund a disposable card with **exactly** the amount needed, and expect refunds to arrive on a card that can no longer spend — recover them with a termination sweep.

## Spending controls that hold

Three independent limits, all in USD minor units, all set once.

| Control | Caps | Good for |
| - | - | - |
| `transactionAmountLimit` | One payment | Stopping a single large charge |
| `dailyAmountLimit` | A day's total | Bounding total exposure |
| `dailyTransactionCount` | Attempts per day | Blunting a card-testing attack |

```jsonc theme={null}
// A subscription card: small, frequent, predictable.
{ "transactionAmountLimit": "3000", "dailyAmountLimit": "3000", "dailyTransactionCount": 2 }

// A travel card: larger single spends, bounded daily.
{ "transactionAmountLimit": "20000", "dailyAmountLimit": "50000", "dailyTransactionCount": 15 }
```

Set all three. A daily cap without a per-transaction cap allows one purchase to consume the whole day's allowance; a per-transaction cap without a count allows a hundred small charges.

<Warning>
  Controls cannot be edited after issuance. There is no endpoint to raise, lower, or remove them — a change means issuing a new card and terminating the old one. Choose them as if they were permanent, because they are.
</Warning>

## The credentials endpoint

`POST /v1/cards/{cardId}/details` returns the **full PAN and CVV**, fetched live from the provider. Sakawave does not store them, and this is the only response in the API that contains them.

```json theme={null}
{ "pan": "5399830000004242", "cvv": "123", "expiryMonth": 8, "expiryYear": 2028 }
```

### Why it is a POST

A `GET` would be reachable from a link, a prefetch, a browser history entry, a server access log with the path in it, and a referrer header. A `POST` is none of those. The endpoint also answers with `Cache-Control: no-store, private` and `Pragma: no-cache`.

Treat the method as part of the security design, not a REST quirk.

### Rules for handling the response

<Steps>
  <Step title="Never log it">
    Not in request logs, not in error reports, not in an exception breadcrumb. Redact the whole response body at the logging layer rather than trusting each call site.
  </Step>

  <Step title="Never store it">
    Not in a database, not in a session, not in `localStorage`. Fetch it when the holder asks to see the card, and let it go.
  </Step>

  <Step title="Never cache or proxy it">
    Not through a CDN, not through a caching reverse proxy, not in a service worker. If you proxy the call, forward the no-store headers.
  </Step>

  <Step title="Render it so it cannot persist">
    A component that clears on navigation and on reload. Do not put it in a URL, a query parameter, or a form value that a browser might restore.
  </Step>

  <Step title="Fetch on demand, one card at a time">
    Never prefetch credentials for a list of cards.
  </Step>
</Steps>

```ts theme={null}
// Fetch, hand to the view, drop the reference. Nothing persists.
async function revealCard(cardId: string) {
  const credentials = await post(`/v1/cards/${cardId}/details`, {});
  try {
    return renderEphemeral(credentials);   // clears on navigation
  } finally {
    // No caching, no return value that outlives the call.
  }
}
```

Everywhere else, use `maskedPan` from the card object. It is on every card response and safe to display, log, and store.

### When it is unavailable

| Status | `code` | Cause |
| - | - | - |
| `409` | `card_not_ready` | The card is still `creating` — the provider has not issued it |
| `503` | `card_credentials_unavailable` | The provider did not return a number or security code |

Neither is a reason to retry in a tight loop. Wait for `card.updated` to report `active`, then try again.

## Balances, holds, and what can leave

A card balance is not simply a number you can move.

| | Meaning |
| - | - |
| `balance` | What the card holds, in USD minor units |
| Available | What can actually be withdrawn |
| Hold | An authorisation the card has made but the merchant has not captured |

A withdrawal draws only on the available portion, so a withdrawal for the full `balance` can be refused while an authorisation is outstanding. Termination handles this properly: it waits, returns a `pending` operation, and sweeps the money once the hold clears.

If you need to empty a card and are not sure of its holds, terminate rather than withdrawing.

## Freeze versus terminate

| | Freeze | Terminate |
| - | - | - |
| Stops merchants | Yes | Yes |
| Stops funding | **No** | Yes |
| Reversible | Yes | **No** |
| Moves the balance | No | Sweeps it |
| Use for | Suspicion, a paused subscription, a lost device | Fraud confirmed, a customer leaving, a card replaced |

Freezing is cheap and reversible, so freeze on suspicion. Terminating is neither, so terminate only on a decision.

## A card program checklist

* [ ] Card type chosen per use, and disposable cards funded to exactly the amount needed
* [ ] All three spending controls set at issuance, since none can change later
* [ ] Logging configured to redact the credentials response by route, not by call site
* [ ] Credentials fetched on demand, rendered ephemerally, never stored or cached
* [ ] `maskedPan` used everywhere a card is displayed or recorded
* [ ] Termination, not withdrawal, used to empty a card whose holds are unknown
* [ ] Freeze wired to your fraud signals as the fast, reversible response


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