# 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/cli';

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/cli';

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/cli';

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/cli';

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/cli';

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/cli';

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/cli';

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/cli';

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",
      "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": {
        "score": 82,
        "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
      },
      "next_action": {
        "type": "verify_domain",
        "message": "Verify your site domain to turn on live mode.",
        "operation_id": "createDomainVerification",
        "mcp_tool": "call_api",
        "url": "https://checkbox.site/docs/api/activation"
      }
    }
  ],
  "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 |
|---|---|---|
| `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. |
| `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 | Cancel on your own page instead of the Stripe customer portal (it must work for the test subscriber). |
| `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/cli';

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",
  "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": {
    "score": 82,
    "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
  },
  "next_action": {
    "type": "verify_domain",
    "message": "Verify your site domain to turn on live mode.",
    "operation_id": "createDomainVerification",
    "mcp_tool": "call_api",
    "url": "https://checkbox.site/docs/api/activation"
  }
}
```

## 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/cli';

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",
  "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": {
    "score": 82,
    "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
  },
  "next_action": {
    "type": "verify_domain",
    "message": "Verify your site domain to turn on live mode.",
    "operation_id": "createDomainVerification",
    "mcp_tool": "call_api",
    "url": "https://checkbox.site/docs/api/activation"
  }
}
```

## 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/cli';

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",
  "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": {
    "score": 82,
    "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
  },
  "next_action": {
    "type": "verify_domain",
    "message": "Verify your site domain to turn on live mode.",
    "operation_id": "createDomainVerification",
    "mcp_tool": "call_api",
    "url": "https://checkbox.site/docs/api/activation"
  }
}
```
