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

# Sandbox & Testing

> Test your card-issuing integration without affecting production data

## Overview

Contro provides a full sandbox environment for testing your integration.

| | Sandbox | Production |
| - | - | - |
| **Base URL** | `https://stg-api.contro.dev/v1` | `https://api.contro.me/v1` |
| **API key prefix** | `sk_test_*` | `sk_live_*` |
| **Dashboard** | [https://partner.contro.dev](https://partner.contro.dev) | [https://partner.contro.me](https://partner.contro.me) |

The API key prefix determines the environment:

* **`sk_test_*`** - Sandbox mode. All operations use a separate ledger and simulated card provider.
* **`sk_live_*`** - Production mode. Operations use real card providers and your live balance.

Sandbox and production data are fully isolated. Cardholders, cards, transactions, webhook events, and usage records created in sandbox mode are tagged with `sandbox: true` and never appear in live queries.

## Test Data

When issuing cards in sandbox mode, the simulated card provider returns:

| Field | Value |
| - | - |
| Card Number (PAN) | `4000 0000 0000 0000` |
| CVV | `123` |
| Expiry | `12/2030` |
| Brand | Visa |
| Type | Virtual |

## Async Lifecycle Parity

Live cardholder approval and card provisioning are asynchronous. The sandbox replays the same event sequence after a short delay (\~5 seconds), so an integration tested in sandbox handles live timing unchanged:

* **KYC**: `POST /partner/cardholders/{id}/kyc` returns `{ "status": "pending" }`. No external verification provider is contacted. After the delay the cardholder flips to `approved` and the same webhooks as live fire: `cardholder.approved` and `cardholder.kyc.updated` (with `sandbox: true` in the payload).
* **Card issuance**: `POST /partner/cards` returns `"status": "pending"`. After the delay the card transitions to `active` and a `card.issued` webhook fires — see [asynchronous issuance](/partner/cards#asynchronous-issuance).

## Sandbox-Only Endpoints

These endpoints are only available when using a `sk_test_` API key. They return `403 Forbidden` with a live key.

### Simulate Transaction

Simulates a settled card purchase — or a refund of a prior simulated purchase — against a sandbox card. The simulation runs through the same settlement path as live: spend plus fees are deducted from your sandbox balance exactly once, [balance thresholds](/partner/balance#balance-thresholds) are evaluated, and a `transaction.created` webhook fires.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://stg-api.contro.dev/v1/partner/sandbox/simulate-transaction \
    -H "x-contro-api-key: $CONTRO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "cardId": "card_abc123",
      "amount": 2500,
      "currency": "USD",
      "merchantName": "Test Coffee Shop",
      "type": "purchase"
    }'
  ```

  ```typescript SDK (Node) theme={null}
  await contro.sandbox.simulateTransaction({
    cardId: "card_abc123",
    amount: 2500,
    currency: "USD",
    merchantName: "Test Coffee Shop",
    type: "purchase",
  });
  ```

  ```python SDK (Python) theme={null}
  contro.sandbox.simulate_transaction(
      card_id="card_abc123",
      amount=2500,
      currency="USD",
      merchant_name="Test Coffee Shop",
      type="purchase",
  )
  ```
</CodeGroup>

| Field | Type | Required | Description |
| - | - | - | - |
| `cardId` | string | Yes | Sandbox card to transact on |
| `amount` | number | Yes | Amount in minor units (cents) |
| `currency` | string | No | ISO 4217 currency code (default `USD`) |
| `merchantName` | string | No | Simulated merchant name |
| `type` | string | No | `purchase` (default) or `refund` |
| `originalTransactionId` | string | Required for refunds | `transactionId` of the simulated purchase to refund. Refund amount cannot exceed the original purchase. |

To simulate a refund, pass `"type": "refund"` with the `originalTransactionId` returned by the purchase. The refund credits your sandbox balance back; replaying the same refund is idempotent and never double-credits.

The `transaction.created` webhook payload includes `transactionId`, `cardId`, `amount`, `currency`, `merchantName`, `status`, `type`, and — for refunds — `originalTransactionId`.

### Reset Balance

Resets your sandbox ledger balance to a specified amount or the default configured during partner creation.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://stg-api.contro.dev/v1/partner/sandbox/reset-balance \
    -H "x-contro-api-key: $CONTRO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "amount": 1000000 }'
  ```

  ```typescript SDK (Node) theme={null}
  await contro.sandbox.resetBalance({ amount: 1000000 });
  ```

  ```python SDK (Python) theme={null}
  contro.sandbox.reset_balance(amount=1000000)
  ```
</CodeGroup>

### Simulate KYC Approval

Triggers sandbox KYC approval for a cardholder. The approval runs through the same short async lifecycle described above — the cardholder flips to `approved` after a few seconds and the `cardholder.approved` / `cardholder.kyc.updated` webhooks fire.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://stg-api.contro.dev/v1/partner/sandbox/simulate-kyc-approval \
    -H "x-contro-api-key: $CONTRO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "cardholderId": "ch_abc456",
      "cardProgramId": "cp_123"
    }'
  ```

  ```typescript SDK (Node) theme={null}
  await contro.sandbox.simulateKycApproval({
    cardholderId: "ch_abc456",
    cardProgramId: "cp_123",
  });
  ```

  ```python SDK (Python) theme={null}
  contro.sandbox.simulate_kyc_approval(
      cardholder_id="ch_abc456",
      card_program_id="cp_123",
  )
  ```
</CodeGroup>

## Go-Live Checklist

Before switching to production, complete the readiness checklist:

1. **KYB Approved** - Your organization has been verified
2. **Sandbox Card Issued** - At least one card issued with `sk_test_` key
3. **Webhook Verified** - At least one webhook successfully delivered
4. **Live Balance Funded** - Your live account has a positive balance

Check your status via `GET /partner/go-live-checklist` or the dashboard Go-Live page.
