Skip to main content
Cards is available in sandbox only while we finish development. Dakota enables Cards per account. The Cards endpoints are in the API reference, marked Sandbox only. Endpoints, fields, and flows can still change before release.
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.

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

2. Create a cardholder

Once per person. Pick the request shape that matches the customer:
  1. For a business customer, GET /customers/{customer_id}/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. 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.

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.
  2. POST /wallets/{wallet_id}/card_enablement 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.
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.

4. Issue a card

  1. POST /customers/{customer_id}/cards 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.

Using cards

5. Show the card number to the cardholder

  1. In the browser, 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 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.

6. Freeze, unfreeze, or close a card

PATCH /cards/{card_id} 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} 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 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. 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.

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, filtered by customer_id, card_id, or cardholder_id, and GET /card_transactions/{card_transaction_id}.
Details: Card funding and Cards webhooks.

11. Find cards and cardholders

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

12. Report a dispute

POST /card_dispute_reports with the card_id, your client_reference, a reason, and when the cardholder told you. Dakota files the dispute with the network. Details: Disputes.

Offboarding

13. Remove a cardholder

  1. Close each of the cardholder’s cards with PATCH /cards/{card_id} and status: "closed". The delete is refused while any of their cards is not closed.
  2. DELETE /cardholders/{cardholder_id}.
  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}.

Testing

14. Run a purchase in sandbox

  1. POST /sandbox/wallets/{wallet_id}/faucet to fund the wallet. Poll GET /sandbox/simulations/{simulation_id} until the funds land.
  2. POST /sandbox/cards/simulate/transaction to authorize a purchase. card_transaction.created fires.
  3. POST /sandbox/cards/simulate/transaction/{card_transaction_id} with an action such as clear, void, or return. card_transaction.updated fires.
Details: Testing Cards.