# Versioning and changelog

What `/v1` promises, what is deliberately not versioned, and everything that has changed.

`/v1` is a promise about what will not change under you, and a changelog of
what has been added since. Both are on this page because they are one subject:
a version number nobody publishes changes to is a version number.

## What `/v1` promises

`/v1` changes **only by addition**. Within it we may:

- add an operation;
- add an **optional** field to a request;
- add a field to a response;
- add a value to an enum a response carries;
- add an error `code`.

What a breaking change is, and what we will not do inside `/v1`: remove or
rename a field, make an optional request field required, narrow an enum a
request accepts, change a status code, or change the shape of a body. Any of
those ships as `/v2`, with `/v1` kept alongside it and a date before anything
is withdrawn.

**So tolerate what you do not know.** Ignore response fields you have no use
for rather than rejecting the body, treat an unrecognised enum value as "some
other value" rather than an error, and treat an unknown error `code` as a
generic failure of its HTTP status class. An integration written that way
survives every change above without a release; one that validates a response
against a closed list breaks the first time we add a field, and the break looks
like our fault from the outside.

The same rule covers webhooks: a new event name is an addition, so a receiver
must ignore an `event` it does not recognise. See
[Webhooks](https://boltclaw.ai/docs/webhooks#every-event).

## What is not versioned

- **Error sentences.** `error` is written for a person and may be reworded at
  any time. `code` is the stable half; branch on it.
- **Cursors.** Opaque, and their encoding changes without notice. Pass back
  what you were given.
- **Rate limit numbers.** They come from the organisation's plan and from the
  abuse ceilings in front of the API, and both are published live
  (`X-RateLimit-*`, `RateLimit-Policy`, and
  [Plans and rate limits](https://boltclaw.ai/docs/rate-limits)) rather than frozen by the version.
- **The dashboard.** Screens move. Where this reference names one, it names
  where it is today.

## Changelog

Each entry is a minor version of the description, not of the path: nothing
below changes `/v1`, which is why none of it is a `/v2`. The number is
`info.version` in [the specification](https://boltclaw.ai/openapi.yaml).

### 1.22.0 — 2026-10-28

Seven referral operations an AI assistant was refused now ask a person first instead.

- **Asked, not refused.** An AI assistant's call to one of these used to answer `403`; it now answers `428 APPROVAL_REQUIRED` with a link, and a person decides at it, every time (none of the seven can be waived): bringing back an archived program (`POST /v1/referrals/programs/{id}/resume`, `referral.program.restore`; resuming a paused one still asks nobody), a new signup link (`POST …/signup-link/rotate`, `referral.signup.rotate`), a new hosted waitlist page address (`POST …/waitlist/rotate-page`, `referral.waitlist.rotate`), a new panel key (`POST …/embed/rotate-key`, `referral.embed.rotate`), approving a referrer again (`PATCH /v1/referrals/referrers/{id}` with `status: approved`, `referral.referrer.approve`; banning one still asks nobody), tying a Stripe promotion code to a partner's code (`PATCH /v1/referrals/codes/{id}`, `referral.code.link`; the approval page names the promotion code, and a partner never decides their own), and cancelling or failing a payout that was already exported or sent (`POST /v1/referrals/payouts/{id}/cancel`, `referral.payout.reopen`; decided at a two-step session and never by the partner it pays, as sending is). A key that is not an assistant's is unchanged.
- **`ApprovalAction`** gains the seven, all always asked.
- **Still refused to an assistant:** minting a panel session, making an invite link, changing the sites allowed to frame the panel or to read the friend's offer, and switching a hosted waitlist page on.

### 1.21.0 — 2026-10-27

Reports as CSV download links, and version 1's referral dashboard actions in the API: pause, archive, ban, codes, links, invites, approving early, cancelling a payout.

- **Fixed.** `GET /v1/referrals/programs/{id}/payouts?status=` takes `unclaimed` and `returned`, as `PayoutStatus` always listed (it answered `400`).
- **Reports.** `GET /v1/referrals/programs/{id}/export?type=…`: a ten-minute, single-use link to one of a program's reports as CSV (`commissions` with the reason for each amount, `conversions`, `partners` and your in-app referrers, `payouts`, `grants`, `contests`), filtered by `from`, `to` and `status`. The file is never in the response; it is built when the link is opened, only if the key that asked is still live and its creator may still read it. The payout report needs `referrals:payouts`. 60 links an hour for each key. See [Reports](https://boltclaw.ai/docs/referral-reports).
- **A program's state.** `POST /v1/referrals/programs/{id}/pause`, `/resume` and `/archive` (archiving is always asked first for an AI assistant, `referral.program.archive`, and an assistant never resumes an archived program), and `POST /v1/referrals/programs/{id}/signup-link/rotate` (a new signup link; refused to an AI assistant).
- **Partners.** `POST /v1/referrals/partners/{id}/ban` and `/unban`, both always asked first for an AI assistant (`referral.partner.ban`, `referral.partner.unban`); a partner never decides their own.
- **Codes, links and invites.** `POST /v1/referrals/partners/{id}/codes` (a discount code for a partner, or the program's suggestion, made in your Stripe and Shopify shop when they are connected; always asked first for an AI assistant, `referral.code.create`), `PATCH /v1/referrals/codes/{id}` (its Stripe promotion code; refused to an AI assistant), `POST /v1/referrals/codes/{id}/deactivate`; `GET /v1/referrals/programs/{id}/links` and `POST /v1/referrals/links/{id}/archive` (asked first for an AI assistant unless your organisation waives it, `referral.link.archive`); `GET /v1/referrals/programs/{id}/invites` (without their links, which are shown once; both lists a page at a time) and `POST /v1/referrals/invites/{id}/revoke`.
- **Money.** `POST /v1/referrals/commissions/{id}/approve` makes a commission payable before its hold ends, with a reason (the permission to pay partners, `referrals:payouts` on a key, and always asked first for an AI assistant, `referral.commission.approve`); `POST /v1/referrals/payouts/{id}/cancel` takes an unpaid payout out of its batch, its commissions back to Ready to pay (marking it `failed`, or cancelling one already exported or sent, is refused to an AI assistant and needs `referrals:payouts` on a key).
- **Levels.** `POST /v1/referrals/programs/{id}/levels/recalculate` moves partners to the levels they qualify for now, as the daily run does.
- **`ApprovalAction`** gains `referral.program.archive`, `referral.partner.ban`, `referral.partner.unban`, `referral.commission.approve` and `referral.code.create` (always asked) and `referral.link.archive` (switchable), and now lists `referral.terms.update` and `referral.subject.erase` (1.20.0) too.

### 1.20.0 — 2026-10-27

Compliance: answer a person's privacy request, draft program terms, and an approval before an assistant saves new terms.

- **Privacy requests about your own people.** `GET /v1/referrals/data-subjects/{external_id}`: everything BoltClaw holds about one of your customers or in-app referrers in this workspace, by the id your system uses for them (or the payment processor's customer id): the programs they were referred in, their conversions and rewards, and for a referrer their links and commissions. An email fingerprint shows only as held, never its value. `DELETE` the same path erases them in every program: ids and fingerprints go, amounts and commissions stay as anonymous rows, and stored webhook bodies that carried the id are scrubbed. Always asked first for an AI assistant (`referral.subject.erase`). Both are recorded in the activity log with counts only.
- **Program terms from a template.** `POST /v1/referrals/programs/{id}/terms/draft`: answer a few questions (your business's legal name and contact address, who may join, the minimum age, countries left out, forfeiture months, governing law and courts) and get the program's terms, with the cookie window, attribution, hold, minimum payout, schedule, tax forms, invoices, self-billing, second tier and disclosure line read from the program's own settings. Nothing is saved. See [Program terms](https://boltclaw.ai/docs/referral-terms).
- **Saving terms asks first.** `PATCH /v1/referrals/programs/{id}` with `terms` now asks a person first for an AI assistant (`referral.terms.update`), because every partner must accept new terms before their next payout; your organisation may let assistants save terms without asking. Any other change to a program is unchanged.

### 1.19.0 — 2026-10-27

Shops and no-code tools: a Shopify app, a WooCommerce plugin, a Zapier app, and the reads and writes they need.

- **Discount codes.** `GET /v1/referrals/programs/{id}/codes`: every partner code of a program, and the discount a friend gets with one (`friend_discount`), in one read. A shop's own system uses it to keep its coupons in step; BoltClaw's WooCommerce plugin reads it every hour. A code carries `stripe_promotion_code`, and `shopify_discount` once it is a discount in the program's linked Shopify shop.
- **Invites.** `POST /v1/referrals/programs/{id}/invites`: a link for one person (or up to `max_uses` people) to join a program as a partner, optionally until `expires_at`. BoltClaw emails nobody: send the link yourself.
- **Finding a partner by name.** `GET /v1/referrals/programs/{id}/partners` takes `q`: partners whose display name contains it, ignoring case.
- **The Shopify app.** A program can be linked to a Shopify shop from the shop's own admin (never through this API). `POST /v1/referrals/programs/{id}/shopify-codes` keeps the program's codes in step with the shop: each active code becomes a discount there, at the friend's discount, and a code switched off here is switched off there; at most 50 a call, with `remaining`. Always asked first for an AI assistant (`referral.shopify.codes`). An integration's `auth_mode` may be `shopify_app`, with a `shopify` object. See [Shopify](https://boltclaw.ai/docs/referral-shopify).
- **Shopify's privacy requests.** `GET /v1/referrals/programs/{id}/privacy-requests`, `GET /v1/referrals/privacy-requests/{id}` and `POST /v1/referrals/privacy-requests/{id}/answer`: when a shop's customer asks for their data, the request waits for you with everything BoltClaw holds about them (never a name, email, address or phone number: BoltClaw has none), due 30 days after Shopify asked; answering forgets the Shopify ids it named. The erasures BoltClaw does when Shopify asks are listed with counts only.
- **`ApprovalAction`** gains `referral.shopify.codes`, always asked.

### 1.18.0 — 2026-10-25

Campaigns: leaderboards, contests with prizes, milestones that give rewards, waitlists, and a fenced second tier.

- **Leaderboards.** `GET /v1/referrals/programs/{id}/leaderboard`: every promoter ranked by paying customers, sales, revenue or (on a waitlist) confirmed friends, this month, the last 30 days, this quarter or ever, with ids and names. Promoters see the first places only, each named as its promoter chose (`hidden`, `initials` or `display`), and money only when `show` is `amounts`. What counts is counted activity: nothing held, refunded or disputed. A program's `leaderboard` setting (`PATCH /v1/referrals/programs/{id}`) decides what its promoters and the in-app panel see. See [Campaigns](https://boltclaw.ai/docs/referral-campaigns).
- **Contests.** `GET` and `POST /v1/referrals/programs/{id}/contests`, `GET` and `PATCH /v1/referrals/contests/{id}`, `POST /v1/referrals/contests/{id}/cancel` and `POST /v1/referrals/contests/{id}/award`: a window, a metric, who may enter, a minimum, and prizes by place (cash as a bonus paid by hand, or credit, a gift card, a free period or a perk). A person awards the prizes, once, after it ends and once no winner is under review or held; awarding needs the `referrals.payouts` permission, and a key that is not an AI assistant's also the `referrals:payouts` scope. Cancelling and awarding are always asked first for an AI assistant (`referral.contest.cancel`, `referral.contest.award`).
- **Milestones that give rewards.** A milestone reward rule (`conditions.milestone`) may now give credit, a free period, a perk or a gift card instead of cash, and count `friends` on a waitlist. Each is a grant, given once per window.
- **Waitlists.** A `waitlist` program (`kind: waitlist`) ranks the people waiting by join order, moved up `bump` places for each friend who joins and confirms. `GET` and `POST /v1/referrals/programs/{id}/entries` (create or update by your own `external_id`), `GET` and `PATCH /v1/referrals/entries/{id}` (`verified`, `status`), `POST /v1/referrals/entries/{id}/disqualify` and `/requalify`, `POST /v1/referrals/programs/{id}/entries/invite`, `POST /v1/referrals/programs/{id}/waitlist/end` (for good), `POST /v1/referrals/programs/{id}/waitlist/forget` (delete the hosted page's addresses now, and end it) and `POST /v1/referrals/programs/{id}/waitlist/rotate-page` (a new page address; refused to an AI assistant). A hosted page at `/r/w/<token>` (switch `waitlist.page` on) takes sign-ups for a business with no backend; those addresses are sealed, never in `/v1` or a webhook, and exported in the dashboard only. Disqualifying, inviting, ending and forgetting are always asked first for an AI assistant.
- **A fenced second tier.** `GET` and `PATCH /v1/referrals/programs/{id}/second-tier`: a recruiter earns a share (at most 20%) of what the partners they recruited earn in cash on sales, for 1 to 24 months, one level only, never on a partner's own purchases. Off by default; switching it on adds a clause to the terms that every partner accepts before their next payout, and is always asked first for an AI assistant (`referral.tier.update`). A commission carries `tier` (`1` or `2`).
- **Four new webhook events:** `referral.contest.ended`, `referral.milestone.reached`, `referral.waitlist.entry_created` and `referral.waitlist.position_changed` (at most once an hour per entry). Each names your own `external_id` and no address.
- **The program** carries `leaderboard`, `waitlist`, `second_tier` and `terms_required_version`. `ApprovalAction` gains `referral.contest.award`, `referral.contest.cancel`, `referral.waitlist.disqualify`, `referral.waitlist.invite`, `referral.tier.update`, `referral.waitlist.end` and `referral.waitlist.forget`, all always asked.

### 1.17.0 — 2026-10-25

Grow your partner program: the directory listing, broadcasts, imports, a file library, links to any page and campaign tags.

- **Your listing in the partner directory.** `GET` and `PATCH /v1/referrals/programs/{id}/listing`: the category, pitch, logo, countries and languages the directory shows, and whether the program is `listed`. The directory is a page inside BoltClaw only signed-in accounts can open; it lists only programs whose business switched listing on, and never shows who promotes a program or how many. Listing is refused until every condition in `missing` is met. `POST /v1/referrals/programs/{id}/listing/logo` answers a signed URL for the logo. Changing the listing is asked first for an AI assistant (`referral.listing.update`, switchable). The directory itself is not in `/v1`. See [Recruiting partners](https://boltclaw.ai/docs/referral-growth).
- **Broadcasts.** `GET` and `POST /v1/referrals/programs/{id}/broadcasts`, `GET` and `PATCH /v1/referrals/broadcasts/{id}`, `POST /v1/referrals/broadcasts/{id}/test` (to the key's creator only) and `POST /v1/referrals/broadcasts/{id}/send`: a message to a program's promoters, everyone or a group or a list, in a small Markdown subset with `{name}`, `{link}` and `{code}`. Promoters with an account are emailed with a one-click unsubscribe; your own users in the in-app panel are not (`GET /v1/referrals/broadcasts/{id}/referrers` names them for your own message). 2 a day and 10 in 30 days per program. Sending is always asked first for an AI assistant (`referral.broadcast.send`).
- **Bring a partner list across.** `POST /v1/referrals/programs/{id}/imports/preview` checks up to 500 rows (or a CSV) and writes nothing; `POST /v1/referrals/programs/{id}/imports` makes an invite reserved for each person, with their vanity code, group, custom rate and opening balance (paid as a bonus only when they claim it). BoltClaw emails nobody: the answer, and `GET /v1/referrals/imports/{id}/invites`, carry each person's link for you to send. `GET /v1/referrals/programs/{id}/imports` lists imports. Opening balances need `referrals:payouts` on a key; an AI assistant is always asked first (`referral.import.commit`).
- **The file library.** `GET` and `POST /v1/referrals/programs/{id}/library`, `PATCH` and `DELETE /v1/referrals/library/{id}`, `POST /v1/referrals/library/{id}/upload`: images (PNG, JPEG, WebP, checked by their bytes), text snippets with `{link}` and `{code}`, and links, each for everyone or one group. Deleting is asked first for an AI assistant (`referral.library.delete`, switchable).
- **Links to any page.** `https://boltclaw.ai/r/<code>?to=/pricing` lands on that page of your product URL's host, and `&host=` on another of your hosts once you prove it with a TXT record: `GET /v1/referrals/programs/{id}/link-hosts` and `POST /v1/referrals/programs/{id}/link-hosts/check`. Anything else falls back to the product URL.
- **Campaign tags.** `?sub=yt` on a referral link is recorded on the click and passed to your site as `bc_sub` (the website tag's `BoltClawReferral.sub()`). Send it back on a conversion as `referral.sub`, or in Stripe metadata as `boltclaw_sub`; a link's own `utm_*` parameters are passed on unchanged.
- **The disclosure line.** A program carries `disclosure_text`, the sentence partners add when they share (never empty; set it with `PATCH /v1/referrals/programs/{id}`). A partner carries `disclosure_acknowledged_at` and `muted_broadcasts`.
- **Two new webhook events:** `referral.broadcast.sent` (with how many partners and how many of your own users it reached) and `referral.partner.imported_claimed`.
- **Approval actions.** `ApprovalAction` gains `referral.listing.update` and `referral.library.delete` (switchable), and `referral.broadcast.send` and `referral.import.commit` (always asked); it now also lists 1.16.0's `referral.brand.update`, `referral.domain.add` and `referral.domain.remove`, which it had left out.

### 1.16.0 — 2026-10-24

Your own domain for referral links and the partner portal, and your brand on everything partners see.

- **Your brand.** `GET` and `PATCH /v1/referrals/brand`: the name, logos, colours, corner radius, sender name, support address and whether "Powered by BoltClaw" shows (off unless you turn it on) on your partner portal, the join page, the in-app panel, the friend's welcome and partners' emails. With `program_id`, one program's override; a field it leaves `null` follows the workspace's brand. `effective` is what partners see. Changing it is asked first for an AI assistant (`referral.brand.update`), unless your organisation lets assistants change it without asking. See [White-label portal](https://boltclaw.ai/docs/referral-portal).
- **Logos and icons.** `POST /v1/referrals/brand/uploads` answers a signed URL for one PNG, JPEG or WebP file (at most 2 MiB); `PUT` the file there, then save it with `PATCH /v1/referrals/brand`, sending its `path` as `logo_light_path`, `logo_dark_path` or `favicon_path`. `upload_url` is null in a replay of the same `Idempotency-Key`.
- **The reply-to address, proven.** `POST /v1/referrals/brand/support-email/verification` emails a six-digit code to the support address on the brand; `POST /v1/referrals/brand/support-email/verification/confirm` takes it back. Only then is the address the reply-to of partners' emails.
- **Your own domains.** `GET` and `POST /v1/referrals/domains`, `GET` and `DELETE /v1/referrals/domains/{id}` and `POST /v1/referrals/domains/{id}/check`: a links domain (`go.example.com/<code>`) or a partner portal (`partners.example.com`), with its status and the exact DNS records still to set. Adding and removing are always asked first for an AI assistant (`referral.domain.add`, `referral.domain.remove`). A new limit, `maxRefDomains` (5 on Free), counts every domain added; past it, adding is `402`. See [Your own domain](https://boltclaw.ai/docs/referral-domains).
- **Links and signup links use your domain once it is active.** A partner's link, a placement's link and an invite's signup link read `https://go.example.com/<code>` and `https://partners.example.com/join/<token>` in every View; the `https://boltclaw.ai/r/<code>` form keeps working for every code.
- **Approval actions.** `ApprovalAction` gains `referral.brand.update` (switchable), and `referral.domain.add` and `referral.domain.remove` (always asked).

### 1.15.0 — 2026-10-24

Tax forms, the year-end 1099-NEC data, and invoices for every payout.

- **Tax settings.** `GET` and `PATCH /v1/referrals/programs/{id}/tax`: whether a program's payouts wait for a tax form (`tax_forms`: `off`, `us_only` or `all`) or for the promoter's own invoice (`invoice_required`), and whether you issue self-billed invoices (`self_billing`), with whether your own tax details are complete and which payout providers count toward your 1099-NEC. Changing them needs `referrals:payouts` on a key as well as `referrals:write`, and is always asked first for an AI assistant (`referral.tax.settings`): turning a requirement off lets money that waited for a form be paid. See [Tax forms and 1099s](https://boltclaw.ai/docs/referral-tax).
- **Who has signed what.** `GET /v1/referrals/programs/{id}/tax-forms` pages every promoter's latest W-9, W-8BEN, W-8BEN-E or statement that they are not a US person, by type, revision, status and expiry, with whether their payouts wait for it and how much. Status only: no operation returns a tax number or a form's contents.
- **Forfeiting held money.** `POST /v1/referrals/partners/{id}/forfeit` gives up, for good and with a reason, a promoter's approved money that waits for a missing, expired or replaced form. It needs `referrals:payouts` as well as `referrals:write`, or an AI assistant with a person's approval (`referral.commission.forfeit`, always asked). BoltClaw never forfeits anything by itself.
- **The year-end data.** `GET /v1/referrals/tax-years/{year}`: everyone your programs paid in a year, how, their form's status, whether they belong on your 1099-NEC and why, and the warnings to fix before you file. No tax number and no legal name: the IRS IRIS CSV, the general CSV and Copy B are made in the dashboard, with two-step verification.
- **Invoices.** `GET /v1/referrals/payouts/{id}/invoices` lists a payout's self-billed and uploaded invoices; `POST /v1/referrals/invoices/{id}/link` and `POST /v1/referrals/payouts/{id}/statement-link` return a PDF link good for ten minutes and one download (`url` is null in a replay of the same `Idempotency-Key`); `POST /v1/referrals/payouts/{id}/invoice` issues a paid payout's missing self-billed invoice; `POST /v1/referrals/invoices/{id}/reissue` replaces a disputed one with a corrected one. See [Self-billing](https://boltclaw.ai/docs/referral-self-billing).
- **Payouts that wait for a form or an invoice say so.** In `GET /v1/referrals/programs/{id}/payout-preview` a promoter's `wait` may be `tax_form` or `invoice`, with `waiting_since` (since when money has waited for a form); in `GET /v1/referrals/payout-batches/{id}` a line's `wait` may be the same two. A new program in US dollars starts with `tax_forms: us_only`; existing programs keep `off` until you change it.
- **Four new webhook events:** `referral.tax_form.signed`, `referral.tax_form.expiring` (with `days_left`), `referral.invoice.issued` and `referral.invoice.disputed`. Each carries ids, a type and a status, never a form's contents.
- **Approval actions.** `ApprovalAction` gains `referral.commission.forfeit` and `referral.tax.settings`, both always asked.

### 1.14.0 — 2026-10-24

Payouts through your own provider: send with one press, or by your own system.

- **Send a payout batch.** `POST /v1/referrals/payout-batches/{id}/send` pays a batch from YOUR own PayPal, Wise, Stripe, Tremendous, Trolley or Tango account, or asks your own payout system for each payout. BoltClaw never holds the money and charges nothing. Send back `expected_totals` (the batch's `sendable.totals`) or nothing is sent; name large payouts in `confirm_large`. It needs `referrals:payouts`, or an AI assistant with a person's approval (`referral.payout.send`, always asked): `referrals:write` alone never sends money. See [Paying partners](https://boltclaw.ai/docs/referral-payouts).
- **Batches and Ready to pay.** `POST /v1/referrals/programs/{id}/payout-batches` makes a batch; `GET /v1/referrals/payout-batches/{id}` shows each payout with its provider, whether it can be sent and why not, and the provider's estimated fee; `GET /v1/referrals/programs/{id}/payout-preview` shows who a batch would pay, per currency and provider, and who waits and why. `POST /v1/referrals/payout-batches/{id}/cancel-auto-send` stops a scheduled send.
- **Payout providers and settings.** `GET /v1/referrals/programs/{id}/payout-providers` lists the program's payout accounts by name and state (connecting one is done in the dashboard); `POST /v1/referrals/payout-providers/{id}/test` tests one. `GET` and `PATCH /v1/referrals/programs/{id}/payout-settings`: the provider, the providers a promoter may choose among, the payout day and the largest payout (an AI assistant is always asked first: `referral.payout.settings`). Sending on a schedule (`auto_send`) becomes available after a legal review.
- **Your own payout system.** A new scope, `referrals:payouts`, never held by an AI assistant: `GET /v1/referrals/payouts/{id}/details` reads where a requested payout goes (audited like an export), and `POST /v1/referrals/payouts/{id}/result` reports `paid`, `failed` or `returned`. See [Your own payout system](https://boltclaw.ai/docs/referral-payout-webhook).
- **Payouts carry how they went.** A payout's `provider` may be any of `manual`, `webhook`, `paypal`, `wise`, `stripe_connect`, `tremendous`, `trolley` and `tango`, its `status` may be `unclaimed` or `returned`, and it carries `delivery`: when it was sent, the provider's own id, whether it waits for you to fund it, and a failure as a fixed code and sentence. `GET /v1/referrals/payouts/{id}` reads one.
- **Gift cards through Tremendous.** `POST /v1/referrals/grants/{id}/send` gives a promoter's ready gift card from your Tremendous balance (an AI assistant is always asked first: `referral.grant.fulfil`).
- **Four new webhook events:** `referral.payout.sent`, `referral.payout.failed`, `referral.payout.returned` (with `after_paid` when it came back after it was paid), and `referral.payout.requested`, sent only to the endpoint a program chose as its own payout system. `referral.payout.paid` keeps its body.
- **A new error code, `TWO_STEP_REQUIRED`** (403), from the dashboard only: sending payouts and connecting a payout account need two-step verification.
- **Approval actions.** `ApprovalAction` lists every always-asked action, including Wave 5's `referral.flag.dismiss`, `referral.flag.confirm` and `referral.risk.update`, which it had left out.

### 1.13.0 — 2026-10-24

Fraud: the review queue, the risk settings, and what the engine saw.

- **The review queue.** `GET /v1/referrals/programs/{id}/flags` lists a program's flags (filter by `status`, `rule`, `reason`, `partner_id`, `min_score`, `older_than_days`); `GET /v1/referrals/flags/{id}` returns one with its score, every reason as a sentence, the customer's timeline and the partner's history. `POST /v1/referrals/flags/{id}/dismiss` and `/confirm` decide one, and `POST /v1/referrals/programs/{id}/flags/dismiss` and `/confirm` decide up to 200 with one reason, each audited. An AI assistant is always asked first (`referral.flag.dismiss`, `referral.flag.confirm`). Nothing bans, rejects or confirms by itself: the engine only holds. See [Fraud and risk](https://boltclaw.ai/docs/referral-fraud).
- **Holding a partner's payouts.** `POST /v1/referrals/partners/{id}/hold-payouts` opens a `manual_hold` flag: a stop, never a decision. One key may place at most 30 an hour (`429 RATE_LIMITED`).
- **The fraud settings.** `GET` and `PATCH /v1/referrals/programs/{id}/risk-policy`: the hold and review lines, the extra hold, the fewest seconds from click to conversion, paid search on your brand with a campaign allowlist, new partners waiting longer, and the alert email. `PATCH` changes only the fields you send (one sent as `null` goes back to the platform's value; `policy: null` clears them all). Changing them is always asked first for an assistant (`referral.risk.update`).
- **New flag rules.** A flag's `rule` may be `risk_review`, `self_referral_card`, `payout_change` or `manual_hold`, and a flag carries `score` (null for the older rules).
- **Why a commission waits.** A commission carries `risk`: the score, `hold` (it waits longer) or `review` (it waits for a person), and every reason as a sentence; null when the engine held nothing. A partner sees the later date, and `under_review` only while a person must decide; never why.
- **The click id.** A conversion's `referral` takes `click_id` (the tag's `clickId()`), and Stripe's `client_reference_id` may be `<code>--<click id>` (what the tag's `clientReferenceId()` now gives), so the time from click to conversion counts. A subscription checkout's click is kept with its customer for the first invoice.
- **Applications.** A program takes `application_questions` (up to three), returned with `risk_alert_email`; a partner carries `application`: the questions as they were asked, their answers and website, their account's age in days, and counts of near-identical applications and of applications from one network, never who or where.
- **A new webhook event, `referral.risk.alert`**, when a promoter first needs a look or a code leak starts, at most once per promoter a day. Its body names the promoter as you know them (a partner's membership and display name, or your own id for a referrer), the `kind` (`review` or `code_leak`), the `score` and the open flag; never what the engine saw.
- **A new error code, `REAUTH_REQUIRED`** (401), from the dashboard's session actions only: changing where money goes asks for a recent sign-in.
- **Payout files.** A payout export leaves out, with the reason `payout_hold`, a partner whose payout details changed in the last 48 hours or who locked their own payouts: their new details go in no file.

### 1.12.0 — 2026-10-23

Any billing system, the live setup status, test data, and clicks from your own server.

- **Every billing system.** Referral programs connect directly to Paddle, Polar, Whop, RevenueCat, WooCommerce, FastSpring, Lemon Squeezy, Shopify, Chargebee, Recurly and Gumroad as well as Stripe, and to Stripe in one click through the Bolt Claw Stripe App. Connecting one is done in the dashboard, because each needs a credential pasted or shown once. A commission's `conversion.source` may now name any of them, and an integration (`Program.integrations[]`) carries `auth_mode`, `test_mode`, `credential`, `read_key`, `provider_account`, `app` and `health`; `provider` takes every name above and `status` may be `disconnected`. See [Connect your billing system](https://boltclaw.ai/docs/referral-setup).
- **The live setup status.** `GET /v1/referrals/programs/{id}/integration-status` says, part by part (the website tag, billing, signups, sales, codes, the panel, the welcome), whether each is `connected`, `warnings` or `not_connected`, with the fix. `POST /v1/referrals/programs/{id}/integration-status/tag-check` fetches your product page once to look for the tag.
- **Test data.** `test: true` on `POST /v1/referrals/conversions` records a conversion as test data: credited to the program's test partner, never payable, never counted, emailing nobody, and never colliding with a live `event_id` or customer. Conversions, commissions and grants carry `test`. `POST /v1/referrals/programs/{id}/test-sale` runs a whole test sale (a click, a signup, a sale with two items and a partial refund) and answers with every step; `DELETE /v1/referrals/programs/{id}/test-data` deletes the program's test data (an AI assistant is always asked first: `referral.testdata.clear`).
- **Test events for your webhooks.** A webhook endpoint takes `test_events` (default `false`). Test data reaches only endpoints that switched it on, and a test delivery's body carries `"test": true`; a live body is unchanged.
- **Clicks from your own server.** `POST /v1/referrals/clicks` records a click on a partner's link that opened straight on your site, hashed and classed as `/r/<code>` does it, with the address and user agent never stored. The SDK's `referralsMiddleware()` sends it.
- **Onboarding answers.** A program takes `setup_payments`, `setup_signups` and `audience_eu_uk`, and returns them as `setup`, which choose the setup steps the dashboard lists.

### 1.11.0 — 2026-10-23

Rewards that are not cash, for either side, and the friend's welcome.

- **Rewards that are not cash.** A reward rule takes `reward_type` (`cash`, `credit`, `free_period`, `discount`, `perk`, `gift_card`) and `beneficiary` (`promoter`, or `referee` for the friend), with each type's own fields (`period_count`, `period_unit`, `perk_text`, `discount_duration`, `discount_months`); both are fixed once the rule exists. Left out, a rule is cash for the promoter, as before, and `kind` is no longer required for a free period or a perk. Every rule already written is unchanged. See [Rewards that are not cash](https://boltclaw.ai/docs/rewards-not-cash).
- **Grants.** What such a rule gives is a grant: `GET /v1/referrals/programs/{id}/grants` and `GET /v1/referrals/grants/{id}` read them, `POST /v1/referrals/grants/{id}/fulfil` says you gave one (idempotent on your `reference`) and `POST /v1/referrals/grants/{id}/fail` says it could not be given. An AI assistant must always ask a person first for both (`referral.grant.fulfil`, `referral.grant.fail`). A conversion gains `grants`, `referee_referrer` and `referral_refused`; a referral may say `discount_applied`.
- **The friend's offer.** `GET /v1/referrals/offers/{code}` returns what a person who arrived through a link is offered, in the welcome's words, and whether a given customer may have it at your checkout, with a stable `reason_code`. `GET /v1/referrals/programs/{id}/customers/{external_id}/referral` says who invited one of your customers, by a name only when they agreed. Both accept `referrals:embed`. The program gains `welcome`, `late_code_days`, `lead_grant_daily_cap`, `invite_after_purchase` and `ask_snooze_days`, set through `PATCH /v1/referrals/programs/{id}`. See [The friend's welcome](https://boltclaw.ai/docs/referral-welcome).
- **Codes in your Stripe.** `POST /v1/referrals/programs/{id}/stripe-codes` makes a coupon from the friend's discount and a promotion code for each of the program's codes, using a restricted key you set in the dashboard; always approved for an assistant (`referral.stripe.codes`). An integration gains `codes_and_credits` and `payload_format`; a reward, `stripe_coupon_id`. See [Stripe codes and credits](https://boltclaw.ai/docs/referral-stripe-codes).
- **Five new webhook events:** `referral.grant.ready`, `referral.grant.fulfilled`, `referral.grant.cancelled`, `referral.grant.reversed` and `referral.referee.invite_ready`. A grant event carries what the grant is and whose (your `external_id` for a referrer, a display name for an affiliate); the invite event carries the friend's new link.
- **The panel View** (`ReferrerPanelView`) gains `grants`, `ask` and `thank_you`.

### 1.10.0 — 2026-10-23

A referral panel inside your own product, and the referrers behind it.

- **Referral panel.** `POST /v1/referrals/programs/{id}/embed-tokens` mints a short-lived session your own server hands to the Bolt Claw panel for one signed-in user; the token is shown once. Switch the panel on and list the sites that may show it with `embed_enabled` and `embed_origins` on `PATCH /v1/referrals/programs/{id}`; `embed_config` sets its look, copy and sharing, and `approve_referrers` decides whether a new referrer waits for you. The program gains an `embed` object. See [Referral panel](https://boltclaw.ai/docs/referral-panel).
- **Referrers.** `GET`/`POST /v1/referrals/programs/{id}/referrers` list, create and update your own users as referrers (the `POST` creates or updates by your `external_id` and answers `201` or `200`), `GET /v1/referrals/programs/{id}/referrers/{external_id}` returns exactly what the panel shows as data (`ReferrerPanelView`, with a stable `reason_code` per friend), `PATCH /v1/referrals/referrers/{id}` approves or bans one, and `DELETE` erases one: an AI assistant must always ask a person first (`referral.referrer.erase`).
- **Two controls for the panel.** `POST /v1/referrals/programs/{id}/embed/revoke-sessions` ends every live session at once, and `POST /v1/referrals/programs/{id}/embed/rotate-key` gives the panel a new public key.
- **A new scope, `referrals:embed`**, issued with the "Referral panel" preset: it mints sessions and manages referrers and nothing else, so a leaked key cannot touch a program, a partner or a payout. `referrals:write` is accepted where it is, except on an AI assistant's key. An assistant can never carry `referrals:embed` or mint a session.
- **Four new webhook events**, `referral.referrer.created`, `referral.referrer.conversion`, `referral.referrer.reward_earned` and `referral.referrer.payout_details_changed`. Unlike the other referral events their bodies also carry your own `external_id` for the user (and, for a reward, its amount, currency and status).
- **Referrers have their own allowance**, `maxRefReferrers` (Free 100,000), and the partner allowance now counts affiliates only: your own users are never counted as partners. A new referrer past the allowance is `402 PLAN_LIMIT`.

### 1.9.0 — 2026-10-23

Referral programs pay by rules: per product or plan, per group or level, bonuses and ceilings.

- **Reward rules.** `GET`/`POST /v1/referrals/programs/{id}/rewards`, `PATCH`/`DELETE /v1/referrals/rewards/{id}`: a program pays by rules for everyone, a group or level, or one partner, with conditions on a sale's products, prices, plans, intervals, first payment or renewal, currency and amount, a ceiling per customer (`cap_minor`), and milestone bonuses (`event: "bonus"`). See [Reward rules](https://boltclaw.ai/docs/rewards).
- **Groups and levels.** `GET`/`POST /v1/referrals/programs/{id}/groups`, `PATCH`/`DELETE /v1/referrals/groups/{id}`, and `PATCH /v1/referrals/partners/{id}` to move a partner (and `pinned` to keep them there). A level is a group with a `rank` and a `minimum`; partners move between levels once a day.
- **A bonus paid by hand**: `POST /v1/referrals/partners/{id}/bonuses`, with the reason the partner reads; it requires an `Idempotency-Key`, so a retried timeout cannot pay twice. An AI assistant must always ask a person first (`referral.commission.bonus`), and so for deleting a rule or a group (`referral.reward.delete`, `referral.group.delete`).
- **`POST /v1/referrals/conversions` takes `items`**, a sale's lines with each one's share of the amount, so a rule can pay per line. The answer adds `commissions`: every commission the sale earned, with why.
- **New response fields**, nothing removed: a commission's `basis_text` (why it is that amount), `capped_minor` (what the monthly ceiling held back) and `bonus`; a program's `kind` and `partner_monthly_cap_minor`; a partner's `group` and `group_pinned`. `POST /v1/referrals/programs` takes `kind` to start from a template, and both program operations take `partner_monthly_cap_minor`.
- **A new webhook event, `referral.bonus.created`**, with the same four ids as `referral.commission.created`. A milestone bonus still on hold is taken back, as `referral.commission.reversed`, when a refund or dispute takes the partner back under its line. A commission a hand-paid bonus creates has no `conversion` in the commission list.

### 1.8.0 — 2026-10-21

Links are unlimited in practice, and every limit is your organisation's own number.

- **A page holds up to 1,000 links** (it was 100): a safety ceiling the same on every plan, so links are unlimited in practice. `GET /v1/pages/{slug}/links` lists up to 5,000, the most any page can ever hold, so a page over its ceiling is still listed whole. Adding a link to a page at its ceiling is still `409 CONFLICT`.
- **Field sizes are your organisation's.** A title is 120 characters, a bio 600 (it was 500 here and 600 in the dashboard), a link label 120, a contact note 300 and a page 12 social icons, on every plan by default. A write past your number answers `400` naming the field and the number. A page saved before a number was lowered keeps what it has; a list of social icons that does not grow is never refused.
- **`GET /v1/usage` lists every limit that applies to you**: besides the allowances, `links_per_page`, `social_icons_per_page`, `page_title_chars`, `page_bio_chars`, `link_label_chars`, `contact_note_chars`, `webhook_endpoints`, `api_pages_created_per_hour`, `api_messages_per_hour` and `thread_messages_per_hour`. Each is the number in force for your organisation, including any we have raised for it.
- **`GET /v1/pages/{slug}/stats` starts no earlier than your plan's analytics history** (`analytics_days`: 30 days on Free). The window actually used is echoed back as before, so a `from` later than the one you sent is where the history ended. Nothing older is deleted, and a larger plan sees it at once. Post `metrics` on `/v1/posts` follow the same window: a post published before it shows `metrics: null`.
- **One conversation takes 30 messages an hour from one sender, here as in the dashboard.** This API allowed 60 while its own description said 30. The per-key budget for sending messages (60 an hour) and for creating pages (10 an hour) are unchanged by default and are now set per plan.
- Free organisations connect every network except X (TikTok, LinkedIn, YouTube and Pinterest join Instagram, Facebook, Threads and Bluesky), and the Free plan's allowances are larger: see [Plans and rate limits](https://boltclaw.ai/docs/rate-limits). Until the paid plans are next reviewed, no paid plan is below Free on any allowance.
- **AI credits are counted in tokens on Free.** `GET /v1/usage` adds `ai_credits_per_day` (3,000 on Free; 1 credit is 10 tokens of what the AI reads and writes, about 70 for a reply draft) and `ai_requests_per_minute` to `limits`, and `ai_credits_today` to `usage`: today's use, by UTC date, reset at midnight UTC. `ai_credits_per_month` is now one credit per draft on the paid plans only, spent before the daily credits; on Free it is 0. An automation with `use_ai` sends one of its written replies when neither has anything left.

### 1.7.0 — 2026-10-19

The API's own host is the base URL, and a page holds at most 100 links.

- **The base URL is now `https://api.boltclaw.ai/v1`.** `https://boltclaw.ai/api/v1` remains a supported alias that reaches the same gateway, so nothing has to move. The limits counted per address count your own address there too, in a bucket of their own, separate from your direct calls, under a ceiling shared by everything arriving through the same proxy address. Use `https://api.boltclaw.ai/v1` for anything new: it is the API's own host and one hop shorter. Every example in this reference, the CLI and the hosted MCP server now use it.
- **A page holds at most 100 links, here as in the dashboard** (this API allowed 500). Nothing is taken away: a page that already has more keeps every link, `GET /v1/pages/{slug}/links` lists them all, and each can still be edited, moved or removed. Adding one to a page at 100 or more is `409 CONFLICT`.
- `platform_charges` on `GET /v1/usage` is `null` unless the person who created the key may manage the organisation's billing. The charges are the organisation's bill, and `social:read` alone no longer shows them. The plan, its allowances and this month's use are unchanged.
- `POST /v1/auth/device/poll` answers a `device_code` that was never issued, or has been cleared away, with `200` and `"status": "expired"`, exactly like one that lapsed. It used to answer `404`, which told a caller whether a code had ever existed. If you handled the `404`, handle `expired`, which you already do.
- `resource_id` on `GET /v1/logs/audit` is `null` for the entries about the workspace itself or its organisation (`workspace.*` and `impersonation.*`).
- `POST /v1/referrals/conversions` now says how to retry in its own description: send the same `event_id` again, including after a `409 CONFLICT`, and never a new one, because a new id is a new conversion. `x-idempotent: true` marks it, and approving a partner, as safe to repeat with the same body.
- `x-mcp-worker-only: true` marks the three operations only the hosted MCP server may call (`GET` and `POST /v1/auth/key/workspaces`, `POST /v1/auth/key/revoke-oauth`); anything else is answered `404`. It appeared in 1.6.0 without a note here.
- `/openapi.yaml` is written from the parsed specification, exactly as `/openapi.json` is: the same document, with nothing in it that is not part of the description.

### 1.6.0 — 2026-10-19

An AI assistant can sign in, and a person stays in charge of what it does.

- `POST /v1/auth/device` and `POST /v1/auth/device/poll` are a device sign-in: **your program generates the key and sends only its hash**, a person approves it in the browser, and no secret of ours exists at any moment. Neither call needs a key, because they are how a client gets one.
- **Some calls now stop and ask.** When an AI assistant tries to delete a page, publish a post, reply to somebody, switch an automation on, or move a partner's money, the API answers `428` with an `approval` link and **nothing happens**. A person approves it, the assistant sends the identical request again with the id in `BoltClaw-Approval`, and it runs. A key you created in the dashboard is never gated this way.
- Four new error codes go with it: `APPROVAL_REQUIRED` and `APPROVAL_PENDING` (428), `APPROVAL_DENIED` (403), and `APPROVAL_INVALID` (409). `AGENT_ACCESS_DISABLED` (403) is what an assistant's key gets when an organisation turns assistants off.
- `GET /v1/approvals/{id}` reads an approval you asked for. `POST /v1/auth/key/extend` and `POST /v1/auth/key/revoke` let a key push out its own expiry or end itself. All three act on the caller alone and need no scope.
- `GET /v1/logs/agent-calls` and `GET /v1/logs/audit` publish what assistants did in a workspace and what everybody did, on `logs:read`. An actor is a label rather than an account, and anything credential-shaped is `[redacted]`.
- `keys:read`, `keys:write`, `webhooks:read` and `webhooks:write` **cannot be carried by an assistant's key at all**, and the refusal is in the database rather than in a route table.

### 1.5.0 — 2026-10-19

The reference is generated from the specification.

- `https://boltclaw.ai/openapi.yaml` and `https://boltclaw.ai/openapi.json` publish the specification itself. Generate a client from either; they are the same document.
- Every page under `/docs` is built from that specification, so an operation cannot be documented without existing and cannot exist without being documented.
- **The error table was wrong about two statuses and now is not.** A `422` and a `503` from a social network operation carry `code: "UPSTREAM_ERROR"`, which is what the API has always answered; the old table said `VALIDATION_ERROR` and `INTERNAL_ERROR`. Nothing in the API changed. If you branched on the documented code for those two statuses, branch on `UPSTREAM_ERROR`.
- The three shapes a success body takes are now written down, on [Pagination](https://boltclaw.ai/docs/pagination#the-three-success-shapes), instead of described as one.

### 1.4.0 — 2026-10-18

A host of its own, and the limits in front of it are published.

- `https://api.boltclaw.ai/v1` is the API's own host. **`https://boltclaw.ai/api/v1` keeps working and is still the base URL in this reference** — nothing has to move.
- Abuse ceilings above your plan's allowance are answered as `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`, and published in full as `RateLimit-Policy` on every response. Your plan's own allowance keeps its `X-RateLimit-*` headers, unchanged.
- Every `401` carries `WWW-Authenticate`, and every response carries `Link` to this reference and to the specification.
- `GET https://api.boltclaw.ai/` answers a descriptor a client can read before it holds any credential: the authentication scheme, every scope, and the published limits.

### 1.3.0 — 2026-10-18

Webhooks are managed through the API.

- Ten operations under `/v1/webhooks`: register, read, update and delete an endpoint, rotate its signing secret, read its delivery attempts, redeliver one, and send yourself a test event.
- Two new scopes, `webhooks:read` and `webhooks:write`. Neither can be carried by an AI assistant's key.
- `GET /v1/webhooks/events` is the live event catalog, so a client can render the picker rather than hardcoding the list.
- **`GET /v1/webhooks` moved from `inquiries:read` to `webhooks:read`.** It still accepts `inquiries:read`, so a key issued before the move keeps working; that acceptance will be withdrawn, so issue new keys with `webhooks:read`.

### 1.2.0 — 2026-10-18

API keys are managed through the API.

- Five operations under `/v1/keys`: list, create, re-scope, rotate and revoke. Rotation takes a grace period, so a replacement can reach every deployment before the old value stops.
- Two new scopes, `keys:read` and `keys:write`. Neither can be carried by an AI assistant's key.
- `lost_scopes` on every key row and on `GET /v1/ping`: the scopes a key was issued with that the person who created it no longer stands behind.
- `dropped` on a created or re-scoped key names what was asked for and not granted, instead of refusing the whole request.

### 1.1.0 — 2026-10-18

One request pipeline, and the first published specification.

- `X-Request-Id` on every response, and the same value as `request_id` in every error body. Send your own and it is used instead.
- `Idempotency-Key` on every `POST`: the same key and the same request inside 24 hours replays the first answer with `Idempotent-Replay: true`.
- `fields` on a validation error, naming each field and the rule it broke.
- `GET /v1/ping` and `GET /v1/workspace`, both scope-free, so a client can check a credential before it does anything with it.

### 1.0.0 — 2026-09-29

`/v1` as it stood before the platform program.

- Pages, links, conversations and page statistics.
- Social publishing, the inbox, automations, leads and usage.
- Referral programs, partners, conversions, commissions and payouts.

<!-- https://boltclaw.ai/docs/versioning as markdown. Generated from openapi.yaml; API version 1.22.0. -->
<!-- Every /docs page answers as markdown: add .md to its path, or send Accept: text/markdown. -->
