# Preflight checks

> Stateless "check before you ship" calls for AI agents and CI — disclosure text, Stripe params, lifecycle emails, checkout HTML and notice timing — with verdicts per legal requirement and the exact fix.

Preflight checks judge what your code will produce, before anything is deployed. Nothing is stored, no site is needed,
and test keys work. Send the text, params, email or HTML; get a verdict per check and per legal requirement, with
citations, and a fix the engine has already verified.

| Check | Endpoint | CLI | Offline |
|---|---|---|---|
| Disclosure text, placement, consent checkbox | [`POST /v1/checks/disclosure`](https://checkbox.site/docs/api/checks#checkDisclosure) | `checkbox check disclosure` | yes |
| Stripe Checkout Session params | [`POST /v1/checks/stripe_checkout_session`](https://checkbox.site/docs/api/checks#checkStripeCheckoutSession) | `checkbox check stripe-session` | yes |
| Stripe Payment Link params | [`POST /v1/checks/stripe_payment_link`](https://checkbox.site/docs/api/checks#checkStripePaymentLink) | `checkbox check payment-link` | yes |
| A lifecycle email | [`POST /v1/checks/email`](https://checkbox.site/docs/api/checks#checkEmail) | `checkbox check email` | yes |
| Checkout HTML (or a live URL) | [`POST /v1/checks/page`](https://checkbox.site/docs/api/checks#checkPage) | `checkbox check page` | `--file` only |
| When each notice must be sent | [`POST /v1/checks/notice_schedule`](https://checkbox.site/docs/api/checks#checkNoticeSchedule) | `checkbox check notice-schedule` | no |

`--offline` runs the same rules engine locally: no key, no network, the same JSON plus `"mode": "offline"`.

## Example {#example}

```bash
checkbox check disclosure \
  --text "Renews monthly at \$19.99 until you cancel." \
  --merchant-name Joybox --price 19.99 --interval month \
  --consent-checkbox --offline
```

The same call over HTTP returns JSON like this (trimmed):

```json
{
  "object": "check_result",
  "kind": "disclosure",
  "verdict": "fail",
  "checks": [
    {
      "check_id": "signup.disclosure.cancellation_policy",
      "status": "fail",
      "severity": "critical",
      "fix": "Explain the cancellation policy and exactly how to cancel in the checkout terms.",
      "requirements": [
        { "id": "US-FED.8403.1", "jurisdiction": "US-FED", "citation": "15 U.S.C. § 8403(1)" },
        { "id": "US-CA.17602.a.1", "jurisdiction": "US-CA", "citation": "Cal. Bus. & Prof. Code § 17602(a)(1); § 17601(a)(2)-(3)" }
      ]
    }
  ],
  "replacement": {
    "disclosure_text": "Your Joybox subscription automatically renews every month at $19.99 (plus applicable taxes) until you cancel. You can cancel anytime online at {{cancel_url}}; cancellation takes effect at the end of your current billing period. To avoid the next charge, cancel before your next billing date (every month).",
    "placeholders": ["{{cancel_url}}"],
    "verified": true
  },
  "rules_version": "1.3.0",
  "catalog_version": "2026.10.31.1",
  "disclaimer": "Automated compliance check against Checkbox’s catalog of automatic-renewal laws. It is not legal advice…",
  "next_action": {
    "type": "replace_text",
    "message": "Failing: signup.disclosure.cancellation_policy (fail), signup.disclosure.conspicuous (needs_review). Replace the disclosure with replacement.disclosure_text, show it directly above the purchase button… then call this check again."
  }
}
```

`next_action.type` is one of `none`, `replace_text`, `apply_patch`, `replace_template`, `fix_and_recheck`,
`human_review`, `provide_input`, `schedule_notices`. Loop until the verdict is `pass`.

## In CI {#ci}

Exit codes are stable: `0` pass (or only `needs_review` without `--strict`), `1` a check failed, `2` needs review with
`--strict`, `3` usage error, `4` not logged in or key rejected, `5` API or network error. A GitHub Actions step:

```yaml title=".github/workflows/compliance.yml (excerpt)"
- name: Checkbox preflight
  env:
    CHECKBOX_API_KEY: ${{ secrets.CHECKBOX_API_KEY }}
  run: npx @checkbox-site/cli check stripe-session --file checkout/session-params.json --merchant-name Joybox --cancel-url https://joybox.example/account/cancel
```

Give CI a restricted key with only `checks: read`.

> **Pre-release packages**
>
> The SDKs, the CLI and the local MCP server are not on npm or PyPI yet, and the package names shown here are
> placeholders (the CLI commands below run from a checkout of the repository today). Until this notice is gone, use the
> HTTP API (curl) or the remote MCP server at `https://app.checkbox.site/mcp`, which are live today. Never install an npm
> package named `checkbox`: it belongs to an unrelated project.

## Determinism and limits {#limits}

- Every result carries `rules_version`, `catalog_version`, `catalog_hash`, `as_of` and a disclaimer. The same input and
  `as_of` date give the same verdicts.
- On any Stripe-hosted page, *clear and conspicuous* (`signup.disclosure.conspicuous`) stays `needs_review`: Stripe
  controls the styling.
- The page check reads static HTML; buttons rendered by JavaScript are not found, so send the rendered HTML. Layout is
  estimated, and a passing estimate is reported as `needs_review`, never `pass`; a scan confirms it.
- Disclosure and Stripe checks model a desktop page; mobile layouts are judged by scans.
- Email checks judge content; real send timing is judged by monitoring.
- Rate limit: 120 checks per minute per account (`page` with `url`: 20 per minute).
