# Card Sandbox Simulation

Due to the complexity of card issuing, Column supports end-to-end card program testing in Sandbox from creating the program, to simulating an authorization, a capture, and a refund against a card.

## Prerequisites

To simulate card transactions in Sandbox, start by creating a [non-root entity](/entities/managing-entities). Debit card programs require a [bank account](/api/bank-account/create-a-new-bank-account) for each non-root entity. Charge and credit card programs require a [loan object](/api/lending/create-a-new-loan) for each non-root entity. Cardholder-secured charge card programs require a secured loan for each non-root entity, backed by a collateral bank account that holds the cardholder's deposit. Before simulating card transactions, a bank account must have a non-zero balance. A loan object must have a non-zero maximum principal balance and available credit.

If you plan to test real-time decisioning, please review [webhook setup requirements](/card-issuing/card-real-time-decisioning#webhook-setup) as well prior to simulating card transactions. You can also simulate card transactions without real-time decisioning as described below.

## Card Program

In Production, Column will configure a card program on your behalf. However, in Sandbox you can [create a card program](/api/simulation/create-sandbox-card-program) with `POST /simulate/issuing/card-programs` specifying the [program type](/card-issuing/card-programs#card-program-types).

```bash
curl https://api.column.com/simulate/issuing/card-programs \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "type": "debit",
      "scheme": "visa",
      "description": "sandbox card program"
  }'
```

| Field           | Required | Description                                                                                                    |
| --------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
| `type`          | Yes      | `debit`, `credit`, or `charge`.                                                                                |
| `scheme`        | No       | `visa`. Defaults to `visa`.                                                                                    |
| `credit_policy` | No       | `unsecured`, `cardholder_secured`, or `platform_secured`. Defaults to `unsecured`. Ignored for debit programs. |
| `description`   | No       | Free-form description, max 255 characters.                                                                     |

The response is a [card program object](/api/card-program/card-program-object). Sandbox assigns and activates a BIN range for you, which allows cards
created under the program to be resolved from simulated network messages.

## Card Authorization Policies

Once a card program has been created, you can create [card authorization policies](/card-issuing/card-authorization-policies). Policies are evaluated before real-time decisioning, so they are the cheapest way to enforce baseline merchant and velocity rules.

[Create a policy](/api/card-authorization-policy/create-card-authorization-policy) and its first version with `POST /issuing/card-authorization-policies`.

```bash
curl https://api.column.com/issuing/card-authorization-policies \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "scope": "card_program",
      "effective_on": "<RFC 3339 timestamp at least 5 minutes from now>",
      "config": {
        "blocked_merchant_category_codes": ["7995"],
        "allowed_merchant_country_codes": ["US"],
        "card_spend_policies": [
          {
            "scope": "card_program",
            "interval": "1_day",
            "total_volume": 100000,
            "number_of_transactions": 20
          }
        ]
      }
  }'
```

| Field          | Required | Description                                                                                                 |
| -------------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| `scope`        | Yes      | `card_program`, `card_account`, or `card`.                                                                  |
| `effective_on` | Yes      | When the version becomes effective, as an `RFC 3339` timestamp. Offsets are converted to `UTC`.             |
| `config`       | Yes      | The category and spend rules. See [Card Authorization Policies](/card-issuing/card-authorization-policies). |

To change authorization rules later, [add a new version](/api/card-authorization-policy/create-card-authorization-policy-version) to the existing policy with `POST /issuing/card-authorization-policy-versions`,
passing the `card_authorization_policy_id` along with the same `scope`, a new `effective_on`, and the new `config`.

> **Warning**
>
> The active policy version is the one with the most recent `effective_on` that has already passed. You must set `effective_on` at least 5 minutes in the future, so wait for the version to become effective before you simulate transactions against it.

A policy is only evaluated once it is attached to a card program, card account, or card. Attach a `card_program`-scoped policy by [updating your card program](/api/card-program/update-card-program) with
`PATCH /issuing/card-programs/:card_program_id`:

```bash
curl https://api.column.com/issuing/card-programs/cpgm_3J6EP7ebC8vIwpP6lQfvy371JFQ \
  -XPATCH \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "card_authorization_policy_id": "caup_2x8gszy5folpA9s0TOCseE9ABDM"
  }'
```

`card_account`-scoped and `card`-scoped policies are attached by passing `card_authorization_policy_id` when you create
the card account or card, or later by [updating the card](/api/card/update-card) with `PATCH /issuing/cards/:card_id`.

## Card Accounts

You can now [create card accounts](/api/card-account/create-card-account) under your card program with applicable card authorization policies. For a debit program, the card account links to the
bank account that funds it. For a credit program, the card account links to the line of credit that funds it. See
[Card Account Types](/card-issuing/card-accounts#card-account-types) for how each program type links its card accounts.

```bash
curl https://api.column.com/issuing/card-accounts \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "card_program_id": "cpgm_3J6EP7ebC8vIwpP6lQfvy371JFQ",
      "bank_account_id": "bacc_2wQFHhraSXePmI1rC2uhnodtvh2"
  }'
```

| Field                          | Required           | Description                                                                                                                                                                          |
| ------------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `card_program_id`              | Yes                | The program to open the card account under.                                                                                                                                          |
| `bank_account_id`              | Debit only         | Bank account backing the card account. Must be omitted for credit and charge programs.                                                                                               |
| `loan_id`                      | Credit/Charge only | Loan backing the card account. For a `cardholder_secured` program, the loan must have `type=secured`; its collateral bank account is derived from the loan and cannot be overridden. |
| `card_authorization_policy_id` | No                 | Include only if policy is scoped to a card account.                                                                                                                                  |

## Cards

Next, [create a card](/api/card/create-card). [Virtual cards](/card-issuing/cards#virtual-cards) are the fastest path to testing in Sandbox as shipping details are not required.

```bash
curl https://api.column.com/issuing/cards \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "type": "virtual",
      "card_account_id": "cacc_3J6FHczL9wF2o3xsoVbOxK2Zu60",
      "authorized_user_entity_id":"enti_3BPEJh4QweINlzZxBH1oZ94qrAQ",
      "status":"active",
      "encrypted_pin":"SKIP"
  }'
```

| Field                                  | Required      | Description                                                                                                                   |
| -------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `type`                                 | Yes           | `virtual` or `physical`.                                                                                                      |
| `card_account_id`                      | Yes           | The card account to create the card under.                                                                                    |
| `authorized_user_entity_id`            | Yes           | The [person entity](/api/entity/create-a-person-entity) authorized to use the card.                                           |
| `shipping_details`                     | Physical only | [Shipping address and contact information](/api/card/shipping-details-sub-object) for fulfillment.                            |
| `card_authorization_policy_id`         | No            | Policy applied at the card scope.                                                                                             |
| `card_template_id`                     | No            | Card template to use. Must belong to the card account's card program.                                                         |
| `expiration_month` / `expiration_year` | No            | Override the generated expiration date.                                                                                       |
| `status`                               | No            | Initial [status](/card-issuing/cards#statuses) for the card. By default status is "paused" if not explicitly set to "active". |
| `encrypted_pin`                        | No            | Base64-encoded encrypted PIN. This is required for debit cards. Use "SKIP" to set this at a later date.                       |

The PAN and CVV2 are under [PCI scope](/card-issuing/cards#pci-compliance) and are not returned when you create a card. You do not need them to simulate transactions — the simulate endpoints address the card by its `card_id`. If you are building a cardholder-facing card display, use the two-step reveal flow. Call `POST /issuing/cards/:card_id/reveal-tokens` to [create a single-use, short-lived reveal token](/api/card/create-card-reveal-token). Then call `GET /issuing/cards/:card_id/reveal`, authenticating with `Authorization: Bearer <card_reveal_token_id>` instead of your API key, to [reveal card details](/api/card/reveal-card-details).

### PIN Encryption

Debit card programs require an `encrypted_pin` prior to use, replacement, or renewal. The PIN is the customer's chosen 4-digit code, encrypted with RSA-2048, OAEP padding, SHA-256 hash, then base64-encoded.

Sandbox public key:

```bash
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApBDtgxhJhZCmbvXWr7Zz
xs8u9JrSVWjtIDcIsD+5cfyv0OsWgjTxBIoBhUWeoTMizEGWWmLl9l0y/fWXFar3
wTriE+sun/cdFydCaBzB0nlyVqH9lIoKhsmpZUOsBS4Wrsnfb6GBzWFNlGu9ZkQE
+sXAEpf5G6gWKIoU6RACbslt0h79xW/EIeagOyl6MCW8Jtb8DsYwItGpVwoPPfU2
1XzmHBgeGTUOtRAWT1Ky//t5P9jcd5THqZjLBUqIq3K9Rz7OL/uY2oNFy57hdSWv
dXd0gnSdFKxOH6BrgTdUXkOU/ytdweOIcRgSGDVYKEHQuZa93XJirH3AY+Xk21CU
kwIDAQAB
-----END PUBLIC KEY-----
```

This single key serves every sandbox platform on Column. It works **only** against `test_`-prefixed API credentials — production uses a different key issued during onboarding.

To set this on an already created card, [update the card's PIN](/api/card/update-card-pin) with `PATCH /issuing/cards/:card_id/pin`:

```bash
curl https://api.column.com/issuing/cards/card_3J6FHczL9wF2o3xsoVbOxK2Zu60/pin \
    -XPATCH \
    -H 'Content-Type: application/json' \
    -u :<YOUR API KEY> \
    --data '{
        "encrypted_pin": "<base64 ciphertext>"
    }'
```

## Activate Card

A card must be `active` before it will authorize. If the card was created in a `paused` state, [activate it](/api/card/activate-card):

```bash
curl https://api.column.com/issuing/cards/card_3J6Gg2WgNLoZAz67PrHhJy9aHlT/activate \
  -XPATCH \
  -u :<YOUR API KEY>
```

## Simulate Card Transaction

With a funded, active card you can drive the full network message lifecycle. Every simulate endpoint returns the
resulting [card transaction object](/api/card-transaction/card-transaction-object), so you can inspect `amounts` and `events` after each step.

| Step                      | Endpoint                                                                                  |
| ------------------------- | ----------------------------------------------------------------------------------------- |
| Authorization             | `POST /simulate/issuing/cards/:card_id/card-transactions/authorization`                   |
| Incremental authorization | `POST /simulate/issuing/card-transactions/:card_transaction_id/incremental-authorization` |
| Capture                   | `POST /simulate/issuing/card-transactions/:card_transaction_id/capture`                   |
| Reversal                  | `POST /simulate/issuing/card-transactions/:card_transaction_id/reversal`                  |
| Refund                    | `POST /simulate/issuing/card-transactions/:card_transaction_id/refund`                    |
| Single-message financial  | `POST /simulate/issuing/cards/:card_id/card-transactions/financial`                       |
| 3DS authentication        | `POST /simulate/issuing/cards/:card_id/threeds/challenge`                                 |

All amounts are expressed in the smallest unit of the merchant's currency and must be greater than `0` and no more
than `999999999999`.

### Authorization

[Authorization](/api/simulation/simulate-card-authorization) places a hold on the card account. It is the entry point for exercising both authorization policies and
real-time decisioning.

```bash
curl https://api.column.com/simulate/issuing/cards/card_3J6Gg2WgNLoZAz67PrHhJy9aHlT/card-transactions/authorization \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "cardholder_amount": 2500,
      "merchant_currency": "USD",
      "realtime_decision":"approve"
  }'
```

| Field                                | Required       | Description                                                                                                                                                  |
| ------------------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `cardholder_amount`                  | Yes            | Amount in the merchant's currency, in the smallest currency unit.                                                                                            |
| `merchant_currency`                  | Yes            | Merchant's ISO 4217 currency code.                                                                                                                           |
| `cardholder_fx_rate`                 | Non-`USD` only | Rate converting the merchant currency to `USD`, the cardholder billing currency.                                                                             |
| `merchant_category_code`             | No             | ISO 18245 MCC, e.g. `5812` (restaurants) or `6011` (ATM). Defaults to `5999`.                                                                                |
| `merchant_name`                      | No             | Card acceptor name, max 25 characters. Defaults to `ACQUIRER NAME`.                                                                                          |
| `merchant_city`                      | No             | Card acceptor city, max 13 characters. Defaults to `CITY NAME`.                                                                                              |
| `merchant_country_code`              | No             | Card acceptor country, ISO 3166-1 alpha-2. Defaults to `US`.                                                                                                 |
| `realtime_decision`                  | No             | Inject an issuer decision: `approve`, `decline`, or `partially_approve`.                                                                                     |
| `realtime_decision_reason`           | No             | Decline reason when `realtime_decision` is `decline`. Defaults to `generic_decline`.                                                                         |
| `realtime_partially_approved_amount` | Conditional    | Required when `realtime_decision` is `partially_approve`. Must be greater than `0` and less than `cardholder_amount`.                                        |
| `network_standin`                    | No             | Simulate the network deciding on the issuer's behalf: `approved` or `declined`.                                                                              |
| `network_decision_reason`            | No             | Decline reason when `network_standin` is `declined`.                                                                                                         |
| `three_ds_attempt_id`                | No             | Links an `authenticated` 3DS attempt on the same card, producing authentication details with liability shift. See [3DS Authentication](#3ds-authentication). |

Use the merchant fields to exercise authorization policy and real-time decisioning rules that depend on the merchant
category or country. For example, with the authorization policy created above, an authorization with `merchant_category_code` set to `7995` is
declined before it reaches your decisioning endpoint.

**Testing your real-time decisioning endpoint.** When you omit both `realtime_decision` and `network_standin`, the simulator sends a real decision webhook to the endpoint your platform has subscribed to `decision.issuing.card_transaction` and waits for your response. If no destination is configured, the authorization declines. Set `realtime_decision` instead when you want to exercise a specific outcome without standing up an endpoint. See [Real-time Decisioning](/card-issuing/card-real-time-decisioning) for the full decisioning flow.

> **Note**
>
> `realtime_decision` and `network_standin` are mutually exclusive — sending both is rejected. `network_standin` produces a transaction with `decision_source` set to `card_network`, which is how you can simulate an issuer timeout.

The response contains the new `card_transaction_id` (prefixed `ctxn_`) used by the remaining steps, and emits `issuing.card_transaction.created`.

### Incremental Authorization

[Incremental authorization](/api/simulation/simulate-incremental-authorization) adds to an existing hold, commonly seen with hotels, gas stations, and rideshare companies.

```bash
curl https://api.column.com/simulate/issuing/card-transactions/ctxn_3J6f3h8oQ6J0SWTTNQOdEiy43pg/incremental-authorization \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "cardholder_amount": 500
  }'
```

| Field               | Required | Description                                                                        |
| ------------------- | -------- | ---------------------------------------------------------------------------------- |
| `cardholder_amount` | Yes      | Additional amount to add to the existing hold.                                     |
| `fx_rate`           | No       | Rate for multi-currency transactions. Defaults to the original transaction's rate. |

The original transaction must have started as an authorization and must not have captured funds. Currency codes carry
over from the original authorization. Each increment sends a real-time decision request to your endpoint subscribed to
`decision.issuing.card_transaction`, and declines if none is configured.

### Capture

[Capture](/api/simulation/simulate-capture) clears the transaction and moves funds. By default a capture is final and releases the entire remaining hold. A capture will not succeed if there is no authorization hold to capture.

```bash
curl https://api.column.com/simulate/issuing/card-transactions/ctxn_3J6f3h8oQ6J0SWTTNQOdEiy43pg/capture \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "cardholder_amount": 2500
  }'
```

| Field                | Required | Description                                                                                             |
| -------------------- | -------- | ------------------------------------------------------------------------------------------------------- |
| `cardholder_amount`  | Yes      | Amount to capture.                                                                                      |
| `is_partial_capture` | No       | When `true`, keeps the remaining hold active for future captures (split shipment). Defaults to `false`. |

On a final capture, over-capture is permitted so you can simulate tips, hotel incidentals, and fuel charges. On a
partial capture the amount must not exceed the remaining authorized amount.

Each capture appends a `clear` event to the card transaction and emits `issuing.card_transaction.updated`. See
[Card Transactions](/card-issuing/card-transactions) for how the six amount types roll up across events — negative
amounts are funds held or withdrawn from the card account, positive amounts are funds released or deposited to the card account.

### Single-Message Financial

[Single-message financial](/api/simulation/simulate-full-financial-transaction) simulates a transaction that authorizes and clears in a single network message, so there is no authorization hold before capture. Examples include ATM transactions and debit purchases.

```bash
curl https://api.column.com/simulate/issuing/cards/card_3J6FHczL9wF2o3xsoVbOxK2Zu60/card-transactions/financial \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "cardholder_amount": 4000,
      "merchant_currency": "USD"
  }'
```

| Field                    | Required       | Description                                                                      |
| ------------------------ | -------------- | -------------------------------------------------------------------------------- |
| `cardholder_amount`      | Yes            | Amount in the merchant's currency, in the smallest currency unit.                |
| `merchant_currency`      | Yes            | Merchant's ISO 4217 currency code.                                               |
| `cardholder_fx_rate`     | Non-`USD` only | Rate converting the merchant currency to `USD`, the cardholder billing currency. |
| `merchant_category_code` | No             | ISO 18245 MCC. Defaults to `5999`.                                               |
| `merchant_name`          | No             | Card acceptor name, max 25 characters. Defaults to `ACQUIRER NAME`.              |
| `merchant_city`          | No             | Card acceptor city, max 13 characters. Defaults to `CITY NAME`.                  |
| `merchant_country_code`  | No             | Card acceptor country, ISO 3166-1 alpha-2. Defaults to `US`.                     |

Unlike an authorization, a simulated financial transaction never calls your real-time decisioning endpoint, so there is
no `realtime_decision` or `network_standin` field to set. Card status, authorization policy, and balance checks still
apply and can decline a financial transaction.

### Reversal

[Reversal](/api/card/simulate-reversal) simulates an acquirer reversing the message that opened a transaction, such as a voided sale or a terminal that never
received its authorization response. The transaction must have started as an authorization or a single-message financial message.

```bash
curl https://api.column.com/simulate/issuing/card-transactions/ctxn_3J6f3h8oQ6J0SWTTNQOdEiy43pg/reversal \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "cardholder_amount": 1000
  }'
```

| Field               | Required | Description                                                                                                                  |
| ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `cardholder_amount` | No       | Amount to release from the authorization hold. Must be less than the outstanding authorized amount. Omit to reverse in full. |

Omit `cardholder_amount` to reverse in full: an authorization releases whatever hold remains, including incremental
authorizations, and a financial transaction reverses its settled amount. Partial reversals only apply to authorizations.
Reversals act on the outstanding hold, so a transaction that has already been captured has nothing left to reverse —
refund it instead.

### Refund

[Refund](/api/simulation/simulate-refund) returns funds to the card account against a previously captured transaction.

```bash
curl https://api.column.com/simulate/issuing/card-transactions/ctxn_3J6f3h8oQ6J0SWTTNQOdEiy43pg/refund \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "cardholder_amount": 2500
  }'
```

Each refund creates a new card transaction linked to the purchase by `original_card_transaction_id`. The total refunded
must not exceed the settled amount on the original transaction.

### 3DS Authentication

[3DS authentication](/api/simulation/simulate-3ds-authentication) simulates a [3DS authentication attempt](/card-issuing/card-real-time-decisioning#3ds-authentication-decisioning) for an online purchase. Link a successful attempt to an authorization with
`three_ds_attempt_id` to test a liability shift.

```bash
curl https://api.column.com/simulate/issuing/cards/card_3J6Gg2WgNLoZAz67PrHhJy9aHlT/threeds/challenge \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "amount": 2500,
      "currency": "USD",
      "realtime_decision": "challenge"
  }'
```

| Field                                                                                 | Required | Description                                                                                                                           |
| ------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `amount`                                                                              | Yes      | Transaction amount in the smallest currency unit.                                                                                     |
| `currency`                                                                            | Yes      | ISO 4217 currency code.                                                                                                               |
| `realtime_decision`                                                                   | No       | Inject an authentication decision: `frictionless_approve`, `challenge`, or `decline_authentication`.                                  |
| `decline_reason`                                                                      | No       | `suspected_fraud` or `transaction_not_permitted` when `realtime_decision` is `decline_authentication`. Defaults to `suspected_fraud`. |
| `message_category`                                                                    | No       | `payment` or `non_payment` (for example, adding a card to a merchant's file).                                                         |
| `merchant_name` / `merchant_url` / `merchant_category_code` / `merchant_country_code` | No       | Merchant details placed on the attempt and on the decision request.                                                                   |

When you omit `realtime_decision`, the simulator sends a decision request to your endpoint subscribed to
`decision.issuing.card_authentication`. If none is subscribed, it creates an OTP challenge.

- `frictionless_approve` authenticates the attempt and emits `issuing.3ds.authentication_completed`.
- `challenge` starts an OTP challenge and emits `issuing.3ds.otp_generated`.
- `decline_authentication` rejects the attempt and emits `issuing.3ds.authentication_completed`.

[Complete the challenge](/api/simulation/complete-simulated-3ds-challenge) with `POST /simulate/issuing/threeds/:three_ds_attempt_id/complete`, setting `outcome` to
`successful` or `failed`. A `successful` outcome leaves the attempt `authenticated`, ready to pass as
`three_ds_attempt_id` on a simulated authorization.

```bash
curl https://api.column.com/simulate/issuing/threeds/<three_ds_attempt_id>/complete \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "outcome": "successful"
  }'
```

## Simulate Card Tokens

You can simulate the digital wallet provisioning flow and network token lifecycle events for cards on a card
program. Tokens are only created through the network provisioning flow, so in Sandbox you start by simulating a
wallet's eligibility check. See [Card Tokens](/card-issuing/card-tokens) for token statuses, events, and provisioning
decisioning.

| Step ::max                | Endpoint                                                                   |
| ------------------------- | -------------------------------------------------------------------------- |
| Eligibility check         | `POST /simulate/issuing/cards/tokens/check-eligibility`                    |
| Approve provisioning      | `POST /simulate/issuing/cards/tokens/:card_token_id/approve-provisioning`  |
| Step-up: retrieve methods | `POST /simulate/issuing/cards/tokens/:card_token_id/step-up/methods`       |
| Step-up: send passcode    | `POST /simulate/issuing/cards/tokens/:card_token_id/step-up/send-passcode` |
| Step-up: complete         | `POST /simulate/issuing/cards/tokens/:card_token_id/step-up/complete`      |
| Device binding            | `POST /simulate/issuing/cards/tokens/:card_token_id/device-binding`        |
| Network suspend           | `POST /simulate/issuing/cards/tokens/:card_token_id/suspend`               |
| Network resume            | `POST /simulate/issuing/cards/tokens/:card_token_id/resume`                |
| Network deactivate        | `POST /simulate/issuing/cards/tokens/:card_token_id/deactivate`            |

### Eligibility Check

[Eligibility check](/api/simulation/simulate-token-eligibility-check) simulates a digital wallet checking whether a card can be tokenized. An eligible card returns a new card token in
`pending_activation` status; an ineligible card returns an error with the reason.

```bash
curl https://api.column.com/simulate/issuing/cards/tokens/check-eligibility \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "card_id": "card_3J6Gg2WgNLoZAz67PrHhJy9aHlT"
  }'
```

### Approve Provisioning

[Approve provisioning](/api/simulation/simulate-approve-provisioning) simulates the wallet's provisioning request for the `pending_activation` token returned by the eligibility check.

```bash
curl https://api.column.com/simulate/issuing/cards/tokens/<card_token_id>/approve-provisioning \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "wallet_type": "apple_pay",
      "device_type": "mobile_phone",
      "device_name": "Cardholder iPhone",
      "action_code": "approve"
  }'
```

| Field                  | Required | Description                                                 |
| ---------------------- | -------- | ----------------------------------------------------------- |
| `action_code`          | No       | Force the decision: `approve`, `decline`, or `step_up`.     |
| `wallet_type`          | No       | `apple_pay`, `google_pay`, or `samsung_pay`.                |
| `device_type`          | No       | `mobile_phone`, `tablet`, or `watch`.                       |
| `device_name`          | No       | Human-readable name of the device requesting provisioning.  |
| `token_requestor_name` | No       | Name of the entity that initiated the provisioning request. |
| `token_type`           | No       | `wallet` or `card_on_file`. Defaults to `wallet`.           |

**Testing your provisioning decisioning endpoint.** When you omit `action_code`, the request goes through Column's real
provisioning decisioning, including your endpoint subscribed to `decision.issuing.card_token`. An `approve` decision
activates the token and emits `issuing.card_token.activated`.

### Step-up Verification

A `step_up` decision leaves the token ready for cardholder verification. If the cardholder has contact info on file,
the simulator also runs the retrieve-methods and send-passcode steps for you, so you only need to complete the step-up.
Otherwise, or to choose the delivery method yourself, run the steps individually:

1. [Retrieve methods](/api/simulation/simulate-step-up-retrieve-methods) returns the SMS and email methods available from the cardholder's contact info.
2. [Send passcode](/api/simulation/simulate-step-up-send-passcode) emits `issuing.card_token.send_passcode` with a simulated one-time passcode and moves
   the token to `pending_authentication`. Pass one of the returned method identifiers as `otp_method_identifier`, or omit
   it to select one automatically.
3. [Complete](/api/simulation/simulate-step-up-complete) finishes verification.

```bash
curl https://api.column.com/simulate/issuing/cards/tokens/<card_token_id>/step-up/complete \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "outcome": "success"
  }'
```

An `outcome` of `success` activates the token and emits `issuing.card_token.activated`; `failure` moves it to
`activation_denied` and emits `issuing.card_token.activation_denied`. The token must be in `pending_authentication`.

### Device Binding

[Device binding](/api/simulation/simulate-device-binding) simulates binding an existing token to a new device and returns the updated card token.

```bash
curl https://api.column.com/simulate/issuing/cards/tokens/<card_token_id>/device-binding \
  -XPOST \
  -H 'Content-Type: application/json' \
  -u :<YOUR API KEY> \
  --data '{
      "device_type": "watch",
      "device_name": "Cardholder Watch",
      "decision_override": "approve"
  }'
```

| Field               | Required | Description                                                                                                                                   |
| ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `decision_override` | No       | Force the outcome: `approve`, `decline`, or `step_up`. Omit to call your endpoint subscribed to `decision.issuing.card_token.device_binding`. |
| `device_id`         | No       | Identifier of the device being bound.                                                                                                         |
| `device_type`       | No       | `mobile_phone`, `tablet`, or `watch`.                                                                                                         |
| `device_name`       | No       | Human-readable name of the device being bound.                                                                                                |
| `wallet_account_id` | No       | Wallet account identifier associated with the binding.                                                                                        |

### Network Lifecycle Events

Simulate the card network changing a token's status, exactly as a production network notification would, with
[suspend](/api/simulation/simulate-token-suspend), [resume](/api/simulation/simulate-token-resume), and [deactivate](/api/simulation/simulate-token-deactivate).
None of these endpoints require a request body.

| Endpoint     | Result                                                                                                                                                                      |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `suspend`    | Token moves to `paused` and emits `issuing.card_token.paused`.                                                                                                              |
| `resume`     | A paused token moves back to `active` and emits `issuing.card_token.activated`.                                                                                             |
| `deactivate` | Token moves to `deactivated` and emits `issuing.card_token.deactivated`. Deactivating a token that is already `deactivated` returns it unchanged without emitting an event. |

To pause, activate, or deactivate a token from your own platform instead, use the
[card token endpoints](/card-issuing/card-tokens#managing-tokens).
