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

# Testing cards in staging

> Exercise every card, transaction, identity, and webhook state in test mode — no real money, no real people

Everything on this page runs in **test mode**: one base URL (`https://api.agentcard.sh`), with test vs live decided by the credential you authenticate with, never by the host. Test-mode activity is simulated end to end — simulated money, instant identity outcomes, fake merchants — but it flows through the same settlement core, produces the same API shapes, and fires the same webhooks (with `livemode: false`) that live traffic does. If your integration handles everything this page can throw at it, it is ready for production traffic.

<Note>
  The [dashboard](https://app.agentcard.sh) drives all of this without code: **Cards → Issue a test card** walks the transaction lifecycle, **Users** simulates identity outcomes, and **Earnings** (test view) shows revenue accruing on simulated volume. This page is the API equivalent.
</Note>

## Test cards

Issue cards in test mode exactly as in live — `POST /api/v1/cards` — after the instant sandbox identity and payment-method steps from the [quickstart](/companies/getting-started/quickstart). Test cards carry fake `4242…` numbers, hold simulated balances, and follow the live one-time-use contract: the first approved authorization consumes the card.

## Attaching a card (BYOC)

The [attach flow](/companies/guides/attach-card) verifies a **real** consumer card through Basis Theory and the card network, so in test mode it reads Basis Theory's BIN enrichment for whatever PAN you enter — the card must classify as eligible: a **US-issued consumer Visa**, or a consumer **Mastercard from any country**.

Use Basis Theory's US consumer Visa test card:

```
4242 4300 0000 0017
```

<Warning>
  The ubiquitous `4242 4242 4242 4242` does **not** attach — Basis Theory enriches it as a **commercial, non-US (Bermuda)** card, so it's correctly rejected (the program is consumer-only, and Visa must be US-issued). In sandbox, the ineligible verdict points you at the card above.
</Warning>

To exercise the ineligible branches (which fall back to the issued-card path — the agent routes to `create_card`): `4000 0566 5566 5556` (business → `commercial_card`) and `4900 0000 0000 0011` (unknown funding → `funding_unknown`). Full list: [Basis Theory test cards](https://developers.basistheory.com/docs/api/testing#card-numbers).

## Simulate transactions

Test merchants never charge a test card on their own — these helpers are the merchant. Each one drives the same settlement pipeline a real network event does, so the `Payment` rows, card state changes, and `transaction.*` webhooks are byte-for-byte what live produces.

### The one-shot charge

`POST /api/v1/cards/{id}/test_charge` authorizes and settles in one call — the happy path:

```bash theme={null}
curl -X POST https://api.agentcard.sh/api/v1/cards/$CARD_ID/test_charge \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "amountCents": 675, "merchant": "COFFEE SHOP #42 SAN FRANCISCO" }'
```

Fires `transaction.authorized` then `transaction.cleared`, and closes the one-time-use card.

### The full lifecycle, step by step

Real transactions are not one-shot: they hold as PENDING, settle later (sometimes for less), get reversed, decline, and refund. Each state has its own helper so you can test the handling code paths individually.

**Authorize** — a PENDING hold, the state your webhook consumer must treat as not-yet-final:

```bash theme={null}
curl -X POST https://api.agentcard.sh/api/v1/cards/$CARD_ID/test_authorization \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "amountCents": 675, "merchant": "MIDNIGHT RAMEN BAR" }'
```

Returns the payment `id` used by the steps below; fires `transaction.authorized`. An approved authorization consumes a one-time-use card (declines never do).

**Decline** — the branch most integrations forget to build:

```bash theme={null}
curl -X POST https://api.agentcard.sh/api/v1/cards/$CARD_ID/test_authorization \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "outcome": "declined", "declineReason": "insufficient_funds" }'
```

Fires `transaction.declined` with your `decline_reason`. An authorization for more than the card's balance auto-declines with `insufficient_funds`, the way live does — no need to force it.

**Capture** — settle a PENDING authorization, fully or partially:

```bash theme={null}
curl -X POST https://api.agentcard.sh/api/v1/cards/$CARD_ID/test_transactions/$PAYMENT_ID/capture \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "amountCents": 500 }'
```

Fires `transaction.cleared` at the captured amount. Omit `amountCents` for a full capture; a partial capture settles for less than the hold, exactly like a merchant finalizing a smaller total.

**Reverse** — void a PENDING authorization instead of settling it:

```bash theme={null}
curl -X POST https://api.agentcard.sh/api/v1/cards/$CARD_ID/test_transactions/$PAYMENT_ID/reverse \
  -H "Authorization: Bearer $TOKEN"
```

Fires `transaction.voided`. (A one-time-use card consumed by the authorization stays closed — live reversals do not resurrect cards either.)

**Refund** — a merchant refund of a settled transaction:

```bash theme={null}
curl -X POST https://api.agentcard.sh/api/v1/cards/$CARD_ID/test_transactions/$PAYMENT_ID/refund \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "amountCents": 200 }'
```

Mirrors the live rail exactly: the refund is **not** a mutation of the original — it arrives as a **separate `transaction.cleared` with a negative `amount_cents`** (correlate by merchant). Partial refunds are allowed up to the settled amount; the original payment stays `SETTLED`.

<Note>
  Every helper responds with the `events` it fired, and each delivery is inspectable in the dashboard under **Settings → Webhooks → your endpoint → Deliveries** — payload, response code, and retries included.
</Note>

## Simulate identity outcomes

Test-mode verifications never complete on their own (there is no reviewer behind them). Force a verdict with [`POST /api/v2/kyc/simulate`](/companies/api/reference/kyc-simulate) — `approved`, `rejected`, or `requires_input` — and the status plus the `identity.verification.updated` webhook land exactly like a real review. The full walkthrough, including the test-mode page end users see, is in [Identity verification → Testing](/companies/api/identity-verification#testing-in-test-mode).

## Watch revenue accrue

Test-mode settled volume accrues the same earnings signal live volume does. The dashboard's **Earnings** page (test view) has a one-click simulated transaction that shows your interchange share appearing in real time — the same math applied to your live volume.

## Webhooks in test mode

Endpoints belong to one mode: an endpoint registered in the test view receives only `livemode: false` events. Register one endpoint per mode to run both, and use the dashboard's **send test event** button to prove your handler before any traffic. Everything else — signatures, retries, the event catalog — is identical to live; see [Webhooks](/companies/webhooks).

## Going live

Nothing on this page exists in live mode: the helpers return `403 sandbox_only` on live credentials, and live verdicts come from real merchants and the identity provider. When the flows above all behave, follow [Getting to production](/companies/production).
