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

# Cards API Recipes

> Every Cards scenario as the calls to make, in order, and the signal to wait for

<Warning>
  **Cards is available in sandbox only while we finish development.** Dakota enables Cards per
  account. The Cards endpoints are in the [API reference](/api-reference/introduction), marked
  **Sandbox only**. Endpoints, fields, and flows can still change before release.
</Warning>

Each recipe lists the calls in order, what to wait for, and the refusals worth handling. Every call links to its API reference page, which has the full request and response.

**Common to every call:**

* Host: `https://api.platform.sandbox.dakota.xyz`. Cards is sandbox only for now.
* Authenticate with your API key in the `x-api-key` header.
* Send an `X-Idempotency-Key` (a UUID) on every `POST`, `PATCH`, and `PUT`, except `POST /cards/{card_id}/reveal_session`.
* Webhooks arrive on your existing stream, in the standard envelope. If your webhook target filters by `event_types`, add the card event types first. See [Cards webhooks](/documentation/cards/webhooks).

## Setup

### 1. Activate a customer for Cards

Once per customer. Dakota must have activated your account first. That step has no API; ask your Dakota contact.

1. [`GET /customers/{customer_id}/capabilities`](/api-reference/customers/list-a-customers-capabilities-and-whats-needed-to-unlock-them). Find the entry with `capability: "cards"`.
2. If its `status` is `action_required`, send the customer the `url` of the requirement with `key: "cards_tos"`. The customer accepts the terms on a page Dakota hosts.
3. **Wait for** `customer.capability_status.updated` with `capability: "cards"` and `status: "available"`.

Until then, cardholder, enablement, and card creation return `403 cards-tos-not-accepted`, or `403 cards-capability-unavailable` when more than the terms is outstanding. Details: [Activating customers](/documentation/cards/activation).

### 2. Create a cardholder

Once per person. Pick the request shape that matches the customer:

| Customer | Send | Name comes from |
| - | - | - |
| Business, person already on its onboarding application | `person_id`, `phone` | The person |
| Business, someone new | `first_name`, `last_name`, `email`, `phone` | The request |
| Individual | `phone` | The customer's onboarding |

1. For a business customer, [`GET /customers/{customer_id}/persons`](/api-reference/cards/list-a-customers-persons). Pick a person whose `cardholder_id` is `null`. Collect what `missing_for_cards` lists: always `phone`, and `email` when none is on file.
2. [`POST /customers/{customer_id}/cardholders`](/api-reference/cards/create-a-cardholder). Returns the cardholder with `status: "pending"`.
3. **Wait for** `cardholder.updated` with `status: "active"`. This usually takes seconds.

Handle `409 cardholder-person-exists` (the email belongs to a person already on the application; retry with the `person_id` it names) and `409 cardholder-already-exists`. Details: [Cardholders](/documentation/cards/cardholders).

### 3. Enable a wallet for cards

Once per wallet, before its first card. The wallet must be an EVM wallet.

1. Have the customer sign the intent `{"type": "enable_card_settlement", "wallet_id": "…", "idempotency_key": "…"}`, the same way they sign any [endorsed request](/documentation/signing-guide).
2. [`POST /wallets/{wallet_id}/card_enablement`](/api-reference/cards/enable-card-settlement-on-a-wallet) with the intent and signatures. Returns `state: "attaching"`.
3. **Wait for** `wallet.card_enablement.completed`. The state is then `active`. You can also read [`GET /wallets/{wallet_id}/card_enablement`](/api-reference/cards/get-a-wallets-card-enablement).

Handle `422 wallet-family-not-supported` (not an EVM wallet) and `409 card-enablement-precondition` (the wallet's policies need fixing, or Dakota's setup is incomplete). Details: [Wallet enablement](/documentation/cards/wallet-enablement).

### 4. Issue a card

1. [`POST /customers/{customer_id}/cards`](/api-reference/cards/create-a-card) with `cardholder_id`, `wallet_id`, and optionally `spend_limit`, `nickname`, and `external_id`. Returns the card with `status: "pending"`.
2. **Wait for** `card.updated` with `status: "active"`. The card can now be revealed and spent.

Handle `403 cardholder-not-active`, `403 wallet-not-card-enabled` (do recipe 3 first), `400 limit-reached` (the cardholder has too many active cards), and `409 cardholder-reenroll-required` (delete the cardholder and create it again). Details: [Issuing and managing cards](/documentation/cards/issuing).

## Using cards

### 5. Show the card number to the cardholder

1. In the browser, [Cards.js](/documentation/cards/cards-js) calls your backend when the cardholder asks to see the card.
2. Your backend authenticates the user and confirms they own the card. Then it calls [`POST /cards/{card_id}/reveal_session`](/api-reference/cards/create-a-card-reveal-session) with `session_type: "card_details"` and the page's `origin`. No idempotency key.
3. Your backend returns the session to Cards.js, which shows the details in a secure frame.

The card must be `active`. A session works once and expires within minutes. Details: [Secure card data](/documentation/cards/secure-card-data).

### 6. Freeze, unfreeze, or close a card

[`PATCH /cards/{card_id}`](/api-reference/cards/update-a-card) with `status` set to `frozen`, `active`, or `closed`. **Wait for** `card.updated`.

Closing is permanent. Unfreezing can be refused: `403 card-freeze-held` (a freeze you cannot lift, named in `freeze_sources`), `403 card-not-activated`, `403 cardholder-suspended`, or `403 card-closed`.

### 7. Rename a card

[`PATCH /cards/{card_id}`](/api-reference/cards/update-a-card) with `nickname`, up to 64 characters. A nickname can be replaced, but not removed.

### 8. Change a spend limit

[`PUT /cards/{card_id}/spend_limit`](/api-reference/cards/replace-a-cards-spend-limit) with `interval` and `amount` in minor units. The new limit replaces the old one and applies from the next purchase.

### 9. Show the available balance

[`GET /wallets/{wallet_id}/balances`](/api-reference/wallets/get-wallet-balances-across-all-networks). The `card` object carries `total`, `held`, `outstanding`, and `available`. Show `available` to the cardholder. A purchase can need slightly more than its own amount, so one for the entire available balance can be declined. Details: [Card funding](/documentation/cards/card-funding#holds-and-available-balance).

### 10. Track purchases

1. **Listen for** `card_transaction.created` (an approved or declined authorization) and `card_transaction.updated` (every later change to the same purchase). Switch on `status`.
2. To backfill or reconcile, [`GET /card_transactions`](/api-reference/cards/list-card-transactions), filtered by `customer_id`, `card_id`, or `cardholder_id`, and [`GET /card_transactions/{card_transaction_id}`](/api-reference/cards/get-a-card-transaction).

Details: [Card funding](/documentation/cards/card-funding) and [Cards webhooks](/documentation/cards/webhooks#card-transactions).

### 11. Find cards and cardholders

* [`GET /customers/{customer_id}/cards`](/api-reference/cards/list-cards), filtered by `cardholder_id`, `status`, or `external_id`. [`GET /cards/{card_id}`](/api-reference/cards/get-a-card) also embeds the cardholder and the wallet.
* [`GET /customers/{customer_id}/cardholders`](/api-reference/cards/list-cardholders), filtered by `external_id`, and [`GET /cardholders/{cardholder_id}`](/api-reference/cards/get-a-cardholder).

`external_id` filters are exact and case-sensitive, and can match more than one record.

### 12. Report a dispute

[`POST /card_dispute_reports`](/api-reference/cards/report-a-card-dispute) with the `card_id`, your `client_reference`, a `reason`, and when the cardholder told you. Dakota files the dispute with the network. Details: [Disputes](/documentation/cards/disputes).

## Offboarding

### 13. Remove a cardholder

1. Close each of the cardholder's cards with [`PATCH /cards/{card_id}`](/api-reference/cards/update-a-card) and `status: "closed"`. The delete is refused while any of their cards is not closed.
2. [`DELETE /cardholders/{cardholder_id}`](/api-reference/cards/delete-a-cardholder).
3. **Wait for** `cardholder.updated` with `status: "closed"`. The cardholder is no longer returned by the API.

To update a cardholder's contact details instead, use [`PATCH /cardholders/{cardholder_id}`](/api-reference/cards/update-a-cardholder).

## Testing

### 14. Run a purchase in sandbox

1. [`POST /sandbox/wallets/{wallet_id}/faucet`](/api-reference/sandbox/fund-a-sandbox-wallet-from-the-testnet-faucet) to fund the wallet. Poll [`GET /sandbox/simulations/{simulation_id}`](/api-reference/sandbox/get-simulation-status) until the funds land.
2. [`POST /sandbox/cards/simulate/transaction`](/api-reference/sandbox/simulate-a-card-authorization) to authorize a purchase. `card_transaction.created` fires.
3. [`POST /sandbox/cards/simulate/transaction/{card_transaction_id}`](/api-reference/sandbox/advance-a-simulated-card-transaction) with an `action` such as `clear`, `void`, or `return`. `card_transaction.updated` fires.

Details: [Testing Cards](/documentation/cards/testing-sandbox).
