# Test runs API

> Synthetic test subscriptions ("CMS preview" of your subscription lifecycle): Checkbox subscribes a test customer in your billing platform's TEST mode, receives your emails at its own inbox, fast-forwards a Stripe test clock through trial end and renewal, cancels online, and reports every step with e

vidence, check results and fixes. Nothing is charged.

| Operation | Endpoint |
|---|---|
| [List billing test-mode keys of a site](#listTestCredentials) | `GET /v1/sites/{site}/test_credentials` |
| [Store a billing test-mode key](#createTestCredential) | `POST /v1/sites/{site}/test_credentials` |
| [Delete a billing test-mode key](#deleteTestCredential) | `DELETE /v1/sites/{site}/test_credentials/{test_credential}` |
| [List registered checkout configs](#listStripeCheckoutConfigs) | `GET /v1/sites/{site}/stripe_checkout_configs` |
| [Register the Checkout Session params your code uses](#createStripeCheckoutConfig) | `POST /v1/sites/{site}/stripe_checkout_configs` |
| [Retrieve a registered checkout config](#retrieveStripeCheckoutConfig) | `GET /v1/stripe_checkout_configs/{stripe_checkout_config}` |
| [Delete a registered checkout config](#deleteStripeCheckoutConfig) | `DELETE /v1/stripe_checkout_configs/{stripe_checkout_config}` |
| [List test runs of a site](#listTestRuns) | `GET /v1/sites/{site}/test_runs` |
| [Start a lifecycle test run](#createTestRun) | `POST /v1/sites/{site}/test_runs` |
| [Retrieve a test run](#retrieveTestRun) | `GET /v1/test_runs/{test_run}` |
| [Cancel a test run](#cancelTestRun) | `POST /v1/test_runs/{test_run}/cancel` |

## The StripeCheckoutConfig object

Checkout Session parameters your code uses, registered so test runs mirror exactly your checkout into Stripe test mode.

| Field | Type | Description |
|---|---|---|
| `id` | string | Unique identifier of the stripe checkout config (scc_…). |
| `object` | enum | String representing the object's type. Always "stripe_checkout_config". |
| `livemode` | boolean | true for live-mode objects, false for test mode (ck_test_ keys act on an isolated sandbox). |
| `site` | string | The site the config belongs to. |
| `name` | string \| null | Your name for the config. |
| `params` | object | The mirrored Checkout Session parameters (the subset of what your code passes to stripe.checkout.sessions.create that shapes what customers see). |
| `params_hash` | string | sha256 of the canonical params; changes whenever the registered parameters change. |
| `ignored_params` | array of strings | Parameters that were dropped because they are not mirrored (customer, URLs, metadata…). |
| `metadata` | object | Up to 20 key/value string pairs you can attach to the object. |
| `created` | integer | When the config was registered. Unix timestamp in seconds. |

## List billing test-mode keys of a site {#listTestCredentials}

`GET /v1/sites/{site}/test_credentials`

The site's stored test-mode keys (hints only).

**Scope** `test_runs:read` · **Operation** `listTestCredentials`

SDK: `checkbox.testCredentials.list()` (Node) · `checkbox.test_credentials.list()` (Python). MCP / agents: operation `listTestCredentials`.

#### Parameters

| Parameter | In | Type | Description |
|---|---|---|---|
| `site` **required** | path | string | Unique identifier of the site (site_…). |

#### Returns

Returns a list (`{ object: "list", data, has_more, url }`) of `TestCredential` objects.

Errors use the [standard envelope](https://checkbox.site/docs/api/errors) with a stable `code` and, where useful, `next_action`.

```curl
curl https://app.checkbox.site/v1/sites/site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/test_credentials \
  -u "$CHECKBOX_SECRET_KEY:"
```
```node
import Checkbox from '@checkbox-site/node';

const checkbox = new Checkbox(process.env.CHECKBOX_SECRET_KEY);

const testCredentials = await checkbox.testCredentials.list('site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(testCredentials.data);
```
```python
import os
from checkbox import Checkbox

checkbox = Checkbox(os.environ["CHECKBOX_SECRET_KEY"])

test_credentials = checkbox.test_credentials.list("site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(test_credentials["data"])
```

```json title="Response"
{
  "object": "list",
  "data": [
    {
      "id": "tcred_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
      "object": "test_credential",
      "livemode": false,
      "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
      "type": "stripe_test",
      "key_hint": "rk_test_…a1b2",
      "verified": true,
      "last_used": 1793318400,
      "last_error": null,
      "created": 1793318400
    }
  ],
  "has_more": false,
  "url": "/v1/sites"
}
```

## Store a billing test-mode key {#createTestCredential}

`POST /v1/sites/{site}/test_credentials`

Stores the site's Stripe TEST-mode key (rk_test_… preferred) used by test runs, after checking it with Stripe. Live keys are refused (test_key_not_test_mode). Least privilege: Write on Customers, Checkout Sessions, Subscriptions, Test clocks and Customer portal; Read on Prices, Products and Billing portal configuration. Replaces an existing key. Next: createTestRun.

**Scope** `test_runs:write` · **Idempotent** (send `Idempotency-Key`) · **Operation** `createTestCredential`

SDK: `checkbox.testCredentials.create()` (Node) · `checkbox.test_credentials.create()` (Python). MCP / agents: operation `createTestCredential`.

#### Parameters

| Parameter | In | Type | Description |
|---|---|---|---|
| `site` **required** | path | string | Unique identifier of the site (site_…). |

#### Body

| Field | Type | Description |
|---|---|---|
| `type` **required** | enum | Kind of key. Only "stripe_test". |
| `secret_key` **required** | string | The Stripe test-mode restricted (rk_test_…) or secret (sk_test_…) key. Stored encrypted; never returned. |

#### Returns

Returns a `TestCredential` object.

Errors use the [standard envelope](https://checkbox.site/docs/api/errors) with a stable `code` and, where useful, `next_action`.

```curl
curl https://app.checkbox.site/v1/sites/site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/test_credentials \
  -u "$CHECKBOX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
  "type": "stripe_test",
  "secret_key": "rk_test_51Hx…"
}'
```
```node
import Checkbox from '@checkbox-site/node';

const checkbox = new Checkbox(process.env.CHECKBOX_SECRET_KEY);

const testCredential = await checkbox.testCredentials.create('site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b', { type: 'stripe_test', secret_key: 'rk_test_51Hx…' });
console.log(testCredential);
```
```python
import os
from checkbox import Checkbox

checkbox = Checkbox(os.environ["CHECKBOX_SECRET_KEY"])

test_credential = checkbox.test_credentials.create("site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b", type="stripe_test", secret_key="rk_test_51Hx…")
print(test_credential)
```

```json title="Response"
{
  "id": "tcred_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "object": "test_credential",
  "livemode": false,
  "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "type": "stripe_test",
  "key_hint": "rk_test_…a1b2",
  "verified": true,
  "last_used": 1793318400,
  "last_error": null,
  "created": 1793318400
}
```

## Delete a billing test-mode key {#deleteTestCredential}

`DELETE /v1/sites/{site}/test_credentials/{test_credential}`

Deletes the stored key. Running test runs fail at their next Stripe call.

**Scope** `test_runs:write` · **Operation** `deleteTestCredential`

SDK: `checkbox.testCredentials.del()` (Node) · `checkbox.test_credentials.delete()` (Python). MCP / agents: operation `deleteTestCredential`.

#### Parameters

| Parameter | In | Type | Description |
|---|---|---|---|
| `site` **required** | path | string | Unique identifier of the site (site_…). |
| `test_credential` **required** | path | string | Unique identifier of the test credential (tcred_…). |

#### Returns

Returns a `DeletedTestCredential` object.

Errors use the [standard envelope](https://checkbox.site/docs/api/errors) with a stable `code` and, where useful, `next_action`.

```curl
curl https://app.checkbox.site/v1/sites/site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/test_credentials/tcred_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b \
  -u "$CHECKBOX_SECRET_KEY:" \
  -X DELETE
```
```node
import Checkbox from '@checkbox-site/node';

const checkbox = new Checkbox(process.env.CHECKBOX_SECRET_KEY);

const testCredential = await checkbox.testCredentials.del('site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b', 'tcred_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(testCredential);
```
```python
import os
from checkbox import Checkbox

checkbox = Checkbox(os.environ["CHECKBOX_SECRET_KEY"])

test_credential = checkbox.test_credentials.delete("site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b", "tcred_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(test_credential)
```

```json title="Response"
{
  "id": "tcred_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "object": "test_credential",
  "deleted": true
}
```

## List registered checkout configs {#listStripeCheckoutConfigs}

`GET /v1/sites/{site}/stripe_checkout_configs`

Registered Checkout Session configs of the site, newest first (test runs mirror the newest by default).

**Scope** `test_runs:read` · **Operation** `listStripeCheckoutConfigs`

SDK: `checkbox.stripeCheckoutConfigs.list()` (Node) · `checkbox.stripe_checkout_configs.list()` (Python). MCP / agents: operation `listStripeCheckoutConfigs`.

#### Parameters

| Parameter | In | Type | Description |
|---|---|---|---|
| `site` **required** | path | string | Unique identifier of the site (site_…). |
| `limit` | query | integer | Number of objects to return, 1-100 (default 10). |
| `starting_after` | query | string | Cursor: an object id; returns the objects after it (older). |
| `ending_before` | query | string | Cursor: an object id; returns the objects before it (newer). |

#### Returns

Returns a list (`{ object: "list", data, has_more, url }`) of [StripeCheckoutConfig](https://checkbox.site/docs/api/test-runs#the-stripecheckoutconfig-object) objects.

Errors use the [standard envelope](https://checkbox.site/docs/api/errors) with a stable `code` and, where useful, `next_action`.

```curl
curl -G https://app.checkbox.site/v1/sites/site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/stripe_checkout_configs \
  -u "$CHECKBOX_SECRET_KEY:" \
  -d limit=3
```
```node
import Checkbox from '@checkbox-site/node';

const checkbox = new Checkbox(process.env.CHECKBOX_SECRET_KEY);

const stripeCheckoutConfigs = await checkbox.stripeCheckoutConfigs.list('site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b', { limit: 3 });
console.log(stripeCheckoutConfigs.data);
```
```python
import os
from checkbox import Checkbox

checkbox = Checkbox(os.environ["CHECKBOX_SECRET_KEY"])

stripe_checkout_configs = checkbox.stripe_checkout_configs.list("site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b", limit=3)
print(stripe_checkout_configs.data)
```

```json title="Response"
{
  "object": "list",
  "data": [
    {
      "id": "scc_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
      "object": "stripe_checkout_config",
      "livemode": false,
      "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
      "name": "Pro monthly with 14-day trial",
      "params": {
        "mode": "subscription",
        "line_items": [
          {
            "price": "price_1Pro",
            "quantity": 1
          }
        ],
        "subscription_data": {
          "trial_period_days": 14
        },
        "consent_collection": {
          "terms_of_service": "required"
        }
      },
      "params_hash": "9f2c…",
      "ignored_params": [
        "success_url"
      ],
      "metadata": {
        "order_ref": "A-1001"
      },
      "created": 1793318400
    }
  ],
  "has_more": false,
  "url": "/v1/sites"
}
```

## Register the Checkout Session params your code uses {#createStripeCheckoutConfig}

`POST /v1/sites/{site}/stripe_checkout_configs`

Registers exactly the object your code passes to stripe.checkout.sessions.create (customer, URLs and secrets are not needed and are ignored). Test runs mirror it into Stripe test mode; when it matches your latest live checkout, the scanned test page counts as an observation of your live checkout. Re-register after changing your checkout code, then call createTestRun.

**Scope** `test_runs:write` · **Idempotent** (send `Idempotency-Key`) · **Operation** `createStripeCheckoutConfig`

SDK: `checkbox.stripeCheckoutConfigs.create()` (Node) · `checkbox.stripe_checkout_configs.create()` (Python). MCP / agents: operation `createStripeCheckoutConfig`.

#### Parameters

| Parameter | In | Type | Description |
|---|---|---|---|
| `site` **required** | path | string | Unique identifier of the site (site_…). |

#### Body

| Field | Type | Description |
|---|---|---|
| `params` **required** | object | The Checkout Session parameters (mode "subscription", line_items, custom_text, consent_collection, subscription_data…). |
| `name` | string | Your name for it. |
| `metadata` | object | Up to 20 key/value string pairs you can attach to the object. |

#### Returns

Returns a [StripeCheckoutConfig](https://checkbox.site/docs/api/test-runs#the-stripecheckoutconfig-object) object.

Errors use the [standard envelope](https://checkbox.site/docs/api/errors) with a stable `code` and, where useful, `next_action`.

```curl
curl https://app.checkbox.site/v1/sites/site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/stripe_checkout_configs \
  -u "$CHECKBOX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
  "params": {
    "mode": "subscription",
    "line_items": [
      {
        "price": "price_1Pro",
        "quantity": 1
      }
    ],
    "subscription_data": {
      "trial_period_days": 14
    }
  }
}'
```
```node
import Checkbox from '@checkbox-site/node';

const checkbox = new Checkbox(process.env.CHECKBOX_SECRET_KEY);

const stripeCheckoutConfig = await checkbox.stripeCheckoutConfigs.create('site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b', {
  params: {
    mode: 'subscription',
    line_items: [{ price: 'price_1Pro', quantity: 1 }],
    subscription_data: { trial_period_days: 14 },
  },
});
console.log(stripeCheckoutConfig);
```
```python
import os
from checkbox import Checkbox

checkbox = Checkbox(os.environ["CHECKBOX_SECRET_KEY"])

stripe_checkout_config = checkbox.stripe_checkout_configs.create(
    "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
    params={
        "mode": "subscription",
        "line_items": [{"price": "price_1Pro", "quantity": 1}],
        "subscription_data": {"trial_period_days": 14},
    },
)
print(stripe_checkout_config)
```

```json title="Response"
{
  "id": "scc_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "object": "stripe_checkout_config",
  "livemode": false,
  "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "name": "Pro monthly with 14-day trial",
  "params": {
    "mode": "subscription",
    "line_items": [
      {
        "price": "price_1Pro",
        "quantity": 1
      }
    ],
    "subscription_data": {
      "trial_period_days": 14
    },
    "consent_collection": {
      "terms_of_service": "required"
    }
  },
  "params_hash": "9f2c…",
  "ignored_params": [
    "success_url",
    "customer_email"
  ],
  "metadata": {
    "order_ref": "A-1001"
  },
  "created": 1793318400
}
```

## Retrieve a registered checkout config {#retrieveStripeCheckoutConfig}

`GET /v1/stripe_checkout_configs/{stripe_checkout_config}`

One registered Checkout Session config.

**Scope** `test_runs:read` · **Operation** `retrieveStripeCheckoutConfig`

SDK: `checkbox.stripeCheckoutConfigs.retrieve()` (Node) · `checkbox.stripe_checkout_configs.retrieve()` (Python). MCP / agents: operation `retrieveStripeCheckoutConfig`.

#### Parameters

| Parameter | In | Type | Description |
|---|---|---|---|
| `stripe_checkout_config` **required** | path | string | Unique identifier of the stripe checkout config (scc_…). |

#### Returns

Returns a [StripeCheckoutConfig](https://checkbox.site/docs/api/test-runs#the-stripecheckoutconfig-object) object.

Errors use the [standard envelope](https://checkbox.site/docs/api/errors) with a stable `code` and, where useful, `next_action`.

```curl
curl https://app.checkbox.site/v1/stripe_checkout_configs/scc_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b \
  -u "$CHECKBOX_SECRET_KEY:"
```
```node
import Checkbox from '@checkbox-site/node';

const checkbox = new Checkbox(process.env.CHECKBOX_SECRET_KEY);

const stripeCheckoutConfig = await checkbox.stripeCheckoutConfigs.retrieve('scc_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(stripeCheckoutConfig);
```
```python
import os
from checkbox import Checkbox

checkbox = Checkbox(os.environ["CHECKBOX_SECRET_KEY"])

stripe_checkout_config = checkbox.stripe_checkout_configs.retrieve("scc_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(stripe_checkout_config)
```

```json title="Response"
{
  "id": "scc_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "object": "stripe_checkout_config",
  "livemode": false,
  "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "name": "Pro monthly with 14-day trial",
  "params": {
    "mode": "subscription",
    "line_items": [
      {
        "price": "price_1Pro",
        "quantity": 1
      }
    ],
    "subscription_data": {
      "trial_period_days": 14
    },
    "consent_collection": {
      "terms_of_service": "required"
    }
  },
  "params_hash": "9f2c…",
  "ignored_params": [
    "success_url",
    "customer_email"
  ],
  "metadata": {
    "order_ref": "A-1001"
  },
  "created": 1793318400
}
```

## Delete a registered checkout config {#deleteStripeCheckoutConfig}

`DELETE /v1/stripe_checkout_configs/{stripe_checkout_config}`

Deletes the config; later runs mirror the next newest one (or your live checkout).

**Scope** `test_runs:write` · **Operation** `deleteStripeCheckoutConfig`

SDK: `checkbox.stripeCheckoutConfigs.del()` (Node) · `checkbox.stripe_checkout_configs.delete()` (Python). MCP / agents: operation `deleteStripeCheckoutConfig`.

#### Parameters

| Parameter | In | Type | Description |
|---|---|---|---|
| `stripe_checkout_config` **required** | path | string | Unique identifier of the stripe checkout config (scc_…). |

#### Returns

Returns a `DeletedStripeCheckoutConfig` object.

Errors use the [standard envelope](https://checkbox.site/docs/api/errors) with a stable `code` and, where useful, `next_action`.

```curl
curl https://app.checkbox.site/v1/stripe_checkout_configs/scc_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b \
  -u "$CHECKBOX_SECRET_KEY:" \
  -X DELETE
```
```node
import Checkbox from '@checkbox-site/node';

const checkbox = new Checkbox(process.env.CHECKBOX_SECRET_KEY);

const stripeCheckoutConfig = await checkbox.stripeCheckoutConfigs.del('scc_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(stripeCheckoutConfig);
```
```python
import os
from checkbox import Checkbox

checkbox = Checkbox(os.environ["CHECKBOX_SECRET_KEY"])

stripe_checkout_config = checkbox.stripe_checkout_configs.delete("scc_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(stripe_checkout_config)
```

```json title="Response"
{
  "id": "scc_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "object": "stripe_checkout_config",
  "deleted": true
}
```

## List test runs of a site {#listTestRuns}

`GET /v1/sites/{site}/test_runs`

Test runs of the site, newest first.

**Scope** `test_runs:read` · **Operation** `listTestRuns`

SDK: `checkbox.testRuns.list()` (Node) · `checkbox.test_runs.list()` (Python). MCP / agents: operation `listTestRuns`.

#### Parameters

| Parameter | In | Type | Description |
|---|---|---|---|
| `site` **required** | path | string | Unique identifier of the site (site_…). |
| `limit` | query | integer | Number of objects to return, 1-100 (default 10). |
| `starting_after` | query | string | Cursor: an object id; returns the objects after it (older). |
| `ending_before` | query | string | Cursor: an object id; returns the objects before it (newer). |

#### Returns

Returns a list (`{ object: "list", data, has_more, url }`) of `TestRun` objects.

Errors use the [standard envelope](https://checkbox.site/docs/api/errors) with a stable `code` and, where useful, `next_action`.

```curl
curl -G https://app.checkbox.site/v1/sites/site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/test_runs \
  -u "$CHECKBOX_SECRET_KEY:" \
  -d limit=3
```
```node
import Checkbox from '@checkbox-site/node';

const checkbox = new Checkbox(process.env.CHECKBOX_SECRET_KEY);

const testRuns = await checkbox.testRuns.list('site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b', { limit: 3 });
console.log(testRuns.data);
```
```python
import os
from checkbox import Checkbox

checkbox = Checkbox(os.environ["CHECKBOX_SECRET_KEY"])

test_runs = checkbox.test_runs.list("site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b", limit=3)
print(test_runs.data)
```

```json title="Response"
{
  "object": "list",
  "data": [
    {
      "id": "run_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
      "object": "test_run",
      "livemode": false,
      "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
      "type": "stripe",
      "mode": "real_checkout",
      "status": "running",
      "phase": "await_ack",
      "test_mode": true,
      "inbox_address": "k3y9+run-3f9a1c2e7b4d@in.checkbox.site",
      "created": 1793318400,
      "started": 1793318400,
      "finished": 1793318400,
      "steps": [],
      "failure": {
        "code": "price_not_in_test_mode",
        "message": "Price price_1Live does not exist in Stripe test mode.",
        "detail": {
          "prices": []
        },
        "fix": {
          "summary": "Send the acknowledgment from your checkout.session.completed webhook.",
          "steps": [
            "Handle checkout.session.completed…"
          ],
          "where": "https://dashboard.stripe.com/test/apikeys",
          "operation_id": "createTestRun",
          "stripe_params": {
            "features": {}
          },
          "command": "stripe prices create --unit-amount=1900 --currency=usd -d \"recurring[interval]=month\"",
          "docs_url": "https://checkbox.site/docs/test-runs#acknowledgment"
        }
      },
      "result": {
        "summary": {
          "passed": 9,
          "failed": 2,
          "needs_review": 1,
          "warnings": 1
        },
        "checks": [],
        "monitoring": {
          "checkout_page_counted": true,
          "cancellation_flow_counted": true,
          "emails_counted": 3,
          "reminder_events": 1,
          "notes": []
        }
      },
      "stripe": {
        "test_clock": "clock_1Q…",
        "customer": "cus_Q…",
        "subscription": "sub_1Q…",
        "checkout_session": "cs_test_a1…",
        "mirrored_from": "registered",
        "checkout_config": null,
        "config_hash": "4be1…",
        "matches_live": true,
        "cleaned_up": true
      },
      "proof": {
        "mode": "real_checkout",
        "what_it_proves": "Your real checkout, webhook handlers and email code ran in Stripe test mode…",
        "notices": "merchant",
        "notices_sender": "merchant",
        "payment_link": null,
        "webhook_endpoints": [
          {
            "host": "staging.acme.example",
            "enabled_events": [],
            "status": "enabled"
          }
        ],
        "stripe_events": [
          {
            "id": "evt_1",
            "type": "checkout.session.completed",
            "created": 1767225600,
            "pending_webhooks": 0
          }
        ],
        "cancellation": {
          "via": "merchant_flow",
          "declared_by_merchant": true,
          "confirmed_in_stripe": true,
          "subscription": "sub_1Q…",
          "note": null
        }
      },
      "next_action": {
        "type": "verify_domain",
        "message": "Verify your site domain to turn on live mode.",
        "operation_id": "createDomainVerification",
        "mcp_tool": "call_api",
        "endpoint": "POST /v1/sites/{site}/domain_verification",
        "url": "https://checkbox.site/docs/concepts/activation#verify"
      }
    }
  ],
  "has_more": false,
  "url": "/v1/sites"
}
```

## Start a lifecycle test run {#createTestRun}

`POST /v1/sites/{site}/test_runs`

Starts a synthetic test subscription in TEST mode and returns it queued. Stripe runs mirror your checkout into a test-mode Checkout Session, subscribe a test customer (card 4242) whose email is a Checkbox inbox, wait for your acknowledgment, advance a test clock through every legal notice window before trial end and renewal, cancel online in the customer portal and wait for the confirmation; then delete the test clock. Takes about 5-20 minutes: poll retrieveTestRun (next_action says when) or listen for test_run.completed. Each step reports observations, check results and agent-readable fixes; apply them and run again to verify.

**Scope** `test_runs:write` · **Idempotent** (send `Idempotency-Key`) · **Live mode needs [activation](https://checkbox.site/docs/concepts/activation)** · **Operation** `createTestRun`

SDK: `checkbox.testRuns.create()` (Node) · `checkbox.test_runs.create()` (Python). MCP / agents: operation `createTestRun`.

#### Parameters

| Parameter | In | Type | Description |
|---|---|---|---|
| `site` **required** | path | string | Unique identifier of the site (site_…). |

#### Body

| Field | Type | Description |
|---|---|---|
| `mode` | enum | Stripe runs. mirror (default): Checkbox mirrors your checkout into test mode and moves a test clock through every legal notice window. real_checkout ("Run proof"): the shopper buys through YOUR checkout on your test deployment (stripe.checkout_url or stripe.payment_link) with card 4242 and the run inbox, so your own webhook handlers and email code fire; Checkbox watches the inbox, Stripe's events and your cancel flow. Needs your test deployment on Stripe TEST keys with a test-mode webhook endpoint. One of `mirror`, `real_checkout`. |
| `type` | enum | stripe (default when a Stripe test key is stored or the site uses Stripe) or generic (test coupon / test account, no time travel). One of `stripe`, `generic`. |
| `email_wait_seconds` | integer | How long to wait for each email (acknowledgment, reminders, cancellation), 15-900 seconds (default 120). |
| `advance_time` | boolean | Advance a Stripe test clock through the trial end and the first renewal (default true). |
| `cancel` | boolean | Cancel online at the end (default true). |
| `region` | enum | State the synthetic shopper visits from (default: the site's first scan region). One of `US-FED`, `US-AL`, `US-AK`, `US-AZ`, `US-AR`, `US-CA`, `US-CO`, `US-CT`, `US-DE`, `US-DC`, `US-FL`, `US-GA`, `US-HI`, `US-ID`, `US-IL`, `US-IN`, `US-IA`, `US-KS`, `US-KY`, `US-LA`, `US-ME`, `US-MD`, `US-MA`, `US-MI`, `US-MN`, `US-MS`, `US-MO`, `US-MT`, `US-NE`, `US-NV`, `US-NH`, `US-NJ`, `US-NM`, `US-NY`, `US-NC`, `US-ND`, `US-OH`, `US-OK`, `US-OR`, `US-PA`, `US-RI`, `US-SC`, `US-SD`, `US-TN`, `US-TX`, `US-UT`, `US-VT`, `US-VA`, `US-WA`, `US-WV`, `US-WI`, `US-WY`. |
| `stripe` | object | Stripe options. |
| `stripe.checkout_config` | string | Registered checkout config (scc_…) to mirror; default: the newest one, else your latest live Checkout Session. "none" skips the registered configs (mirror your live checkout, stripe.price or stripe.payment_link instead). |
| `stripe.price` | string | A test-mode price to subscribe to when nothing is registered. |
| `stripe.trial_period_days` | integer | Trial for `price` (registered configs carry their own). |
| `stripe.price_map` | object | Live price id -> test-mode price id, when they differ. |
| `stripe.checkout_steps` | array of ScanSteps | Steps that complete your hosted checkout with card 4242 4242 4242 4242 when it differs from Stripe's default page. |
| `stripe.cancel_url` | string | Your cancellation page (after login with test_account, or public). Checkbox never falls back to the Stripe portal when your flow fails. |
| `stripe.cancel_steps` | array of ScanSteps | Clicks that cancel in your flow (after login / cancel_url), e.g. [{ "action": "click", "text": "Cancel subscription" }, { "action": "click", "text": "Confirm" }]. |
| `stripe.cancel_via` | enum | How your customers cancel. Default: your flow when test_account / cancel_url / cancel_steps describe it, else the Stripe portal reported as a warning (cancel_flow_not_configured). One of `merchant_flow`, `stripe_portal`. |
| `stripe.test_account` | object | A test account the shopper logs into: to buy as a logged-in user (real_checkout) and to cancel through your in-app flow. Mail to its address counts when you BCC the site's Checkbox address. |
| `stripe.payment_link` | string | A Stripe Payment Link (plink_… or its buy.stripe.com URL): mirror mode mirrors its prices, custom_text, consent_collection and trial; real_checkout buys through it (test-mode links only). |
| `stripe.checkout_url` | string | real_checkout: the page of your TEST deployment that leads to your Stripe test-mode checkout (a pricing page, or a route that creates the Checkout Session and redirects). |
| `stripe.pre_checkout_steps` | array of ScanSteps | real_checkout: clicks from checkout_url to the payment page, e.g. [{ "action": "click", "text": "Start free trial" }]. |
| `stripe.cleanup_url` | string | A page of your test deployment Checkbox opens before the checkout (after logging in) that CLEANS UP the test account (ends its subscription), so every run starts without one. Mirror mode opens it before cancelling the test account's subscription. (Round 3: renamed from reset_url, which meant the opposite of the site's test_account.reactivate_url.) |
| `stripe.reset_url` | string | Deprecated alias of cleanup_url. |
| `generic` | object | Generic (non-Stripe) options. |
| `generic.checkout_url` | string | Checkout page (default: the site's scan settings). |
| `generic.checkout_steps` | array of ScanSteps | Steps to reach the final pre-payment screen. |
| `generic.complete_purchase` | object | Complete a real sign-up with a test coupon. |
| `generic.account` | object | Test account used to cancel online. |
| `generic.cancel_steps` | array of ScanSteps | Explicit cancellation steps (otherwise auto-discovered). |

#### Returns

Returns a `TestRun` object.

In live mode before activation: `403 account_not_activated` with `next_action.operation_id: "createDomainVerification"`.

Errors use the [standard envelope](https://checkbox.site/docs/api/errors) with a stable `code` and, where useful, `next_action`.

```curl
curl https://app.checkbox.site/v1/sites/site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/test_runs \
  -u "$CHECKBOX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -d '{}'
```
```node
import Checkbox from '@checkbox-site/node';

const checkbox = new Checkbox(process.env.CHECKBOX_SECRET_KEY);

const testRun = await checkbox.testRuns.create('site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(testRun);
```
```python
import os
from checkbox import Checkbox

checkbox = Checkbox(os.environ["CHECKBOX_SECRET_KEY"])

test_run = checkbox.test_runs.create("site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(test_run)
```

```json title="Response"
{
  "id": "run_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "object": "test_run",
  "livemode": false,
  "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "type": "stripe",
  "mode": "real_checkout",
  "status": "running",
  "phase": "await_ack",
  "test_mode": true,
  "inbox_address": "k3y9+run-3f9a1c2e7b4d@in.checkbox.site",
  "created": 1793318400,
  "started": 1793318400,
  "finished": 1793318400,
  "steps": [],
  "failure": {
    "code": "price_not_in_test_mode",
    "message": "Price price_1Live does not exist in Stripe test mode.",
    "detail": {
      "prices": []
    },
    "fix": {
      "summary": "Send the acknowledgment from your checkout.session.completed webhook.",
      "steps": [
        "Handle checkout.session.completed…"
      ],
      "where": "https://dashboard.stripe.com/test/apikeys",
      "operation_id": "createTestRun",
      "stripe_params": {
        "features": {
          "subscription_cancel": {
            "enabled": true
          }
        }
      },
      "command": "stripe prices create --unit-amount=1900 --currency=usd -d \"recurring[interval]=month\"",
      "docs_url": "https://checkbox.site/docs/test-runs#acknowledgment"
    }
  },
  "result": {
    "summary": {
      "passed": 9,
      "failed": 2,
      "needs_review": 1,
      "warnings": 1
    },
    "checks": [],
    "monitoring": {
      "checkout_page_counted": true,
      "cancellation_flow_counted": true,
      "emails_counted": 3,
      "reminder_events": 1,
      "notes": []
    }
  },
  "stripe": {
    "test_clock": "clock_1Q…",
    "customer": "cus_Q…",
    "subscription": "sub_1Q…",
    "checkout_session": "cs_test_a1…",
    "mirrored_from": "registered",
    "checkout_config": null,
    "config_hash": "4be1…",
    "matches_live": true,
    "cleaned_up": true
  },
  "proof": {
    "mode": "real_checkout",
    "what_it_proves": "Your real checkout, webhook handlers and email code ran in Stripe test mode…",
    "notices": "merchant",
    "notices_sender": "merchant",
    "payment_link": null,
    "webhook_endpoints": [
      {
        "host": "staging.acme.example",
        "enabled_events": [
          "checkout.session.completed"
        ],
        "status": "enabled"
      }
    ],
    "stripe_events": [
      {
        "id": "evt_1",
        "type": "checkout.session.completed",
        "created": 1767225600,
        "pending_webhooks": 0
      }
    ],
    "cancellation": {
      "via": "merchant_flow",
      "declared_by_merchant": true,
      "confirmed_in_stripe": true,
      "subscription": "sub_1Q…",
      "note": null
    }
  },
  "next_action": {
    "type": "verify_domain",
    "message": "Verify your site domain to turn on live mode.",
    "operation_id": "createDomainVerification",
    "mcp_tool": "call_api",
    "endpoint": "POST /v1/sites/{site}/domain_verification",
    "url": "https://checkbox.site/docs/concepts/activation#verify"
  }
}
```

## Retrieve a test run {#retrieveTestRun}

`GET /v1/test_runs/{test_run}`

The run with every step's status, observations, check results and fixes. While it runs, next_action.type is "wait"; when it finished, next_action says what to fix (apply_fixes / review_warnings) or "none".

**Scope** `test_runs:read` · **Operation** `retrieveTestRun`

SDK: `checkbox.testRuns.retrieve()` (Node) · `checkbox.test_runs.retrieve()` (Python). MCP / agents: operation `retrieveTestRun`.

#### Parameters

| Parameter | In | Type | Description |
|---|---|---|---|
| `test_run` **required** | path | string | Unique identifier of the test run (run_…). |

#### Returns

Returns a `TestRun` object.

Errors use the [standard envelope](https://checkbox.site/docs/api/errors) with a stable `code` and, where useful, `next_action`.

```curl
curl https://app.checkbox.site/v1/test_runs/run_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b \
  -u "$CHECKBOX_SECRET_KEY:"
```
```node
import Checkbox from '@checkbox-site/node';

const checkbox = new Checkbox(process.env.CHECKBOX_SECRET_KEY);

const testRun = await checkbox.testRuns.retrieve('run_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(testRun);
```
```python
import os
from checkbox import Checkbox

checkbox = Checkbox(os.environ["CHECKBOX_SECRET_KEY"])

test_run = checkbox.test_runs.retrieve("run_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(test_run)
```

```json title="Response"
{
  "id": "run_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "object": "test_run",
  "livemode": false,
  "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "type": "stripe",
  "mode": "real_checkout",
  "status": "running",
  "phase": "await_ack",
  "test_mode": true,
  "inbox_address": "k3y9+run-3f9a1c2e7b4d@in.checkbox.site",
  "created": 1793318400,
  "started": 1793318400,
  "finished": 1793318400,
  "steps": [],
  "failure": {
    "code": "price_not_in_test_mode",
    "message": "Price price_1Live does not exist in Stripe test mode.",
    "detail": {
      "prices": []
    },
    "fix": {
      "summary": "Send the acknowledgment from your checkout.session.completed webhook.",
      "steps": [
        "Handle checkout.session.completed…"
      ],
      "where": "https://dashboard.stripe.com/test/apikeys",
      "operation_id": "createTestRun",
      "stripe_params": {
        "features": {
          "subscription_cancel": {
            "enabled": true
          }
        }
      },
      "command": "stripe prices create --unit-amount=1900 --currency=usd -d \"recurring[interval]=month\"",
      "docs_url": "https://checkbox.site/docs/test-runs#acknowledgment"
    }
  },
  "result": {
    "summary": {
      "passed": 9,
      "failed": 2,
      "needs_review": 1,
      "warnings": 1
    },
    "checks": [],
    "monitoring": {
      "checkout_page_counted": true,
      "cancellation_flow_counted": true,
      "emails_counted": 3,
      "reminder_events": 1,
      "notes": []
    }
  },
  "stripe": {
    "test_clock": "clock_1Q…",
    "customer": "cus_Q…",
    "subscription": "sub_1Q…",
    "checkout_session": "cs_test_a1…",
    "mirrored_from": "registered",
    "checkout_config": null,
    "config_hash": "4be1…",
    "matches_live": true,
    "cleaned_up": true
  },
  "proof": {
    "mode": "real_checkout",
    "what_it_proves": "Your real checkout, webhook handlers and email code ran in Stripe test mode…",
    "notices": "merchant",
    "notices_sender": "merchant",
    "payment_link": null,
    "webhook_endpoints": [
      {
        "host": "staging.acme.example",
        "enabled_events": [
          "checkout.session.completed"
        ],
        "status": "enabled"
      }
    ],
    "stripe_events": [
      {
        "id": "evt_1",
        "type": "checkout.session.completed",
        "created": 1767225600,
        "pending_webhooks": 0
      }
    ],
    "cancellation": {
      "via": "merchant_flow",
      "declared_by_merchant": true,
      "confirmed_in_stripe": true,
      "subscription": "sub_1Q…",
      "note": null
    }
  },
  "next_action": {
    "type": "verify_domain",
    "message": "Verify your site domain to turn on live mode.",
    "operation_id": "createDomainVerification",
    "mcp_tool": "call_api",
    "endpoint": "POST /v1/sites/{site}/domain_verification",
    "url": "https://checkbox.site/docs/concepts/activation#verify"
  }
}
```

## Cancel a test run {#cancelTestRun}

`POST /v1/test_runs/{test_run}/cancel`

Stops a queued or running test run; the worker deletes its Stripe test clock (and with it the test customer and subscription). Finished runs cannot be canceled (resource_conflict).

**Scope** `test_runs:write` · **Idempotent** (send `Idempotency-Key`) · **Operation** `cancelTestRun`

SDK: `checkbox.testRuns.cancel()` (Node) · `checkbox.test_runs.cancel()` (Python). MCP / agents: operation `cancelTestRun`.

#### Parameters

| Parameter | In | Type | Description |
|---|---|---|---|
| `test_run` **required** | path | string | Unique identifier of the test run (run_…). |

#### Returns

Returns a `TestRun` object.

Errors use the [standard envelope](https://checkbox.site/docs/api/errors) with a stable `code` and, where useful, `next_action`.

```curl
curl https://app.checkbox.site/v1/test_runs/run_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/cancel \
  -u "$CHECKBOX_SECRET_KEY:" \
  -X POST
```
```node
import Checkbox from '@checkbox-site/node';

const checkbox = new Checkbox(process.env.CHECKBOX_SECRET_KEY);

const testRun = await checkbox.testRuns.cancel('run_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(testRun);
```
```python
import os
from checkbox import Checkbox

checkbox = Checkbox(os.environ["CHECKBOX_SECRET_KEY"])

test_run = checkbox.test_runs.cancel("run_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(test_run)
```

```json title="Response"
{
  "id": "run_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "object": "test_run",
  "livemode": false,
  "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "type": "stripe",
  "mode": "real_checkout",
  "status": "running",
  "phase": "await_ack",
  "test_mode": true,
  "inbox_address": "k3y9+run-3f9a1c2e7b4d@in.checkbox.site",
  "created": 1793318400,
  "started": 1793318400,
  "finished": 1793318400,
  "steps": [],
  "failure": {
    "code": "price_not_in_test_mode",
    "message": "Price price_1Live does not exist in Stripe test mode.",
    "detail": {
      "prices": []
    },
    "fix": {
      "summary": "Send the acknowledgment from your checkout.session.completed webhook.",
      "steps": [
        "Handle checkout.session.completed…"
      ],
      "where": "https://dashboard.stripe.com/test/apikeys",
      "operation_id": "createTestRun",
      "stripe_params": {
        "features": {
          "subscription_cancel": {
            "enabled": true
          }
        }
      },
      "command": "stripe prices create --unit-amount=1900 --currency=usd -d \"recurring[interval]=month\"",
      "docs_url": "https://checkbox.site/docs/test-runs#acknowledgment"
    }
  },
  "result": {
    "summary": {
      "passed": 9,
      "failed": 2,
      "needs_review": 1,
      "warnings": 1
    },
    "checks": [],
    "monitoring": {
      "checkout_page_counted": true,
      "cancellation_flow_counted": true,
      "emails_counted": 3,
      "reminder_events": 1,
      "notes": []
    }
  },
  "stripe": {
    "test_clock": "clock_1Q…",
    "customer": "cus_Q…",
    "subscription": "sub_1Q…",
    "checkout_session": "cs_test_a1…",
    "mirrored_from": "registered",
    "checkout_config": null,
    "config_hash": "4be1…",
    "matches_live": true,
    "cleaned_up": true
  },
  "proof": {
    "mode": "real_checkout",
    "what_it_proves": "Your real checkout, webhook handlers and email code ran in Stripe test mode…",
    "notices": "merchant",
    "notices_sender": "merchant",
    "payment_link": null,
    "webhook_endpoints": [
      {
        "host": "staging.acme.example",
        "enabled_events": [
          "checkout.session.completed"
        ],
        "status": "enabled"
      }
    ],
    "stripe_events": [
      {
        "id": "evt_1",
        "type": "checkout.session.completed",
        "created": 1767225600,
        "pending_webhooks": 0
      }
    ],
    "cancellation": {
      "via": "merchant_flow",
      "declared_by_merchant": true,
      "confirmed_in_stripe": true,
      "subscription": "sub_1Q…",
      "note": null
    }
  },
  "next_action": {
    "type": "verify_domain",
    "message": "Verify your site domain to turn on live mode.",
    "operation_id": "createDomainVerification",
    "mcp_tool": "call_api",
    "endpoint": "POST /v1/sites/{site}/domain_verification",
    "url": "https://checkbox.site/docs/concepts/activation#verify"
  }
}
```
