# The agent loop

> How a coding agent connects a project with one human click, learns the business, and works a compliance plan to done — with the API calls behind each step.

The builder says *"Add Checkbox"*. From there, Checkbox and the builder's agent talk to each other; the human clicks
**Approve** once and reviews the result. This page shows each step and the API behind it, so you can drive it from
the CLI, the [MCP server](https://checkbox.site/docs/mcp) or your own code.

```text
agent  → MCP setup_checkbox  (or the CLI: checkbox init --yes --json)
agent  → POST /v1/device/authorize                   → a link with a code, e.g. BKRF-QXTP
human  → opens the link, signs in (email link, GitHub or Google), clicks Approve
agent  → POST /v1/device/token (polls)               → ck_test_ + pk_test_ keys, setup session, site
agent  → saves the key, adds the snippet to the root layout, reports the business URLs
Checkbox → fetches Terms / refund / cancellation pages, runs the first checks, (with permission) a test subscription
agent  → get_compliance_plan → apply task → verify_task → … until next_action.type is "done"
human  ← email: "Claude Code set up Checkbox for yoursite.com" with a review link (when the setup is finished)
```

## 1. Connect: one call, one click {#connect}

With the [MCP server](https://checkbox.site/docs/mcp), call `setup_checkbox` with what you know from the code; with plain HTTP, call
`POST /v1/device/authorize` and poll `POST /v1/device/token`. With the CLI:

```bash
npx @checkbox-site/cli init --yes --json --checkout-url https://joybox.example/pricing --merchant-name Joybox --plan 19.99/month,trial=7
```

> **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.

- The command prints exactly one JSON document on stdout. The approval link and progress go to stderr: show the link
  to the human. New accounts are free and need no card.
- If the human has not approved yet, the result is `status: "awaiting_approval"` with
  `next_action.command: "checkbox init --yes --json"`. Run it again; it resumes the same login.
- After approval the CLI writes `CHECKBOX_SECRET_KEY` and the publishable key to `.env.local` (and git-ignores it),
  installs the snippet in the root layout (between `checkbox:snippet` markers), and registers your site and policy
  pages.

What the CLI sends to Checkbox: framework, file paths and the matching lines of checkout code, URLs. Never your source
code or `.env` values. A Stripe **test** key is used only after the human ticks the box for it on the approval page,
and live keys are refused.

No shell? The MCP tool `setup_checkbox` does the same login: it returns the link for the human, then the keys.

## 2. Teach Checkbox the business {#setup-session}

The setup session is the checklist Checkbox keeps for the project. It always returns one `next_action`. An agent that
already has a key (for example from the dashboard) can start one directly:

```curl
curl https://app.checkbox.site/v1/setup_sessions \
  -u "$CHECKBOX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
    "site_url": "https://joybox.example",
    "checkout_url": "https://joybox.example/pricing",
    "terms_url": "https://joybox.example/terms",
    "billing_platform": "stripe",
    "plans": [{ "name": "Pro", "amount": 1999, "currency": "usd", "interval": "month", "trial_days": 14 }]
  }'
```
```node
import Checkbox from '@checkbox-site/cli';

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

const session = await checkbox.setupSessions.create({
  site_url: 'https://joybox.example',
  checkout_url: 'https://joybox.example/pricing',
  terms_url: 'https://joybox.example/terms',
  billing_platform: 'stripe',
  plans: [{ name: 'Pro', amount: 1999, currency: 'usd', interval: 'month', trial_days: 14 }],
});
console.log(session.steps.map((s) => `${s.key}: ${s.status}`));
console.log(session.next_action);
```
```python
import os
from checkbox import Checkbox

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

session = checkbox.setup_sessions.create(
    site_url="https://joybox.example",
    checkout_url="https://joybox.example/pricing",
    terms_url="https://joybox.example/terms",
    billing_platform="stripe",
    plans=[{"name": "Pro", "amount": 1999, "currency": "usd", "interval": "month", "trial_days": 14}],
)
print([(s["key"], s["status"]) for s in session["steps"]])
print(session["next_action"])
```

Steps are `account, site, snippet, policies, checks, fixes, lifecycle_test, domain, owner_notified`, each `done`,
`pending`, `action_required`, `skipped` or `failed`. A typical `next_action`:

```json
{
  "type": "install_snippet",
  "message": "Add <script async src=\"https://app.checkbox.site/s.js\" data-key=\"…\"></script> to the <head> of every page (root layout), then report snippet_installed_in with updateSetupSession.",
  "operation_id": "updateSetupSession",
  "command": "checkbox init --yes --json"
}
```

Report what you did with `updateSetupSession` (`snippet_installed_in`, `refund_url`, `plans`…). After deploying, call
`verifySetupSession` (`checkbox init --verify --yes --json`): Checkbox loads the site, confirms the snippet is live,
re-fetches the policies, re-evaluates and queues the first scan. `completeSetupSession` sends the human the summary
email. Policy pages are fetched and checked against the law too: a Terms of Service that says "no cancellations" or a
refund policy that takes back a money-back promise on your pricing page shows up as a task.

## 3. See every lifecycle step: the test subscription {#test-run}

With a Stripe **test-mode** key, Checkbox runs a synthetic subscription like a CMS preview: it subscribes a test
customer with a test card, receives your acknowledgment and reminder emails at its own inbox, moves a Stripe test clock
through the trial end and the first renewal, and cancels online. Every lifecycle check is observed, not inferred.
Nothing is charged. See [Test subscriptions](https://checkbox.site/docs/guides/test-runs).

## 4. Work the compliance plan {#plan}

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

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

const plan = await checkbox.plans.retrieve('site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(plan.status, plan.summary);
console.log(plan.next_action);
```
```python
import os
from checkbox import Checkbox

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

plan = checkbox.plans.retrieve("site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(plan["status"], plan["summary"])
print(plan["next_action"])
```

The plan is an ordered list of tasks. Each task carries:

| Field | What it holds |
|---|---|
| `requirement` | The law: jurisdictions, `citation`, a plain-language statement |
| `why` | What Checkbox observed (and where) |
| `where` | `file_hint`, `url`, `selector`, or the Stripe object to change |
| `change` | `type` (`patch`, `copy`, `stripe_params`, `config`, `manual`), the exact `content` (a diff template, the wording, the Stripe params) and machine-applicable `edits` |
| `verify` | How Checkbox will confirm it (`page_fetch`, `stripe_params`, `policy_refetch`, `email_copy`, `platform_sync`, `evaluation`) |
| `next_action` | What to call after applying it |

Plan `status`: `action_required` (an agent can apply open tasks), `verifying` (only confirmations pending), `blocked`
(only human answers remain, or a critical/high task was declined), `compliant`. Before the first evaluation the plan's
`next_action` is `run_first_check`: call `verifySite`.

## 5. Verify, and repeat {#verify}

After applying a task, ask Checkbox to check it. Send what changed — the rendered HTML before you deploy, the new
Stripe params, or the new email copy — and Checkbox runs the fastest re-check now and queues the authoritative one
(a scan, a billing sync, the next real email):

```bash
checkbox verify task_8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f --wait --json
```

To re-check the whole site (for example after a deploy):

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

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

const verification = await checkbox.sites.verify('site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(verification.status, verification.next_action);
```
```python
import os
from checkbox import Checkbox

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

verification = checkbox.sites.verify("site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(verification["status"], verification["next_action"])
```

A verification is `passed`, `failed` (with what is still wrong) or `pending`; poll
`GET /v1/verifications/{id}?wait=30` or subscribe to the `task.verified` [webhook](https://checkbox.site/docs/webhooks). Fetch the plan
again and continue until `status` is `compliant` — the `site.compliant` event fires once when it gets there.

### Questions only a human can answer {#manual-tasks}

Some requirements cannot be observed: for example, New York requires reminders "in the manner the consumer selected".
Those become `manual` tasks with a `question`. The agent asks the human and records the answer with `answerTask`; it
never answers on the human's behalf. Manual tasks never block the others, and the plan summary says how many items rest
on a human confirmation rather than an observation.

## `next_action` {#next-action}

Every agent-facing object and every error carries `next_action`: `type` (stable), a short `message` (setup, account,
checks) or `description` (plans, verifications) for the model, and where useful `operation_id` (the API operation and
SDK method to call), `endpoint`, `command` (the CLI equivalent) or `url`. Follow it; do not guess.
