# Receipts API

> Consent receipts (opt-in per account): for each subscriber checkout where they are on, a tamper-evident record of the disclosure visibly rendered next to the pay button and the consent given — a structured snapshot of the disclosure (visible text, sanitized HTML, legally relevant styles, checkbox st

ate, button label, layout), hashed, chained into the evidence vault, signed and re-rendered as an image on demand. For Stripe-hosted checkouts, reconstructed from Stripe's record. Evidence you can produce; not legal protection.

| Operation | Endpoint |
|---|---|
| [Mint a capture token for a checkout](#createReceiptToken) | `POST /v1/receipt_tokens` |
| [List consent receipts](#listReceipts) | `GET /v1/receipts` |
| [Record a consent receipt](#createReceipt) | `POST /v1/receipts` |
| [Erase a subscriber's receipts](#eraseReceipts) | `POST /v1/receipts/erase` |
| [Retrieve a consent receipt](#retrieveReceipt) | `GET /v1/receipts/{receipt}` |
| [Render a receipt as an image](#renderReceipt) | `GET /v1/receipts/{receipt}/render.png` |
| [Verify a receipt's integrity](#verifyReceipt) | `GET /v1/receipts/{receipt}/verify` |
| [Download a self-verifying receipt bundle](#exportReceipt) | `GET /v1/receipts/{receipt}/export` |
| [Retrieve receipt settings](#retrieveReceiptSettings) | `GET /v1/receipt_settings` |
| [Update receipt settings](#updateReceiptSettings) | `POST /v1/receipt_settings` |

## The Receipt object

A consent receipt: a tamper-evident record of what one subscriber saw next to the pay button and agreed to at checkout. Evidence you can produce; not a legal opinion.

| Field | Type | Description |
|---|---|---|
| `id` | string | Unique identifier of the receipt (rcpt_…). |
| `object` | enum | String representing the object's type. Always "receipt". |
| `livemode` | boolean | true for live-mode objects, false for test mode (ck_test_ keys act on an isolated sandbox). |
| `created` | integer | When Checkbox recorded the receipt (server time). Unix timestamp in seconds. |
| `site` | string | Site the receipt belongs to. |
| `source` | enum | elements: <checkbox-disclosure>; snippet: /r.js; api: your server; stripe_*: reconstructed from Stripe's record of a Stripe-hosted page. One of `elements`, `snippet`, `api`, `stripe_checkout`, `stripe_payment_link`. |
| `binding` | enum | bound: the browser presented a valid capture token your server minted; unbound: no (valid) token, so the receipt is a client claim; server: recorded by your server or reconstructed from Stripe. One of `bound`, `unbound`, `server`. |
| `reconstructed` | boolean | true when built from Stripe's record instead of the page the subscriber saw (lower evidentiary weight). |
| `evidentiary_weight` | enum | How the record was obtained, strongest first. Not a legal assessment. One of `direct_capture`, `direct_capture_unbound`, `server_record`, `reconstructed_from_stripe`. |
| `detection` | object | Disclosure detection. |
| `detection.method` | enum | How the disclosure region was found. One of `elements`, `marked`, `auto`, `server`, `stripe`. |
| `detection.confidence` | enum | Confidence that the region is the disclosure. One of `high`, `medium`, `low`. |
| `customer_reference` | string \| null | Verified subscriber reference (from the capture token, your server, or Stripe). |
| `client_reference` | string \| null | Reference the page claimed (unverified). |
| `email_sha256` | string \| null | HMAC-SHA-256 (keyed per account) of the subscriber's lowercased email, when your server gave one. Personal data: erased with the receipt. |
| `checkout_session` | string \| null | Stripe Checkout Session id. |
| `subscription` | string \| null | Subscription id, when known. |
| `consent` | string \| null | cons_… recorded with this receipt by <checkbox-disclosure>. |
| `pre_checkout_receipt` | string \| null | For Stripe-hosted checkouts: the rcpt_… captured on your page before the redirect. |
| `page_url` | string \| null | Page (origin + path, query string removed). |
| `language` | string \| null | Page / browser language. |
| `user_agent_class` | string \| null | Device + browser family (never the full user agent). |
| `viewport` | object \| null | Viewport at submit. |
| `viewport.width` | number | CSS px. |
| `viewport.height` | number | CSS px. |
| `client_timestamp` | integer \| null | Device clock at capture (unix seconds; informational). |
| `disclosure` | object | What the disclosure said and how it was shown. |
| `disclosure.text` | string | Disclosure text the subscriber saw (personal data redacted). |
| `disclosure.sha256` | string | SHA-256 of the text. |
| `disclosure.presentation` | object | How the disclosure was presented (null fields: not measured). |
| `checkbox` | object \| null | The consent checkbox (null: none found). |
| `checkbox.present` | boolean | A consent checkbox was found. |
| `checkbox.checked` | boolean | It was ticked at submit. |
| `checkbox.label` | string \| null | Its label as captured. Null for receipts reconstructed from Stripe: Stripe's record has no checkbox wording, so none is claimed. |
| `checkbox.required` | boolean | It was required. |
| `submit_label` | string \| null | Label of the button the subscriber pressed (null when not observed, e.g. Stripe-hosted pages). |
| `stripe` | ReceiptStripeRecord \| null | The Checkout Session as Stripe recorded it (Stripe-hosted checkouts and Payment Links). |
| `flags` | array of strings | Things a reviewer should know: unbound, token_invalid, token_expired, token_used_up, token_site_mismatch, low_confidence_detection, disclosure_not_found, below_fold, not_above_submit, far_from_submit, low_contrast, small_font, hidden, hidden_text (text was present but not visibly rendered; left out), typed_text_masked, checkbox_unchecked, no_consent_checkbox, client_clock_skew, implausible_render_gap, reconstructed, no_pre_checkout_snapshot, linked_by_client_nonce, multiple_pre_checkout_candidates, stripe_mode_mismatch, no_terms_of_service_consent, no_custom_text, test_mode, unsigned. |
| `integrity` | object | Tamper evidence. verifyReceipt checks all of it. |
| `integrity.receipt_sha256` | string | SHA-256 of the canonical JSON receipt (keys sorted, no whitespace) — also the evidence record's content hash. |
| `integrity.evidence` | string \| null | ev_… record that chains the receipt into the site's evidence vault. |
| `integrity.chain_hash` | string \| null | Chain hash of that record. |
| `integrity.public_key_id` | string \| null | Ed25519 key that signed the receipt (published at /.well-known/checkbox-keys.json). |
| `integrity.signed` | integer \| null | When it was signed (unix seconds). |
| `retain_until` | integer | When the receipt is deleted under your retention setting. Unix timestamp in seconds. |
| `render_url` | string | PNG of what the subscriber saw, re-rendered from the snapshot (renderReceipt, same auth). |
| `next_action` | NextAction | What the caller (often an AI agent) should do next. |

## Mint a capture token for a checkout {#createReceiptToken}

`POST /v1/receipt_tokens`

Call from your server when you render a checkout page: returns a short-lived token (rct_…) to put into the page (data-receipt-token on the disclosure or form, or receipt-token on <checkbox-disclosure>). Receipts that present it are "bound" to this checkout and carry your customer_reference; receipts without one are accepted but flagged "unbound". For Stripe-hosted checkouts also set metadata.checkbox_receipt_token=<id> on the Checkout Session (or pass checkout_session here).

**Scope** `receipts:write` · **Idempotent** (send `Idempotency-Key`) · **Operation** `createReceiptToken`

SDK: `checkbox.receiptTokens.create()` (Node) · `checkbox.receipt_tokens.create()` (Python). MCP / agents: operation `createReceiptToken`.

#### Body

| Field | Type | Description |
|---|---|---|
| `site` **required** | string | site_… whose checkout page will present the token. |
| `customer_reference` | string | Your id for the subscriber (Stripe customer id, your user id, order id). Copied onto the receipt as the verified customer_reference. |
| `checkout_session` | string | Stripe Checkout Session id when you already created it (links the Stripe record to this receipt). Or put the token id in the session's metadata.checkbox_receipt_token. |
| `email` | string | The subscriber's email address; only an HMAC-SHA-256 under your account's own key is stored (listReceipts?email= finds receipts by it). Still personal data: covered by retention and eraseReceipts. |
| `expires_in` | integer | Seconds until the token expires (default 3600). |
| `max_uses` | integer | Receipts the token may bind (default 1: the subscriber can read the token in the page, so mint one per checkout attempt; raise it for checkouts that legitimately submit several times). |
| `metadata` | object | Up to 20 key/value string pairs you can attach to the object. |

#### Returns

Returns a `ReceiptToken` 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/receipt_tokens \
  -u "$CHECKBOX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
  "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "customer_reference": "cus_Q2x9"
}'
```
```node
import Checkbox from '@checkbox-site/cli';

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

const receiptToken = await checkbox.receiptTokens.create({
  site: 'site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b',
  customer_reference: 'cus_Q2x9',
});
console.log(receiptToken);
```
```python
import os
from checkbox import Checkbox

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

receipt_token = checkbox.receipt_tokens.create(
    site="site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
    customer_reference="cus_Q2x9",
)
print(receipt_token)
```

```json title="Response"
{
  "id": "rtok_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "object": "receipt_token",
  "livemode": false,
  "created": 1793318400,
  "expires": 1793318400,
  "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "token": "rct_test_8Jq2k3…",
  "customer_reference": "cus_Q2x9",
  "checkout_session": null,
  "max_uses": 3,
  "html_attribute": "data-receipt-token=\"rct_test_8Jq2k3…\"",
  "metadata": {},
  "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/concepts/activation#verify"
  }
}
```

## List consent receipts {#listReceipts}

`GET /v1/receipts`

Receipts newest first. Filter by site, customer_reference (find the receipt behind a disputed subscription), checkout_session, email (hashed before the lookup), source, binding or creation time.

**Scope** `receipts:read` · **Operation** `listReceipts`

SDK: `checkbox.receipts.list()` (Node) · `checkbox.receipts.list()` (Python). MCP / agents: operation `listReceipts`.

#### Parameters

| Parameter | In | Type | Description |
|---|---|---|---|
| `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). |
| `site` | query | string | Only receipts of this site. |
| `customer_reference` | query | string | Only receipts for this subscriber reference. |
| `checkout_session` | query | string | Only receipts of this Stripe Checkout Session. |
| `email` | query | string | Only receipts whose email hash matches this address (hashed before the lookup). |
| `source` | query | enum | Only this source. One of `elements`, `snippet`, `api`, `stripe_checkout`, `stripe_payment_link`. |
| `binding` | query | enum | Only this binding. One of `bound`, `unbound`, `server`. |
| `created_gte` | query | integer \| null | Created at or after (unix seconds). |
| `created_lte` | query | integer \| null | Created at or before (unix seconds). |

#### Returns

Returns a list (`{ object: "list", data, has_more, url }`) of [Receipt](https://checkbox.site/docs/api/receipts#the-receipt-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/receipts \
  -u "$CHECKBOX_SECRET_KEY:" \
  -d limit=3
```
```node
import Checkbox from '@checkbox-site/cli';

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

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

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

receipts = checkbox.receipts.list(limit=3)
print(receipts.data)
```

```json title="Response"
{
  "object": "list",
  "data": [
    {
      "id": "rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
      "object": "receipt",
      "livemode": false,
      "created": 1793318400,
      "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
      "source": "snippet",
      "binding": "bound",
      "reconstructed": false,
      "evidentiary_weight": "direct_capture",
      "detection": {
        "method": "marked",
        "confidence": "high"
      },
      "customer_reference": "cus_Q2x9",
      "client_reference": null,
      "email_sha256": null,
      "checkout_session": null,
      "subscription": null,
      "consent": null,
      "pre_checkout_receipt": null,
      "page_url": "https://acme.example/checkout",
      "language": "en-US",
      "user_agent_class": "desktop-chrome",
      "viewport": {
        "width": 1280,
        "height": 800
      },
      "client_timestamp": 1793318399,
      "disclosure": {
        "text": "Your Acme subscription renews automatically every month at $19.99 until you cancel.",
        "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
        "presentation": {
          "font_size_px": 16,
          "font_weight": 400,
          "color": "rgb(17, 24, 39)",
          "background_color": "rgb(255, 255, 255)",
          "contrast_ratio": 17.74,
          "visible": true,
          "set_off": true,
          "above_fold": true,
          "above_submit": true,
          "distance_to_submit_px": 12
        }
      },
      "checkbox": {
        "present": true,
        "checked": true,
        "label": "I agree to the automatic renewal terms.",
        "required": true
      },
      "submit_label": "Start subscription",
      "stripe": {
        "checkout_session": "cs_live_a1b2c3",
        "payment_link": null,
        "session_created": 1793318100,
        "completed": 1793318400,
        "terms_of_service": "required",
        "terms_of_service_accepted": true,
        "custom_text": {
          "submit": "Renews monthly at $19.99 until you cancel.",
          "terms_of_service_acceptance": null,
          "after_submit": null
        },
        "amount": 1999,
        "currency": "usd",
        "interval": "month",
        "interval_count": 1,
        "trial_days": 14,
        "subscription": "sub_1Q2"
      },
      "flags": [],
      "integrity": {
        "receipt_sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
        "evidence": "ev_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
        "chain_hash": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
        "public_key_id": "1a2b3c4d5e6f7a8b",
        "signed": 1793318400
      },
      "retain_until": 1793318400,
      "render_url": "/v1/receipts/rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/render.png",
      "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/concepts/activation#verify"
      }
    }
  ],
  "has_more": false,
  "url": "/v1/sites"
}
```

## Record a consent receipt {#createReceipt}

`POST /v1/receipts`

Records one receipt from the structured snapshot the browser captured at submit (the /r.js snippet add-on calls this with the publishable key, Origin-checked; servers may call it with a secret key and site). The snapshot is validated strictly and re-sanitized. Next: retrieveReceipt / renderReceipt to see it, verifyReceipt to check its integrity.

**Key** publishable (`pk_…`) or secret · **Idempotent** (send `Idempotency-Key`) · **Operation** `createReceipt`

SDK: `checkbox.receipts.create()` (Node) · `checkbox.receipts.create()` (Python). MCP / agents: operation `createReceipt`.

#### Body

| Field | Type | Description |
|---|---|---|
| `site` | string | site_… the receipt belongs to. Optional with a publishable key (resolved from the page Origin); required with a secret key. |
| `snapshot` **required** | object | The structured snapshot captured by /r.js or <checkbox-disclosure> (version 1): the disclosure region's text, sanitized subtree and computed styles, the consent checkbox state and label, the submit label, layout, viewport, page URL without query string, language and user-agent class. Validated strictly and re-sanitized by the server; it never contains form-field values. |
| `token` | string | Capture token (rct_…) your server minted for this checkout with createReceiptToken. Binds the receipt to that checkout; without it the receipt is accepted but flagged "unbound". |
| `client_reference` | string | A reference the page claims for the subscriber (unverified; kept apart from customer_reference). |
| `capture_nonce` | string | Random nonce the snippet also put in a Payment Link's client_reference_id (ckbx_<nonce>), linking this pre-checkout snapshot to the Stripe record. |
| `customer_reference` | string | Secret keys only: your id for the subscriber (Stripe customer / subscription / Checkout Session id). Not an email address. |
| `checkout_session` | string | Secret keys only: the Stripe Checkout Session id this receipt belongs to. |
| `email` | string | Secret keys only: the subscriber's email address. Only an HMAC-SHA-256 of it (lowercased, trimmed; keyed per account) is stored. |

#### Returns

Returns a [Receipt](https://checkbox.site/docs/api/receipts#the-receipt-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/receipts \
  -u "$CHECKBOX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
  "snapshot": {
    "v": 1,
    "phase": "submit",
    "captured_at": 1793318400000,
    "page_url": "https://acme.example/checkout",
    "viewport": {
      "width": 1280,
      "height": 800
    },
    "scroll": {
      "x": 0,
      "y": 0
    },
    "language": "en-US",
    "user_agent_class": "desktop-chrome",
    "detection": {
      "method": "marked",
      "confidence": "high",
      "reasons": [
        "marked_by_merchant"
      ]
    },
    "region": {
      "text": "Your Acme subscription renews automatically every month at $19.99 until you cancel.",
      "tree": [
        {
          "t": "p",
          "s": {
            "font-size": "16px"
          },
          "c": [
            "Your Acme subscription renews automatically every month at $19.99 until you cancel."
          ]
        }
      ],
      "rect": {
        "x": 40,
        "y": 520,
        "width": 480,
        "height": 72
      },
      "style": {
        "font_size_px": 16,
        "font_weight": 400,
        "color": "rgb(17, 24, 39)",
        "background_color": "rgb(255, 255, 255)",
        "contrast_ratio": 17.74,
        "opacity": 1,
        "visible": true
      },
      "set_off": true,
      "above_fold": true,
      "above_submit": true,
      "distance_to_submit_px": 12,
      "masked_fields": 0
    },
    "checkbox": {
      "present": true,
      "checked": true,
      "label": "I agree to the automatic renewal terms.",
      "required": true
    },
    "submit": {
      "label": "Start subscription",
      "rect": {
        "x": 40,
        "y": 604,
        "width": 480,
        "height": 44
      },
      "kind": "button"
    },
    "render": null
  },
  "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b"
}'
```
```node
import Checkbox from '@checkbox-site/cli';

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

const receipt = await checkbox.receipts.create({
  snapshot: {
    v: 1,
    phase: 'submit',
    captured_at: 1793318400000,
    page_url: 'https://acme.example/checkout',
    viewport: { width: 1280, height: 800 },
    scroll: { x: 0, y: 0 },
    language: 'en-US',
    user_agent_class: 'desktop-chrome',
    detection: {
      method: 'marked',
      confidence: 'high',
      reasons: ['marked_by_merchant'],
    },
    region: {
      text: 'Your Acme subscription renews automatically every month at $19.99 until you cancel.',
      tree: [
        {
          t: 'p',
          s: { 'font-size': '16px' },
          c: [
            'Your Acme subscription renews automatically every month at $19.99 until you cancel.',
          ],
        },
      ],
      rect: { x: 40, y: 520, width: 480, height: 72 },
      style: {
        font_size_px: 16,
        font_weight: 400,
        color: 'rgb(17, 24, 39)',
        background_color: 'rgb(255, 255, 255)',
        contrast_ratio: 17.74,
        opacity: 1,
        visible: true,
      },
      set_off: true,
      above_fold: true,
      above_submit: true,
      distance_to_submit_px: 12,
      masked_fields: 0,
    },
    checkbox: {
      present: true,
      checked: true,
      label: 'I agree to the automatic renewal terms.',
      required: true,
    },
    submit: {
      label: 'Start subscription',
      rect: { x: 40, y: 604, width: 480, height: 44 },
      kind: 'button',
    },
    render: null,
  },
  site: 'site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b',
});
console.log(receipt);
```
```python
import os
from checkbox import Checkbox

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

receipt = checkbox.receipts.create(
    snapshot={
        "v": 1,
        "phase": "submit",
        "captured_at": 1793318400000,
        "page_url": "https://acme.example/checkout",
        "viewport": {"width": 1280, "height": 800},
        "scroll": {"x": 0, "y": 0},
        "language": "en-US",
        "user_agent_class": "desktop-chrome",
        "detection": {
            "method": "marked",
            "confidence": "high",
            "reasons": ["marked_by_merchant"],
        },
        "region": {
            "text": "Your Acme subscription renews automatically every month at $19.99 until you cancel.",
            "tree": [
                {
                    "t": "p",
                    "s": {"font-size": "16px"},
                    "c": [
                        "Your Acme subscription renews automatically every month at $19.99 until you cancel.",
                    ],
                },
            ],
            "rect": {"x": 40, "y": 520, "width": 480, "height": 72},
            "style": {
                "font_size_px": 16,
                "font_weight": 400,
                "color": "rgb(17, 24, 39)",
                "background_color": "rgb(255, 255, 255)",
                "contrast_ratio": 17.74,
                "opacity": 1,
                "visible": True,
            },
            "set_off": True,
            "above_fold": True,
            "above_submit": True,
            "distance_to_submit_px": 12,
            "masked_fields": 0,
        },
        "checkbox": {
            "present": True,
            "checked": True,
            "label": "I agree to the automatic renewal terms.",
            "required": True,
        },
        "submit": {
            "label": "Start subscription",
            "rect": {"x": 40, "y": 604, "width": 480, "height": 44},
            "kind": "button",
        },
        "render": None,
    },
    site="site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
)
print(receipt)
```

```json title="Response"
{
  "id": "rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "object": "receipt",
  "livemode": false,
  "created": 1793318400,
  "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "source": "snippet",
  "binding": "bound",
  "reconstructed": false,
  "evidentiary_weight": "direct_capture",
  "detection": {
    "method": "marked",
    "confidence": "high"
  },
  "customer_reference": "cus_Q2x9",
  "client_reference": null,
  "email_sha256": null,
  "checkout_session": null,
  "subscription": null,
  "consent": null,
  "pre_checkout_receipt": null,
  "page_url": "https://acme.example/checkout",
  "language": "en-US",
  "user_agent_class": "desktop-chrome",
  "viewport": {
    "width": 1280,
    "height": 800
  },
  "client_timestamp": 1793318399,
  "disclosure": {
    "text": "Your Acme subscription renews automatically every month at $19.99 until you cancel.",
    "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    "presentation": {
      "font_size_px": 16,
      "font_weight": 400,
      "color": "rgb(17, 24, 39)",
      "background_color": "rgb(255, 255, 255)",
      "contrast_ratio": 17.74,
      "visible": true,
      "set_off": true,
      "above_fold": true,
      "above_submit": true,
      "distance_to_submit_px": 12
    }
  },
  "checkbox": {
    "present": true,
    "checked": true,
    "label": "I agree to the automatic renewal terms.",
    "required": true
  },
  "submit_label": "Start subscription",
  "stripe": {
    "checkout_session": "cs_live_a1b2c3",
    "payment_link": null,
    "session_created": 1793318100,
    "completed": 1793318400,
    "terms_of_service": "required",
    "terms_of_service_accepted": true,
    "custom_text": {
      "submit": "Renews monthly at $19.99 until you cancel.",
      "terms_of_service_acceptance": null,
      "after_submit": null
    },
    "amount": 1999,
    "currency": "usd",
    "interval": "month",
    "interval_count": 1,
    "trial_days": 14,
    "subscription": "sub_1Q2"
  },
  "flags": [],
  "integrity": {
    "receipt_sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "evidence": "ev_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
    "chain_hash": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
    "public_key_id": "1a2b3c4d5e6f7a8b",
    "signed": 1793318400
  },
  "retain_until": 1793318400,
  "render_url": "/v1/receipts/rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/render.png",
  "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/concepts/activation#verify"
  }
}
```

## Erase a subscriber's receipts {#eraseReceipts}

`POST /v1/receipts/erase`

For a subscriber's deletion request: deletes every receipt that matches customer_reference, email (hashed before the lookup) or checkout_session (optionally only for one site). The signed copy in the write-once evidence vault is kept but holds only a salted commitment to the subscriber's identifiers; erasing destroys the salt, so it can no longer be tied to the person. Pending attorney review.

**Scope** `receipts:write` · **Idempotent** (send `Idempotency-Key`) · **Operation** `eraseReceipts`

SDK: `checkbox.receipts.erase()` (Node) · `checkbox.receipts.erase()` (Python). MCP / agents: operation `eraseReceipts`.

#### Body

| Field | Type | Description |
|---|---|---|
| `customer_reference` | string | Erase receipts with this customer_reference. |
| `email` | string | Erase receipts recorded with this email (hashed before the lookup). |
| `checkout_session` | string | Erase receipts of this Stripe Checkout Session. |
| `site` | string | Only this site_…. |

#### Returns

Returns a `ReceiptErasure` 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/receipts/erase \
  -u "$CHECKBOX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
  "customer_reference": "cus_Q2x9"
}'
```
```node
import Checkbox from '@checkbox-site/cli';

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

const receipt = await checkbox.receipts.erase({ customer_reference: 'cus_Q2x9' });
console.log(receipt);
```
```python
import os
from checkbox import Checkbox

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

receipt = checkbox.receipts.erase(customer_reference="cus_Q2x9")
print(receipt)
```

```json title="Response"
{
  "object": "receipt_erasure",
  "livemode": false,
  "erased": 2
}
```

## Retrieve a consent receipt {#retrieveReceipt}

`GET /v1/receipts/{receipt}`

What one subscriber saw and agreed to at checkout: disclosure text and presentation, checkbox state and label, button label, page, time, binding, flags and integrity data. renderReceipt shows it as an image; exportReceipt downloads a self-verifying bundle.

**Scope** `receipts:read` · **Operation** `retrieveReceipt`

SDK: `checkbox.receipts.retrieve()` (Node) · `checkbox.receipts.retrieve()` (Python). MCP / agents: operation `retrieveReceipt`.

#### Parameters

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

#### Returns

Returns a [Receipt](https://checkbox.site/docs/api/receipts#the-receipt-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/receipts/rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b \
  -u "$CHECKBOX_SECRET_KEY:"
```
```node
import Checkbox from '@checkbox-site/cli';

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

const receipt = await checkbox.receipts.retrieve('rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(receipt);
```
```python
import os
from checkbox import Checkbox

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

receipt = checkbox.receipts.retrieve("rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(receipt)
```

```json title="Response"
{
  "id": "rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "object": "receipt",
  "livemode": false,
  "created": 1793318400,
  "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "source": "snippet",
  "binding": "bound",
  "reconstructed": false,
  "evidentiary_weight": "direct_capture",
  "detection": {
    "method": "marked",
    "confidence": "high"
  },
  "customer_reference": "cus_Q2x9",
  "client_reference": null,
  "email_sha256": null,
  "checkout_session": null,
  "subscription": null,
  "consent": null,
  "pre_checkout_receipt": null,
  "page_url": "https://acme.example/checkout",
  "language": "en-US",
  "user_agent_class": "desktop-chrome",
  "viewport": {
    "width": 1280,
    "height": 800
  },
  "client_timestamp": 1793318399,
  "disclosure": {
    "text": "Your Acme subscription renews automatically every month at $19.99 until you cancel.",
    "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    "presentation": {
      "font_size_px": 16,
      "font_weight": 400,
      "color": "rgb(17, 24, 39)",
      "background_color": "rgb(255, 255, 255)",
      "contrast_ratio": 17.74,
      "visible": true,
      "set_off": true,
      "above_fold": true,
      "above_submit": true,
      "distance_to_submit_px": 12
    }
  },
  "checkbox": {
    "present": true,
    "checked": true,
    "label": "I agree to the automatic renewal terms.",
    "required": true
  },
  "submit_label": "Start subscription",
  "stripe": {
    "checkout_session": "cs_live_a1b2c3",
    "payment_link": null,
    "session_created": 1793318100,
    "completed": 1793318400,
    "terms_of_service": "required",
    "terms_of_service_accepted": true,
    "custom_text": {
      "submit": "Renews monthly at $19.99 until you cancel.",
      "terms_of_service_acceptance": null,
      "after_submit": null
    },
    "amount": 1999,
    "currency": "usd",
    "interval": "month",
    "interval_count": 1,
    "trial_days": 14,
    "subscription": "sub_1Q2"
  },
  "flags": [],
  "integrity": {
    "receipt_sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "evidence": "ev_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
    "chain_hash": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
    "public_key_id": "1a2b3c4d5e6f7a8b",
    "signed": 1793318400
  },
  "retain_until": 1793318400,
  "render_url": "/v1/receipts/rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/render.png",
  "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/concepts/activation#verify"
  }
}
```

## Render a receipt as an image {#renderReceipt}

`GET /v1/receipts/{receipt}/render.png`

A PNG of "what the subscriber saw", rendered on demand in an isolated Chromium (no network, scripts disabled) from the sanitized snapshot — a reconstruction, labeled as such, not a screenshot taken from the browser.

**Scope** `receipts:read` · **Operation** `renderReceipt`

SDK: `checkbox.receipts.render()` (Node) · `checkbox.receipts.render()` (Python). MCP / agents: operation `renderReceipt`.

#### Parameters

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

#### Returns

Returns the file (`image/png`).

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/receipts/rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/render.png \
  -u "$CHECKBOX_SECRET_KEY:"
```
```node
import Checkbox from '@checkbox-site/cli';

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

const file = await checkbox.receipts.render('rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(file);
```
```python
import os
from checkbox import Checkbox

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

receipt = checkbox.receipts.render("rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(receipt)
```

## Verify a receipt's integrity {#verifyReceipt}

`GET /v1/receipts/{receipt}/verify`

Re-hashes the stored receipt, checks Checkbox's Ed25519 signature over its hash and chain position, and verifies the site's evidence chain. For a check that does not rely on Checkbox, download exportReceipt and run its verify.mjs.

**Scope** `receipts:read` · **Operation** `verifyReceipt`

SDK: `checkbox.receipts.verify()` (Node) · `checkbox.receipts.verify()` (Python). MCP / agents: operation `verifyReceipt`.

#### Parameters

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

#### Returns

Returns a `ReceiptVerification` 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/receipts/rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/verify \
  -u "$CHECKBOX_SECRET_KEY:"
```
```node
import Checkbox from '@checkbox-site/cli';

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

const receipt = await checkbox.receipts.verify('rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(receipt);
```
```python
import os
from checkbox import Checkbox

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

receipt = checkbox.receipts.verify("rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(receipt)
```

```json title="Response"
{
  "object": "receipt_verification",
  "receipt": "rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "verified": true,
  "checks": {
    "content": {
      "ok": true,
      "message": "sha256 matches"
    },
    "signature": {
      "ok": true,
      "message": "signed by 1a2b3c4d5e6f7a8b"
    },
    "chain": {
      "ok": true,
      "message": "42 records checked"
    },
    "index": {
      "ok": true,
      "message": "the receipt as shown matches the signed document",
      "linked_after_signing": []
    }
  },
  "receipt_sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "verified_at": 1793318400,
  "offline": "GET /v1/receipts/rcpt_…/export, unzip, node verify.mjs --keys checkbox-keys.json"
}
```

## Download a self-verifying receipt bundle {#exportReceipt}

`GET /v1/receipts/{receipt}/export`

ZIP for a lawyer, insurer or court expert: manifest.json (the receipt's evidence record with its chain links) + manifest.sig.json, the canonical receipt JSON under records/, receipt.sig.json (Checkbox's signature over the receipt hash), receipt.png and render.html (the reconstruction), README.md and verify.mjs (zero-dependency offline verifier: node verify.mjs --keys checkbox-keys.json).

**Scope** `receipts:read` · **Operation** `exportReceipt`

SDK: `checkbox.receipts.export()` (Node) · `checkbox.receipts.export()` (Python). MCP / agents: operation `exportReceipt`.

#### Parameters

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

#### Returns

Returns the file (`application/zip`).

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/receipts/rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/export \
  -u "$CHECKBOX_SECRET_KEY:"
```
```node
import Checkbox from '@checkbox-site/cli';

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

const file = await checkbox.receipts.export('rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(file);
```
```python
import os
from checkbox import Checkbox

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

receipt = checkbox.receipts.export("rcpt_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(receipt)
```

## Retrieve receipt settings {#retrieveReceiptSettings}

`GET /v1/receipt_settings`

Whether consent receipts are collected for this account (in this mode) and how long they are kept.

**Scope** `receipts:read` · **Operation** `retrieveReceiptSettings`

SDK: `checkbox.receiptSettings.retrieve()` (Node) · `checkbox.receipt_settings.retrieve()` (Python). MCP / agents: operation `retrieveReceiptSettings`.

#### Returns

Returns a `ReceiptSettings` 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/receipt_settings \
  -u "$CHECKBOX_SECRET_KEY:"
```
```node
import Checkbox from '@checkbox-site/cli';

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

const receiptSetting = await checkbox.receiptSettings.retrieve();
console.log(receiptSetting);
```
```python
import os
from checkbox import Checkbox

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

receipt_setting = checkbox.receipt_settings.retrieve()
print(receipt_setting)
```

```json title="Response"
{
  "object": "receipt_settings",
  "livemode": false,
  "enabled": true,
  "retention_days": 1461,
  "updated": 1793318400
}
```

## Update receipt settings {#updateReceiptSettings}

`POST /v1/receipt_settings`

Turn receipts on or off and set the retention in days (default 1461 = 4 years, the California UCL limitation period; pending attorney review). A new retention applies to receipts recorded afterwards.

**Scope** `receipts:write` · **Idempotent** (send `Idempotency-Key`) · **Operation** `updateReceiptSettings`

SDK: `checkbox.receiptSettings.update()` (Node) · `checkbox.receipt_settings.update()` (Python). MCP / agents: operation `updateReceiptSettings`.

#### Body

| Field | Type | Description |
|---|---|---|
| `enabled` | boolean | Turn receipt collection on or off. |
| `retention_days` | integer | Days to keep receipts (30–3653). Applies to new receipts. |

#### Returns

Returns a `ReceiptSettings` 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/receipt_settings \
  -u "$CHECKBOX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
  "retention_days": 1461
}'
```
```node
import Checkbox from '@checkbox-site/cli';

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

const receiptSetting = await checkbox.receiptSettings.update({ retention_days: 1461 });
console.log(receiptSetting);
```
```python
import os
from checkbox import Checkbox

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

receipt_setting = checkbox.receipt_settings.update(retention_days=1461)
print(receipt_setting)
```

```json title="Response"
{
  "object": "receipt_settings",
  "livemode": false,
  "enabled": true,
  "retention_days": 1461,
  "updated": 1793318400
}
```
