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

> ## Agent Instructions
> When building application code, use `@naturalpay/sdk` for TypeScript or `naturalpay` for Python.
> When searching Natural documentation, use the docs MCP at `https://docs.natural.com/mcp`.
> Use the Natural CLI for terminal and CI automation.
> Start integration development and testing in the sandbox. Do not use production unless the user explicitly requests it.
> For sandbox requests that require `customerPartyId`, list customers first. Reuse only the intended fixture; otherwise create a customer.
> REST and SDK amounts use integer minor units. Payments MCP amounts use decimal strings with a required currency code.

# Issued cards in Sandbox

> Issue a test agent card, then simulate the merchant authorizing, capturing, reversing, expiring, and refunding purchases on it

In Sandbox, your agent issues single-use cards the same way as in production. You then simulate the merchant side: authorize a purchase on the card, then capture, reverse, expire, or refund it.

Natural approves or declines each authorization based on the card and the wallet. To test cards your customers use to pay you, see [card fixtures](/api-reference/sandbox/card-fixtures).

Issued-card simulations are available through the REST API and the [Sandbox MCP server](/api-reference/sandbox/surfaces#mcp). The SDKs and CLI don't support them yet.

## Before you start

You need:

* **Issued cards enabled for your organization.** To request access, contact [hi@natural.com](mailto:hi@natural.com). Without it, card requests return `403` with `issued_cards_not_enabled`.
* **A funded wallet.** [Link a test bank account](/api-reference/simulations/link-test-bank-account) and [deposit](/guides/transfers/deposit-and-withdraw) into the wallet. Sandbox deposits complete immediately.
* **An agent with `cards.read` on the wallet.**
  * An agent connected over OAuth needs the `cards:agent` scope. If the agent connected before issued cards were enabled, reconnect it.
  * To act for a connected customer, the customer's grant to your agent must include `cards.read`. Pass the customer's party ID as `customerPartyId`.

Send every request below to `https://api.sandbox.natural.com` with a sandbox key.

## Issue a test card

Issue a card with the agent's key. `maximumAmount` is in cents and caps the card's one purchase.

```bash theme={null}
curl https://api.sandbox.natural.com/agent-cards \
  -H "Authorization: Bearer $NATURAL_AGENT_KEY" \
  -H "Idempotency-Key: card-1" \
  -H "X-Instance-ID: run-1" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "attributes": {
        "merchant": { "name": "Sandbox Coffee" },
        "maximumAmount": 3000,
        "currency": "USD"
      }
    }
  }'
```

The response is the card (`pcd_...`). Use it once its status is `active`. A card expires after 60 minutes unless you set `expiresInMinutes` (5 to 1440).

## Read the test card

`GET /agent-cards/{cardId}/details` returns the card's details. Every Sandbox card returns the same test card, unencrypted:

| Field | Sandbox value |
| - | - |
| `number` | `4242424242424242` |
| `cvv` | `123` |
| `expirationMonth`, `expirationYear` | The card's own expiration |

Simulations use the card ID, not the number.

## Simulate a purchase

Authorize a purchase on the card:

```bash theme={null}
curl https://api.sandbox.natural.com/simulations/agent-cards/pcd_.../authorize \
  -H "Authorization: Bearer $NATURAL_AGENT_KEY" \
  -H "Idempotency-Key: authorize-1" \
  -H "X-Instance-ID: run-1" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "attributes": {
        "amount": 3000,
        "currency": "USD",
        "merchant": { "name": "Sandbox Coffee" }
      }
    }
  }'
```

The response is a card transaction (`cit_...`) with status `authorized` or `declined`. Amounts are in cents.

Then act on it with `POST /simulations/card-transactions/{cardTransactionId}/{action}`:

| Action | Request attributes | Result |
| - | - | - |
| `capture` | `amount`, `currency` | `captured`, or `partially_captured` if you capture less; the rest of the hold is released |
| `reverse` | none | `reversed`; the hold is released |
| `expire` | none | `reversed`; the authorization expires now and the hold is released |
| `refund` | `amount`, `currency` | A new card transaction with status `refunded`; the purchase's `refunded` amount goes up |

For `reverse` and `expire`, send `{"data": {"attributes": {}}}`.

Rules:

* A purchase can be captured, reversed, or expired once. A capture can be up to the authorized amount.
* A refund needs a capture first. You can refund more than once, up to the captured amount.
* An action the purchase's state doesn't allow returns `409` with `card_purchase_simulation_not_allowed`.

Read the result with `GET /card-transactions/{cardTransactionId}`.

### Who can simulate

* The agent the card was issued to, while it has `cards.read` on the wallet.
* The wallet owner's dashboard user or party API key with `payments.create` on the wallet.

To simulate for a connected customer, use the connected agent and pass `customerPartyId` in the request attributes.

## Declines

A declined authorization returns `200` with status `declined` and a `decline` object. No money moves.

| Decline | How to produce it | `decline.code` |
| - | - | - |
| Insufficient funds | Issue a card with a maximum above the wallet's balance, then authorize more than the balance but within the maximum | `insufficient_funds` |
| Over the maximum | Authorize more than the card's `maximumAmount` | `single_use_amount_over_maximum` |
| Card already used | Authorize again on a card whose first purchase was approved | `single_use_card_used` |
| Expired card | Issue a card with `expiresInMinutes: 5`, wait until `expiresAt` passes, then authorize on it | `single_use_card_expired` |

## Retries

* Every simulation needs an `Idempotency-Key`. Retrying with the same key and body returns the first result without running the simulation again. See [Idempotency](/api-reference/idempotency).
* Agents also send `X-Instance-ID`.
* A `503` with `retryable: true` means the simulation was accepted but its result wasn't ready yet. Retry with the same key.


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