# Suggestions API

> Compliant conversion suggestions: optional ways to make the checkout, emails and cancellation flow perform better without breaking automatic-renewal laws.

| Operation | Endpoint |
|---|---|
| [Get conversion suggestions for a site](#listSiteSuggestions) | `GET /v1/sites/{site}/suggestions` |
| [Get conversion suggestions for copy you send](#suggestImprovements) | `POST /v1/suggestions` |

## The SuggestionReport object

Optional, compliant conversion suggestions for a checkout, Stripe Checkout, lifecycle emails and the cancellation flow. Never a compliance task.

| Field | Type | Description |
|---|---|---|
| `id` | string | Id of this report (sugr_…). Reports are computed on request and not stored. |
| `object` | enum | String representing the object's type. Always "suggestion_report". |
| `livemode` | boolean | true for live-mode objects, false for test mode (ck_test_ keys act on an isolated sandbox). |
| `created` | integer | When it was computed. Unix seconds. |
| `site` | string \| null | The site (GET /v1/sites/:site/suggestions), or null for POST /v1/suggestions. |
| `optional` | enum | Always true: suggestions are optional and never block anything. |
| `counts_toward_compliance` | enum | Always false: suggestions never count for or against compliance, the score or the plan status. |
| `summary` | string | One paragraph for an LLM or a human. |
| `jurisdictions` | enum | Jurisdictions the examples were checked for. |
| `as_of` | string | Date the law was evaluated at. |
| `library_version` | string | Version of the suggestion library. |
| `rules_version` | string | Rules engine version. |
| `catalog_version` | string | Legal catalog version. |
| `surfaces` | array of objects | Per surface. |
| `suggestions` | array of Suggestions | Offered suggestions: detected gaps in your own copy first. |
| `withheld` | array of objects | Suggestions that would apply but are withheld, and why. |
| `next_action` | object | Next step. |
| `next_action.type` | enum | What to do next. One of `apply_suggestion`, `fix_compliance_first`, `none`. |
| `next_action.message` | string | One or two sentences for an agent. |
| `next_action.operation_id` | string | The operation to call next, when there is one. |
| `next_action.mcp_tool` | string | The MCP tool for operation_id. |
| `disclaimer` | string | Not legal advice; optional; results vary. |

## Get conversion suggestions for a site {#listSiteSuggestions}

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

Suggestions for a site, detected from what Checkbox observed (checkout captures, emails, the test cancellation, billing settings, registered Stripe Checkout params) and its latest evaluation. Optional, deterministic suggestions (no LLM) from Checkbox’s curated library: each has the surface, what to change, why it commonly helps (no invented figures), a concrete before/after built from your plan terms, the exact copy / Stripe patch / flow change to apply, the legal guardrails it respects (catalog requirement ids, e.g. California’s and Minnesota’s save-offer rules), and the engine check of the after-example (only verified suggestions are offered). Surfaces whose compliance checks fail are held until fixed. Suggestions never block and never count toward compliance.

**Scope** `checks:read` · **Operation** `listSiteSuggestions`

SDK: `checkbox.suggestions.forSite()` (Node) · `checkbox.suggestions.for_site()` (Python). MCP / agents: operation `listSiteSuggestions`.

#### Parameters

| Parameter | In | Type | Description |
|---|---|---|---|
| `site` **required** | path | string | Unique identifier of the site (site_…). |
| `surfaces` | query | string | Comma-separated surfaces to include (default all). |

#### Returns

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

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

const suggestion = await checkbox.suggestions.forSite('site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b');
console.log(suggestion);
```
```python
import os
from checkbox import Checkbox

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

suggestion = checkbox.suggestions.for_site("site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b")
print(suggestion)
```

```json title="Response"
{
  "id": "sugr_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "object": "suggestion_report",
  "livemode": false,
  "created": 1793318400,
  "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "optional": true,
  "counts_toward_compliance": false,
  "summary": "6 optional suggestions (4 from your own copy, 2 recommended) across 3 surface(s).",
  "jurisdictions": [
    "US-CA",
    "US-NY"
  ],
  "as_of": "2026-11-02",
  "library_version": "2026.11.02.2",
  "rules_version": "1.4.0",
  "catalog_version": "2026.11.01.1",
  "surfaces": [
    {
      "surface": "checkout_page",
      "status": "offered",
      "observed": true,
      "held_by": [],
      "held_label": null,
      "offered": 3
    }
  ],
  "suggestions": [
    {
      "key": "checkout.trial_button",
      "object": "suggestion",
      "surface": "checkout_page",
      "title": "Name the trial on the button: “Start my free trial”",
      "why": "A button that says exactly what happens next is clearer than “Subscribe”.",
      "match": "detected",
      "reason": "Your button says “Subscribe”, which does not mention the trial.",
      "before": {
        "label": "Button",
        "text": "Subscribe"
      },
      "after": {
        "label": "Button",
        "text": "Start my 7-day free trial"
      },
      "apply": {
        "type": "replace_text",
        "where": "The purchase button label.",
        "content": "Start my 7-day free trial",
        "json_patch": [
          {
            "op": "add",
            "path": "/payment_method_collection",
            "value": "if_required"
          }
        ],
        "instructions": "Replace the purchase button label. Keep the renewal terms and the unchecked consent checkbox above it unchanged."
      },
      "preconditions": [
        "Nothing is charged during the trial."
      ],
      "guardrails": [
        {
          "rule": "Show any offer next to a prominent “Cancel subscription” button that stays visible the whole time; one click on it cancels.",
          "requirement_ids": [
            "US-CA.17602.e"
          ]
        }
      ],
      "verification": {
        "operation_id": "checkDisclosure",
        "verdict": "pass",
        "verified": true,
        "open_checks": [],
        "compliance_status": "compliant",
        "compliance_label": "Compliant",
        "jurisdictions": "all",
        "note": "The compliant baseline with this change applied was run through checkDisclosure."
      },
      "placeholders": []
    }
  ],
  "withheld": [
    {
      "key": "checkout.annual_equivalent",
      "surface": "checkout_page",
      "title": "Show the per-month figure next to the yearly charge",
      "reason": "Your yearly plan starts with a free trial: next to a trial, a per-month figure reads as the charge after the trial."
    }
  ],
  "next_action": {
    "type": "apply_suggestion",
    "message": "Optional. Pick a suggestion, apply suggestion.apply.content exactly where apply.where says…",
    "operation_id": "retrieveCompliancePlan",
    "mcp_tool": "get_compliance_plan"
  },
  "disclaimer": "Optional suggestions to improve conversion and retention…"
}
```

## Get conversion suggestions for copy you send {#suggestImprovements}

`POST /v1/suggestions`

Stateless: send any of the inputs of /v1/checks/* (disclosure, page html, Stripe Checkout params, emails) plus a cancellation flow and your plan terms; get suggestions for them. The current status of what you send comes from the same preflight checks; nothing is stored. Optional, deterministic suggestions (no LLM) from Checkbox’s curated library: each has the surface, what to change, why it commonly helps (no invented figures), a concrete before/after built from your plan terms, the exact copy / Stripe patch / flow change to apply, the legal guardrails it respects (catalog requirement ids, e.g. California’s and Minnesota’s save-offer rules), and the engine check of the after-example (only verified suggestions are offered). Surfaces whose compliance checks fail are held until fixed. Suggestions never block and never count toward compliance.

**Scope** `checks:read` · **Operation** `suggestImprovements`

SDK: `checkbox.suggestions.generate()` (Node) · `checkbox.suggestions.generate()` (Python). MCP / agents: operation `suggestImprovements`.

#### Body

| Field | Type | Description |
|---|---|---|
| `terms` | CheckPlanTerms | What Stripe params do not say: merchant_name, cancel_url, and price / interval when line_items reference a Price id. Also accepted as `terms`. |
| `terms.merchant_name` | string | Brand shown to subscribers; used in generated text. |
| `terms.plan_name` | string | Plan / product name. |
| `terms.price` | number | Recurring price in major units (19.99). Or give `amount` in minor units. |
| `terms.amount` | integer | Recurring price in minor units (1999 = $19.99), Stripe style. |
| `terms.currency` | string | Three-letter ISO currency (default usd). |
| `terms.interval` | string | Billing interval: day, week, month or year. |
| `terms.interval_count` | integer | Number of intervals between charges (default 1). |
| `terms.trial_days` | integer | Free-trial length in days (0 or absent = no trial). |
| `terms.cancel_url` | string | https URL where subscribers cancel online (used in generated text; a {{cancel_url}} placeholder otherwise). |
| `terms.cancel_timing` | enum | When a cancellation takes effect (default period_end). One of `period_end`, `immediately`. |
| `other_plans` | array of CheckPlanTerms | Other plans you sell (enables “point to a cheaper plan” and plan-choice suggestions). |
| `jurisdictions` | enum | Where you sell: "all" (default) or state codes ("US-CA", "CA", "US-NY"; "US-FED" is always included). |
| `as_of` | string | Evaluate the law as of this date (YYYY-MM-DD, default today). |
| `surfaces` | array of enum | Only suggest for these surfaces (default: all). |
| `disclosure` | object | Your own checkout: the same fields as checkDisclosure (without the plan terms, which go in terms). |
| `disclosure.text` **required** | string | The disclosure shown next to the purchase button, exactly as rendered. |
| `disclosure.button_text` | string | Label of the purchase button. |
| `disclosure.order_summary` | string | Other text next to the button (order summary). |
| `disclosure.placement` | object | Where and how the disclosure is shown (as in checkDisclosure). |
| `disclosure.consent` | object | The consent checkbox (as in checkDisclosure). |
| `page` | object | Your checkout page HTML (as in checkPage; html only). |
| `page.html` **required** | string | Rendered checkout page HTML (as in checkPage). |
| `page.url` | string | Where the page lives (resolves relative links). |
| `stripe_checkout_session` | object | Stripe Checkout Session params (as in checkStripeCheckoutSession; plan terms in terms). |
| `stripe_checkout_session.params` **required** | object | Exactly what your code passes to stripe.checkout.sessions.create. |
| `emails` | array of objects | Lifecycle emails (as in checkEmail; plan terms in terms). |
| `cancellation_flow` | object | Your cancellation flow as a subscriber sees it. |
| `cancellation_flow.steps` **required** | array of objects | Screens from the account page to the confirmation, in order. |
| `cancellation_flow.confirmation_shown` | boolean | A confirmation is shown at the end (default true). |
| `trial_reminders` | boolean | You send a trial-ending reminder to every trial subscriber (false withholds suggestions that promise one). |
| `site_url` | string | Your site’s base URL (used in example links). |

#### Returns

Returns a [SuggestionReport](https://checkbox.site/docs/api/suggestions#the-suggestionreport-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/suggestions \
  -u "$CHECKBOX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -d '{}'
```
```node
import Checkbox from '@checkbox-site/node';

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

const suggestion = await checkbox.suggestions.generate();
console.log(suggestion);
```
```python
import os
from checkbox import Checkbox

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

suggestion = checkbox.suggestions.generate()
print(suggestion)
```

```json title="Response"
{
  "id": "sugr_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "object": "suggestion_report",
  "livemode": false,
  "created": 1793318400,
  "site": "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
  "optional": true,
  "counts_toward_compliance": false,
  "summary": "6 optional suggestions (4 from your own copy, 2 recommended) across 3 surface(s).",
  "jurisdictions": [
    "US-CA",
    "US-NY"
  ],
  "as_of": "2026-11-02",
  "library_version": "2026.11.02.2",
  "rules_version": "1.4.0",
  "catalog_version": "2026.11.01.1",
  "surfaces": [
    {
      "surface": "checkout_page",
      "status": "offered",
      "observed": true,
      "held_by": [],
      "held_label": null,
      "offered": 3
    }
  ],
  "suggestions": [
    {
      "key": "checkout.trial_button",
      "object": "suggestion",
      "surface": "checkout_page",
      "title": "Name the trial on the button: “Start my free trial”",
      "why": "A button that says exactly what happens next is clearer than “Subscribe”.",
      "match": "detected",
      "reason": "Your button says “Subscribe”, which does not mention the trial.",
      "before": {
        "label": "Button",
        "text": "Subscribe"
      },
      "after": {
        "label": "Button",
        "text": "Start my 7-day free trial"
      },
      "apply": {
        "type": "replace_text",
        "where": "The purchase button label.",
        "content": "Start my 7-day free trial",
        "json_patch": [
          {
            "op": "add",
            "path": "/payment_method_collection",
            "value": "if_required"
          }
        ],
        "instructions": "Replace the purchase button label. Keep the renewal terms and the unchecked consent checkbox above it unchanged."
      },
      "preconditions": [
        "Nothing is charged during the trial."
      ],
      "guardrails": [
        {
          "rule": "Show any offer next to a prominent “Cancel subscription” button that stays visible the whole time; one click on it cancels.",
          "requirement_ids": [
            "US-CA.17602.e"
          ]
        }
      ],
      "verification": {
        "operation_id": "checkDisclosure",
        "verdict": "pass",
        "verified": true,
        "open_checks": [],
        "compliance_status": "compliant",
        "compliance_label": "Compliant",
        "jurisdictions": "all",
        "note": "The compliant baseline with this change applied was run through checkDisclosure."
      },
      "placeholders": []
    }
  ],
  "withheld": [
    {
      "key": "checkout.annual_equivalent",
      "surface": "checkout_page",
      "title": "Show the per-month figure next to the yearly charge",
      "reason": "Your yearly plan starts with a free trial: next to a trial, a per-month figure reads as the charge after the trial."
    }
  ],
  "next_action": {
    "type": "apply_suggestion",
    "message": "Optional. Pick a suggestion, apply suggestion.apply.content exactly where apply.where says…",
    "operation_id": "retrieveCompliancePlan",
    "mcp_tool": "get_compliance_plan"
  },
  "disclaimer": "Optional suggestions to improve conversion and retention…"
}
```
