Run a referral or affiliate program and tell Bolt Claw when a referred customer signs up, pays, or asks for their money back. Bolt Claw works out which partner referred them, what that partner earned, holds the commission through your refund window, and takes it back if the sale is refunded.
Three jobs, and they belong on separate keys:
- Reporting what happened —
POST /v1/referrals/conversions— needsconversions:write. The Referral tracking preset is exactly that one scope: the key your checkout server holds, which can read nothing. Report from your server, never from a browser: anything shipped to a visitor can be read by that visitor. - Running the program — programs, partners, commissions, payouts and
statistics — needs
referrals:read(andreferrals:writeto change anything). The Referrals preset is those two. - Showing your own users their referrals — a referral panel
inside your product, or your own screen built on the same data — needs
referrals:embedalone. The Referral panel preset is that one scope: the key your own server holds to mint a panel session for a signed-in user and to create the referrers behind it, which can touch no program, partner or payout. Never put it in a browser or an app.
| Method & path | Scope |
|---|---|
POST /v1/referrals/conversions |
conversions:write |
GET /v1/referrals/programs, GET /v1/referrals/programs/{id} |
referrals:read |
POST /v1/referrals/programs, PATCH /v1/referrals/programs/{id} |
referrals:write |
GET /v1/referrals/programs/{id}/partners |
referrals:read |
POST /v1/referrals/partners/{id}/approve, …/reject |
referrals:write |
GET /v1/referrals/programs/{id}/commissions |
referrals:read |
POST /v1/referrals/commissions/{id}/adjust |
referrals:write |
GET /v1/referrals/programs/{id}/payouts |
referrals:read |
POST /v1/referrals/payouts/{id}/paid |
referrals:write |
GET /v1/referrals/programs/{id}/stats |
referrals:read |
GET /v1/referrals/programs/{id}/rewards, GET …/groups |
referrals:read |
POST /v1/referrals/programs/{id}/rewards, PATCH/DELETE /v1/referrals/rewards/{id} |
referrals:write |
POST /v1/referrals/programs/{id}/groups, PATCH/DELETE /v1/referrals/groups/{id} |
referrals:write |
PATCH /v1/referrals/partners/{id} (a partner's group) |
referrals:write |
POST /v1/referrals/partners/{id}/bonuses |
referrals:write |
POST /v1/referrals/programs/{id}/embed-tokens |
referrals:embed (or referrals:write, never an assistant's key) |
GET /v1/referrals/programs/{id}/referrers |
referrals:read or referrals:embed |
POST /v1/referrals/programs/{id}/referrers (create or update by external_id) |
referrals:embed or referrals:write |
GET /v1/referrals/programs/{id}/referrers/{external_id} |
referrals:read or referrals:embed |
PATCH/DELETE /v1/referrals/referrers/{id} (approve or ban; erase) |
referrals:write |
POST /v1/referrals/programs/{id}/embed/revoke-sessions, …/embed/rotate-key |
referrals:write |
GET /v1/referrals/programs/{id}/grants, GET /v1/referrals/grants/{id} |
referrals:read |
POST /v1/referrals/grants/{id}/fulfil, …/fail |
referrals:write |
GET /v1/referrals/offers/{code} |
referrals:read or referrals:embed |
GET /v1/referrals/programs/{id}/customers/{external_id}/referral |
referrals:read or referrals:embed |
POST /v1/referrals/programs/{id}/stripe-codes |
referrals:write |
GET /v1/referrals/programs/{id}/integration-status |
referrals:read |
POST /v1/referrals/programs/{id}/integration-status/tag-check |
referrals:write |
POST /v1/referrals/programs/{id}/test-sale |
referrals:write |
DELETE /v1/referrals/programs/{id}/test-data |
referrals:write (an AI assistant is always asked first) |
POST /v1/referrals/clicks |
conversions:write |
Every amount is an integer in the currency's minor units — cents for USD,
yen for JPY — in a field ending _minor (the conversion request's amount is
the one exception to the suffix, and the same unit). Responses are the same
objects the dashboard shows, in snake_case; lists are
cursor-paginated, newest first, 50 to a page by default and at
most 100.
This API is the merchant's side of a program. It never carries a partner's
account id or email (a partner's email appears only if they chose to share it
with your program), and a partner never sees your account either — they know
you as the program's name. An id that is not on your account is a 404,
whatever it is.
Referrers: your own users
A referrer is one of your users who holds a referral link, usually through the
referral panel. They are not partners: they have no Bolt Claw
account, never apply, and are not counted against the partner allowance (referrers
have their own, maxRefReferrers, Free 100,000). You identify one by your own id for
them, external_id, which is 1 to 200 printable characters and must be
percent-encoded in a path. GET .../referrers/{external_id} returns exactly what the
panel shows that person, so your own screen can show the same thing.
Rewards that are not cash, and the friend's side
A reward rule can give a free period, credit, a perk, a gift card or (to the
friend) a discount, as well as a commission; it can reward the person who shared
(beneficiary: promoter) or the friend who arrived through the link
(referee). What such a rule gives is a grant, which you give in your own
system and record with POST /v1/referrals/grants/{id}/fulfil, or which Bolt
Claw gives through your Stripe with a restricted key. See
Rewards that are not cash,
The friend's welcome and
Stripe codes and credits.
Carrying the referral to your checkout
A partner's link, https://boltclaw.ai/r/<code>, sends the visitor to your
product URL with ?via=<code> added. Put this tag on your site to keep that
code until checkout:
<script src="https://boltclaw.ai/referrals/v1.js" defer
data-cookie-days="30" data-attribution="last" data-param="via"></script>
| Attribute | |
|---|---|
data-cookie-days |
How long the code is kept, 1–365 days. Default 30. |
data-attribution |
last (the default): the newest link a visitor follows wins. first: the first one is kept. Match your program's attribution model. |
data-param |
The query parameter to read. Default via. |
It stores the code in a first-party cookie on your domain, bc_ref
(mirrored to localStorage, for in-app browsers that drop cookies). It makes
no network request of any kind and sets nothing on boltclaw.ai. It gives you:
BoltClawReferral.code()— the code, ornull.BoltClawReferral.attach(form)— adds a hiddenboltclaw_reffield to a form.BoltClawReferral.clientReferenceId()— the code for Stripe Checkout'sclient_reference_id, orundefined.
v1.js never changes behaviour under you. v2.js does everything it
does, the same way, and adds what you ask for: the friend's welcome banner,
Stripe Payment Links, codes from another tool's links and a consent mode. With
none of those switched on it, too, makes no request. See
The friend's welcome.
event_id makes retries safe
Report every event with an id of your own that never changes — an order id, a
refund id. The same event_id sent again returns 200 with
"duplicate": true and the conversion as it was first recorded, and changes
nothing: no second commission, no second refund. So after a timeout, retry with
the same id; never mint a new one. An event_id is unique per program.
A 409 CONFLICT here means another event for the same customer was being
recorded at that very moment, and nothing was recorded for yours. Send the same
request again, with the same event_id. A new id is a new conversion, and a
new conversion can pay a partner twice for one sale.
Who gets the credit
In order: a referral.code of this program, then an active discount_code
of this program, then whoever the customer is already attributed to. Under
first-click, a customer once attributed stays with that partner. Under
last-click, a newer code moves the customer to its partner for that event
and the ones after it; what was already recorded keeps its commission either way.
A conversion is recorded whether or not anyone referred it ("attributed": false). A partner earns only while their membership is approved: a pending,
rejected or banned partner's conversions are recorded and earn nothing.
Commissions, holds and refunds
A commission is a percentage of the sale (rounded down to the minor unit)
or a flat amount, as the program's reward rules say, and either
once per customer, for a number of months, or for life. A program can pay a
different rate per product, price, plan or interval, more to a group or level of
partners, its own rate to one partner, a bonus at a milestone, and a ceiling per
customer or per month: send a sale's items and each line is matched to the most
specific rule, and every commission says why it is that amount (basis_text).
It starts pending for the program's hold period and becomes approved
when the hold ends, unless it has been flagged for review.
A refund takes back the same share of the commission as the share of the sale
refunded, against the running total refunded: two refunds of 30% each take
back 60%. A full refund, or a dispute, takes back all of it (reversed). If
the commission had already been paid out, the money has left, so a negative
commission is recorded instead and comes off the partner's next payout.
Stripe
If your checkout runs on Stripe, you can connect your Stripe account from the
dashboard instead (in one click with the Bolt Claw Stripe App, or with a webhook
and its signing secret), and paid checkouts, subscription invoices, refunds and
disputes are reported for you. Eleven other billing systems connect the same way,
from Paddle and Shopify to RevenueCat and Gumroad: see
Connect your billing system.
Each one's conversions carry it as their source. Put the code in the Checkout Session's
client_reference_id (or metadata.boltclaw_ref); for a subscription, also in
subscription_data.metadata.boltclaw_ref, so the first invoice is credited
whichever event Stripe delivers first. To tie Stripe customers to the ids you
use here, set metadata.boltclaw_customer_id to your own customer id.
A subscription invoice carries its lines, so a rule for one product, price or
billing interval applies to it with nothing else to set up (each line's share
of what was paid, tax and invoice discounts spread across the lines). When
Stripe sends only some of an invoice's lines, the invoice is matched as a
whole. A one-time Checkout carries no lines: set metadata.boltclaw_product,
metadata.boltclaw_price or metadata.boltclaw_plan on the session and the
sale is matched as that one line.
Refunds are matched to their payment by payment intent. On Stripe API versions
from 2025-03-31, an invoice event no longer carries one, so select
invoice_payment.paid too (the Stripe App receives it for you): it links the
invoice to the payment that paid it, and a refund of a subscription invoice
is then matched whichever event arrives first. Without it, report such a refund
here with refund_of set to the invoice.paid event's id.
The setup status, and test data
GET /v1/referrals/programs/{id}/integration-status answers, part by part (the
website tag, billing, signups, sales, codes, the in-app panel and the friend's
welcome), whether each is connected, warnings or not_connected, with one
sentence saying what was seen and one saying what to do, and lists every
connected billing system without its credential. It counts live data only.
POST .../integration-status/tag-check fetches the product page once to look
for the website tag.
Test data is recorded with "test": true on a conversion,
POST /v1/referrals/programs/{id}/test-sale runs a whole test sale (a click, a
signup, a sale with two items, a partial refund) and answers every step, and a
test-mode or sandbox event from a billing system is test data too. Test data is
credited to the program's own test partner whoever's code it carried, is never
paid, never counted and emails nobody, and reaches only webhook endpoints with
test_events on. Conversions, commissions and rewards carry test. A test
event_id and a live one, or a test customer and a live one, never collide.
DELETE /v1/referrals/programs/{id}/test-data deletes it all and touches nothing
live. Connect your billing system has the whole picture.
A click your own server saw. For a partner's link that opens straight on
your site, POST /v1/referrals/clicks with the link's code and what your
server saw (ip, user_agent, referrer) records it as a click on
boltclaw.ai/r/<code> would be: hashed and classed, never stored, counted
toward the click allowance, and earning nothing by itself.
The onboarding answers. A program takes setup_payments (how you take
payments), setup_signups (how people sign up) and audience_eu_uk, and
returns them as setup; they choose the steps the dashboard lists.
What stays in the dashboard
On purpose, and permanently for this API:
- Payout exports. A payout file carries partners' PayPal, Wise or bank details, for money you owe them. It is downloaded by a signed-in person and audited; a key cannot fetch one.
- Connecting a billing system. Every integration needs a credential pasted or shown once (a signing secret, a read-only key, the Stripe App's install), and a secret never passes through an API call or a chat with a model. A credential goes in once and never comes back out. The setup status reads every integration, without its credential.
- Everything partner-side. Applying, links, codes, placements and payout details belong to the partner's own session. This API is yours.
Also dashboard-only today: invites, discount codes, links, the Stripe connection, pausing or archiving a program, banning a partner, approving a commission early, creating or cancelling a payout batch, and the fraud review queue.
Errors here
The usual codes, and what they mean here:
| Status | code |
|
|---|---|---|
| 400 | VALIDATION_ERROR |
A field is wrong; error names it. |
| 402 | PLAN_LIMIT |
error starts REF_PROGRAM_LIMIT_REACHED (a new program), REF_PARTNER_LIMIT_REACHED (an approval) or REF_REFERRER_LIMIT_REACHED (a new referrer, or approving one again). |
| 403 | INSUFFICIENT_SCOPE |
The key lacks referrals:read, referrals:write, referrals:embed or conversions:write; or an AI assistant's key tried to mint a panel session. |
| 404 | NOT_FOUND |
No such program, partner, commission or payout on your account — including one that is somebody else's. |
| 409 | CONFLICT |
Not in a state that allows this (an archived program, a partner already decided, a payout no longer open, a payout total that refunds changed), or a conversion recorded at the same instant for the same customer — send the same request again. |
Operations
POST /v1/referrals/conversions
Report a conversion
Scope conversions:write. Accepts Idempotency-Key. Another workspace's answers 404.
A lead, sale, refund or dispute. event_id is YOUR id for the event and the idempotency
key: to retry, send the SAME event_id. A repeat answers HTTP 200 with
duplicate: true and changes nothing. A 409 CONFLICT means another event for this
customer was being recorded at that moment: send the same request again. Never make up a
new event_id to retry: that is a new conversion, and can pay a partner twice.
A repeat answers with the conversion as it was first recorded. A lead or a sale needs
customer; a sale needs amount and currency; a refund or dispute needs refund_of
(the sale's event_id), and a refund its amount. Amounts are integers in minor units.
Needs conversions:write, which does nothing else, so it can sit in a checkout server.
Request body (required)
| Field | Type | |
|---|---|---|
program_id |
uuid |
(required) |
event |
ConversionEvent |
(required; one of lead, sale, refund, dispute) |
event_id |
string |
YOUR id for this event, and the idempotency key. Reporting it again changes nothing, so a retry sends the same one; a new one is a new conversion. (required; min 1; max 255) |
occurred_at |
date-time |
|
customer |
object |
|
referral |
object |
|
amount |
integer |
Minor units. (>= 0) |
currency |
string |
|
refund_of |
string |
The sale's event_id. (min 1; max 255) |
items |
SaleItem[] |
A sale's lines, so a reward rule can pay per product, price, plan or interval. Their amounts must add up to the sale's amount (400 otherwise). Leave them out and the sale is matched as a whole. (<= 100 items) |
test |
boolean |
Record this as TEST data: credited to the program's test partner whoever's code it carries, never payable, never counted toward a limit or a total, emailing nobody, and sent only to webhook endpoints that receive test events. A test event_id and a live one never collide, and neither do test and live customers. Deleted with DELETE /v1/referrals/programs/{id}/test-data. |
{
"program_id": "3f8c1e42-9b7a-4d61-a0f5-2c9e1b7d4a83",
"event": "lead",
"event_id": "<event_id>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | Conversion |
Already reported; the first conversion, unchanged. |
| 201 | Conversion |
Recorded. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
Report a signup — answers 201
event_id is your own id for the event and makes the call safe to retry: reporting the same one again answers 200 with duplicate: true and changes nothing.
curl -X POST https://api.boltclaw.ai/v1/referrals/conversions \
-H "Authorization: Bearer bc_live_EXAMPLExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"program_id": "4e8a2b71-39cd-4f06-8a52-71b0d4c9e283",
"event": "lead",
"event_id": "signup-10492",
"customer": {
"id": "cus_10492"
}
}'
const res = await fetch("https://api.boltclaw.ai/v1/referrals/conversions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BOLTCLAW_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"program_id": "4e8a2b71-39cd-4f06-8a52-71b0d4c9e283",
"event": "lead",
"event_id": "signup-10492",
"customer": {
"id": "cus_10492"
}
}),
});
// 201
const data = await res.json();
GET /v1/referrals/programs
List programs
Scope referrals:read.
The workspace's referral programs.
Parameters
| Name | In | Type | |
|---|---|---|---|
limit |
query | integer |
Page size, 1 to 100. Above 100 is clamped; anything else is the default. (default 50; >= 1; <= 100) |
cursor |
query | string |
The next_cursor from the previous page. Opaque; an invalid one is 400. (max 512) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ProgramList |
A page of programs. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs
Create a program
Scope referrals:write. Accepts Idempotency-Key. Can make something publicly visible.
A referral program with a public join page partners apply through. Values are checked
here: an https product_url, cookie_days from 1 to 365, a known attribution_model,
payout_schedule and join_mode (400). The plan sets how many programs an
organisation may run.
Request body (required)
| Field | Type | |
|---|---|---|
name |
string |
(required; max 200) |
product_url |
string |
An https URL. (required; max 4096) |
currency |
string |
(max 8) |
cookie_days |
integer |
1 to 365. |
attribution_model |
string |
last_click or first_click. (max 20) |
hold_days |
integer |
|
min_payout_minor |
integer |
|
payout_schedule |
string |
manual or monthly. (max 20) |
join_mode |
string |
apply or invite. (max 20) |
auto_approve |
boolean |
|
terms |
string | null |
(max 20000) |
preview_title |
string | null |
(max 400) |
preview_description |
string | null |
(max 1000) |
customer_discount |
string | null |
(max 1000) |
discount_code_prefix |
string | null |
(max 40) |
partner_monthly_cap_minor |
integer | null |
The most one partner earns in a calendar month (UTC), in minor units; null for no ceiling. |
embed_enabled |
boolean |
Turns the in-app referral panel on or off. Off, no session can be minted and the panel does not load. |
embed_origins |
string[] |
The sites allowed to frame the panel, like https://app.example.com. Replaces the list. https only (http://localhost:3000 for development), no wildcard, no path, at most 10. (<= 10 items) |
embed_config |
EmbedConfig |
The panel look, copy and sharing, replacing the old settings whole. Unknown keys are 400. |
approve_referrers |
boolean |
True (default): a new referrer is approved at once. False: they are pending until you approve them. |
welcome_config |
any |
The welcome's look and words, replacing the old settings whole; null for the defaults. Unknown keys are 400. |
welcome_show_name |
boolean |
Show a referrer's name to the friend (default true). An affiliate's is shown only when they agreed. |
welcome_page |
boolean |
A one-screen welcome at the link before your product (default false). |
allowed_hosts |
string[] |
Extra hosts your website tag may read the offer from, like www.example.com, beside the product URL's own and the panel's framing origins. Replaces the list. Host names only, at most 10. An AI assistant's key cannot change it. (<= 10 items) |
late_code_days |
integer |
How many days after sign-up a referral code may still be entered (default 14; 0 for never after). (>= 0; <= 365) |
lead_grant_daily_cap |
integer |
The most rewards a day (UTC) signups alone may earn across the program (default 100). (>= 0; <= 100000) |
invite_after_purchase |
boolean |
Give a friend who makes their first payment a referral link of their own (default false). |
ask_snooze_days |
integer |
How long "Not right now" in the panel lasts (default 30). (>= 1; <= 365) |
setup_payments |
SetupPayments |
(one of stripe, paddle, polar, whop, revenuecat, woocommerce, fastspring, lemonsqueezy, shopify, chargebee, recurly, gumroad, own_system, not_sure, null) |
setup_signups |
SetupSignups |
(one of own_backend, supabase, firebase, clerk, authjs, nocode, checkout_only, null) |
audience_eu_uk |
boolean |
Your audience includes the EU or the UK (default false). The setup status then asks for the tag's consent mode. |
application_questions |
string[] |
Up to three questions a partner answers when they apply (each 1 to 200 characters, no hidden characters). Answers are shown on their application; an empty list asks nothing. (<= 3 items) |
disclosure_text |
string |
The sentence partners add when they share (plain text, 10 to 280 characters). It may be edited, never emptied: partners must tell people they earn from the link (FTC, ASA). A new program starts with one for its currency's region. (min 10; max 280) |
leaderboard |
LeaderboardFields |
The leaderboard promoters see, merged onto the current setting; null switches it off. You always see your own. |
waitlist |
WaitlistFields |
A waitlist program's settings, merged onto the current ones. |
kind |
string |
Start from a template: affiliate, refer_a_friend, creator, ambassador, reseller, shop or waitlist. The program gets that kind's settings and reward rules (and levels, for reseller; the waitlist itself, for waitlist), with anything you send yourself taking precedence, in one step. Left out, the program starts with no rules, as before. (max 40) |
{
"name": "<name>",
"product_url": "<product_url>"
}
Answers
| Status | Body | |
|---|---|---|
| 201 | Program |
Created. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 402 | ErrorEnvelope |
PLAN_LIMIT: the organisation's plan does not allow more of this. The message names the allowance. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
Create a referral program — answers 201
name and product_url are the only required fields; everything else takes a sensible default you can change later.
curl -X POST https://api.boltclaw.ai/v1/referrals/programs \
-H "Authorization: Bearer bc_live_EXAMPLExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme partners",
"product_url": "https://example.com"
}'
const res = await fetch("https://api.boltclaw.ai/v1/referrals/programs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BOLTCLAW_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Acme partners",
"product_url": "https://example.com"
}),
});
// 201
const data = await res.json();
GET /v1/referrals/programs/{id}
Read a program
Scope referrals:read. Another workspace's answers 404.
The program, its join link and how many partners are waiting for a decision.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Program |
The program. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/programs/{id}
Update a program
Scope referrals:write. Another workspace's answers 404. Can make something publicly visible.
Changes the fields you send; a field left out keeps its value. Values are checked as on create.
Sending terms saves new program terms and raises terms_version: every partner must accept
the new version before their next payout. An AI assistant's key is asked first when the body
carries terms, unless your organisation lets assistants save terms without asking. Draft
terms from the program's own settings with POST /v1/referrals/programs/{id}/terms/draft.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
name |
string |
(max 200) |
product_url |
string |
An https URL. (max 4096) |
currency |
string |
(max 8) |
cookie_days |
integer |
1 to 365. |
attribution_model |
string |
last_click or first_click. (max 20) |
hold_days |
integer |
|
min_payout_minor |
integer |
|
payout_schedule |
string |
manual or monthly. (max 20) |
join_mode |
string |
apply or invite. (max 20) |
auto_approve |
boolean |
|
terms |
string | null |
(max 20000) |
preview_title |
string | null |
(max 400) |
preview_description |
string | null |
(max 1000) |
customer_discount |
string | null |
(max 1000) |
discount_code_prefix |
string | null |
(max 40) |
partner_monthly_cap_minor |
integer | null |
The most one partner earns in a calendar month (UTC), in minor units; null for no ceiling. |
embed_enabled |
boolean |
Turns the in-app referral panel on or off. Off, no session can be minted and the panel does not load. |
embed_origins |
string[] |
The sites allowed to frame the panel, like https://app.example.com. Replaces the list. https only (http://localhost:3000 for development), no wildcard, no path, at most 10. (<= 10 items) |
embed_config |
EmbedConfig |
The panel look, copy and sharing, replacing the old settings whole. Unknown keys are 400. |
approve_referrers |
boolean |
True (default): a new referrer is approved at once. False: they are pending until you approve them. |
welcome_config |
any |
The welcome's look and words, replacing the old settings whole; null for the defaults. Unknown keys are 400. |
welcome_show_name |
boolean |
Show a referrer's name to the friend (default true). An affiliate's is shown only when they agreed. |
welcome_page |
boolean |
A one-screen welcome at the link before your product (default false). |
allowed_hosts |
string[] |
Extra hosts your website tag may read the offer from, like www.example.com, beside the product URL's own and the panel's framing origins. Replaces the list. Host names only, at most 10. An AI assistant's key cannot change it. (<= 10 items) |
late_code_days |
integer |
How many days after sign-up a referral code may still be entered (default 14; 0 for never after). (>= 0; <= 365) |
lead_grant_daily_cap |
integer |
The most rewards a day (UTC) signups alone may earn across the program (default 100). (>= 0; <= 100000) |
invite_after_purchase |
boolean |
Give a friend who makes their first payment a referral link of their own (default false). |
ask_snooze_days |
integer |
How long "Not right now" in the panel lasts (default 30). (>= 1; <= 365) |
setup_payments |
SetupPayments |
(one of stripe, paddle, polar, whop, revenuecat, woocommerce, fastspring, lemonsqueezy, shopify, chargebee, recurly, gumroad, own_system, not_sure, null) |
setup_signups |
SetupSignups |
(one of own_backend, supabase, firebase, clerk, authjs, nocode, checkout_only, null) |
audience_eu_uk |
boolean |
Your audience includes the EU or the UK (default false). The setup status then asks for the tag's consent mode. |
application_questions |
string[] |
Up to three questions a partner answers when they apply (each 1 to 200 characters, no hidden characters). Answers are shown on their application; an empty list asks nothing. (<= 3 items) |
disclosure_text |
string |
The sentence partners add when they share (plain text, 10 to 280 characters). It may be edited, never emptied: partners must tell people they earn from the link (FTC, ASA). A new program starts with one for its currency's region. (min 10; max 280) |
leaderboard |
LeaderboardFields |
The leaderboard promoters see, merged onto the current setting; null switches it off. You always see your own. |
waitlist |
WaitlistFields |
A waitlist program's settings, merged onto the current ones. |
Answers
| Status | Body | |
|---|---|---|
| 200 | Program |
The program. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/partners
List a program's partners
Scope referrals:read. Another workspace's answers 404.
Partners by their name in this program, with all-time stats. An email appears only where the partner chose to share it. q keeps the partners whose display name contains it, ignoring case.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
status |
query | PartnerStatus |
(one of pending, approved, rejected, banned) |
q |
query | string |
1 to 80 characters, without *. Keeps partners whose display name contains it, ignoring case. (min 1; max 80) |
limit |
query | integer |
Page size, 1 to 100. Above 100 is clamped; anything else is the default. (default 50; >= 1; <= 100) |
cursor |
query | string |
The next_cursor from the previous page. Opaque; an invalid one is 400. (max 512) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PartnerList |
A page of partners. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/partners/{id}/approve
Approve a partner
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Approves an application. The partner gets their links, starts earning, and is emailed at once. Approving a partner who is already approved changes nothing and emails nobody, so a retry is safe. Takes no body.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Partner |
The partner. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/partners/{id}/reject
Reject a partner
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Rejects an application. Takes no body.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Partner |
The partner. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/commissions
List a program's commissions
Scope referrals:read. Another workspace's answers 404.
Commissions, newest first, each with its conversion and partner.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
status |
query | CommissionStatus |
(one of pending, approved, payable, paid, reversed, fraud) |
partner_id |
query | uuid |
|
from |
query | string |
(max 64) |
to |
query | string |
(max 64) |
adjustments |
query | string |
true for adjustments only, false for earned commissions only. (one of true, false) |
limit |
query | integer |
Page size, 1 to 100. Above 100 is clamped; anything else is the default. (default 50; >= 1; <= 100) |
cursor |
query | string |
The next_cursor from the previous page. Opaque; an invalid one is 400. (max 512) |
Answers
| Status | Body | |
|---|---|---|
| 200 | CommissionList |
A page of commissions. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/commissions/{id}/adjust
Adjust a commission
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Adds an adjustment on the same conversion, never an edit in place: a signed amount in minor units (not 0) and your reason, which is recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
amount_minor |
integer |
Signed minor units; not 0. (required) |
reason |
string |
Trimmed; recorded in the activity log. (required; min 1; max 500) |
{
"amount_minor": 1,
"reason": "<reason>"
}
Answers
| Status | Body | |
|---|---|---|
| 201 | Commission |
The adjustment. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/payouts
List a program's payouts
Scope referrals:read. Another workspace's answers 404.
Payouts, newest first. Payout details and export files stay in the dashboard.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
status |
query | PayoutStatus |
unclaimed: the provider holds it until the promoter claims it (PayPal's sign-up link, a reward email that bounced). returned: it came back before it was paid, and its commissions are back in Ready to pay. (one of draft, processing, unclaimed, paid, failed, returned, cancelled) |
limit |
query | integer |
Page size, 1 to 100. Above 100 is clamped; anything else is the default. (default 50; >= 1; <= 100) |
cursor |
query | string |
The next_cursor from the previous page. Opaque; an invalid one is 400. (max 512) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PayoutList |
A page of payouts. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/payouts/{id}/paid
Mark a payout paid
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
You paid outside BoltClaw; this records it with your reference. Send expected_minor (the
amount you were shown): if refunds have moved the total since, nothing is marked and the
answer is 409 CONFLICT naming the new figure. Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
reference |
string |
Your payment reference; trimmed. (required; min 1; max 200) |
expected_minor |
integer |
The amount you were shown. (>= 0) |
{
"reference": "<reference>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | Payout |
The payout. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/payout-providers
List a program's payout providers
Scope referrals:read. Another workspace's answers 404.
The payout accounts this program can pay through, by name and state: your own PayPal,
Wise, Stripe, Tremendous, Trolley or Tango account, or your own payout system (webhook).
Never a credential. Connecting or disconnecting one is done in the dashboard, on the
Payouts tab.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PayoutProviderList |
The connections. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/payout-providers/{id}/test
Test a payout provider connection
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Makes one harmless read at the provider with the stored credential and records whether it works, and why not. Moves no money.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PayoutProvider |
The connection, as the test left it. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/payout-settings
Get a program's payout settings
Scope referrals:read. Another workspace's answers 404.
How the program pays: its provider, the providers a promoter may choose among, the schedule, sending on a schedule (available only after a legal review), and the largest payout sent without a second confirmation.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PayoutSettings |
The settings. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/programs/{id}/payout-settings
Change how a program pays
Scope referrals:write. Another workspace's answers 404.
Choose the provider that pays promoters, the providers they may choose among (each must be
connected), the day of the month a monthly batch is made, and an optional largest payout.
auto_send can be turned on only once BoltClaw allows it, after a legal review; until
then a person sends every batch. Recorded in the activity log.
These are a send's own rails, so a key that is not an AI assistant's needs
referrals:payouts as well as referrals:write, as sending does; an assistant needs a
person's approval.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
provider |
PayoutProviderName |
How a payout is paid. manual: a file you pay from yourself. webhook: your own payout system. The rest: your own account at that provider. BoltClaw never holds the money. (one of manual, webhook, paypal, wise, stripe_connect, tremendous, trolley, tango) |
providers |
PayoutProviderName[] |
(<= 8 items) |
auto_send |
boolean |
|
payout_day |
integer |
(>= 1; <= 28) |
max_payout_minor |
integer | null |
(>= 1) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PayoutSettings |
The settings. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/payout-preview
See who is ready to be paid
Scope referrals:read. Another workspace's answers 404.
Ready to pay, before any batch: per currency and provider, the promoters a batch would pay and the provider's estimated fees (charged by that provider to your account; BoltClaw charges nothing), and every promoter who would wait, with why: payout details changed in the last 48 hours, payouts locked, an open fraud flag, below your minimum, or details the provider needs that they have not given yet.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PayoutPreview |
Ready to pay. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/payout-batches
Make a payout batch
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Turns every approved commission that can be paid into draft payouts, one per promoter per
currency, each with the provider that pays it. Nothing is sent: the batch waits for
send, a file export, or marking each payout paid. Below the minimum, negative, flagged
or held waits for the next batch.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PayoutBatchResult |
Nothing could be paid now; no batch was made. |
| 201 | PayoutBatchResult |
A batch was made. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/payout-batches/{id}
Get a payout batch, ready to send
Scope referrals:read. Another workspace's answers 404.
Every payout in the batch with its provider, whether it can be sent now (and why not),
the provider's estimated fee, and how it went once sent. sendable is exactly what send
would send: its payout ids, its totals per currency (send them back as
expected_totals), and the payouts large enough to need confirming by name.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PayoutBatch |
The batch. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/payout-batches/{id}/send
Send a payout batch through your payout provider
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Pays the batch's payouts from YOUR provider account (PayPal, Wise, Stripe, Tremendous,
Trolley, Tango) or asks your own payout system for each (referral.payout.requested).
BoltClaw never holds the money. expected_totals must equal what the batch would send
now (GET /referrals/payout-batches/{id}, sendable.totals), or nothing is sent; a
payout in sendable.large must be named in confirm_large. Each payout is sent once,
with an idempotency key made from its id; results arrive as referral.payout.* events.
Needs referrals:payouts, the key your own payout server holds, or an AI assistant with a
person's approval: referrals:write alone never sends money. Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
payout_ids |
uuid[] |
Send only these; every payout in sendable when absent. (<= 5000 items) |
expected_totals |
object |
What you expect to send, per currency, in minor units: {"USD": 12000}. Must equal sendable.totals. (required) |
confirm_large |
uuid[] |
(<= 5000 items) |
{
"expected_totals": {}
}
Answers
| Status | Body | |
|---|---|---|
| 200 | PayoutSendResult |
What happened to each payout. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/payout-batches/{id}/cancel-auto-send
Stop a scheduled send
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Stops a batch that was going to be sent by itself after its 24-hour notice. A stop: it always works, and the batch stays for a person to send. Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | AutoSendCancelled |
Whether a scheduled send was stopped. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/payouts/{id}
Get a payout
Scope referrals:read. Another workspace's answers 404.
One payout, with how it was sent and what its provider said (delivery).
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Payout |
The payout. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/payouts/{id}/details
Read where a requested payout goes
Scope referrals:payouts. Another workspace's answers 404.
For YOUR OWN payout system, after referral.payout.requested: the promoter's own payout
details for this one payout, while it waits to be paid. Refused (409) while the promoter's
details changed in the last 48 hours or they locked their payouts. Every read is recorded
in the activity log, as a payout export is. Needs referrals:payouts, which an AI
assistant's key never holds.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PayoutDetails |
Where to send it. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/payouts/{id}/result
Report what your payout system did
Scope referrals:payouts. Accepts Idempotency-Key. Another workspace's answers 404.
For YOUR OWN payout system: the payout was paid (with your reference), failed or
returned (with a reason). Failed and returned put its commissions back in Ready to pay.
The same answer again changes nothing; a payout already settled otherwise is 409. Needs
referrals:payouts, which an AI assistant's key never holds.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
status |
string |
(required; one of paid, failed, returned) |
reference |
string |
Your own payment reference (paid only). (max 200) |
reason |
string |
Why it failed or came back (required unless paid). (one of invalid_destination, recipient_not_ready, insufficient_funds, returned, other) |
{
"status": "paid"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | Payout |
The payout. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/tax
Get a program's tax settings
Scope referrals:read. Another workspace's answers 404.
Whether this program's payouts wait for a tax form (tax_forms) or for the promoter's own
invoice (invoice_required), and whether you issue self-billed invoices for your EU and UK
business promoters (self_billing). Also whether your own tax details are complete, never
the details themselves (your EIN, legal name and addresses are entered in the dashboard),
and the table of which payout providers count toward your 1099-NEC, each with why and its
source. BoltClaw prepares these forms; it does not give tax advice or file anything for
you. See Tax forms and 1099s.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | TaxSettings |
The settings. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/programs/{id}/tax
Change whether a program's payouts wait for tax forms or invoices
Scope referrals:write. Another workspace's answers 404.
Send only what you change. tax_forms: off; us_only (a US person needs a W-9, and
anyone else signs a short statement that they are not a US person); or all (everyone
needs a W-9, W-8BEN or W-8BEN-E). A promoter whose required form is missing, expired or
replaced waits in Ready to pay until they sign. invoice_required: each payout waits for
the promoter's own invoice (a promoter who self-bills never waits for one).
self_billing: you issue the invoices for promoters who accept your self-billing
agreement; turning it on needs your invoicing details first (409 until they are
complete). An archived program is 409. Recorded in the activity log with the values
before and after.
Turning a requirement off lets money that waited for a form be paid, so a key needs
referrals:payouts as well as referrals:write for this call, as changing how payouts
are sent does; an AI assistant needs a person's approval.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
tax_forms |
TaxRequirement |
Whether a program's payouts wait for a tax form. off: never. us_only: a US person needs a W-9, and anyone else signs a short statement that they are not a US person. all: everyone needs a W-9, W-8BEN or W-8BEN-E. (one of off, us_only, all) |
invoice_required |
boolean |
|
self_billing |
boolean |
Turning it on needs your invoicing details first (409 until then). |
Answers
| Status | Body | |
|---|---|---|
| 200 | TaxSettings |
The settings. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/tax-forms
List each promoter's tax form status
Scope referrals:read. Another workspace's answers 404.
Every approved or pending promoter of the program with their latest tax form for your
business: its type (W-9, W-8BEN, W-8BEN-E, or the statement that they are not a US
person), revision, status, when it was signed and when it expires. STATUS ONLY: never a
tax number, a legal name or anything the form says. Opening a form is done in the
dashboard, with two-step verification. requirement_met is false while their payouts
wait for a form; waiting_since and forfeitable_minor say since when, and how much.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
limit |
query | integer |
Page size, 1 to 100. Above 100 is clamped; anything else is the default. (default 50; >= 1; <= 100) |
cursor |
query | string |
The next_cursor from the previous page. Opaque; an invalid one is 400. (max 512) |
Answers
| Status | Body | |
|---|---|---|
| 200 | TaxFormStatusList |
A page of promoters, by membership id. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/partners/{id}/forfeit
Forfeit a promoter's money held for a missing tax form
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Gives up, for good, every approved commission of this promoter that is waiting for a tax
form in this program: they will not be paid it, and no payout will ever include it. Only
money held for a missing, expired or replaced form can be forfeited (409 otherwise, and
when nothing is waiting); what they earn afterwards is not touched. Send a reason of 3 to
500 characters: it is recorded in the activity log with what was given up. BoltClaw never
forfeits anything by itself.
Needs referrals:payouts as well as referrals:write, as sending money does, or an AI
assistant with a person's approval: referrals:write alone never gives a promoter's money
up.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
reason |
string |
Why, recorded in the activity log. (required; min 3; max 500) |
{
"reason": "<reason>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | ForfeitResult |
What was given up. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/tax-years/{year}
Get a year's 1099-NEC data
Scope referrals:read. Another workspace's answers 404.
Everyone your referral programs paid in a calendar year, as one person per account, with
how each was paid (per provider and currency), their tax form's status, and whether they
belong on your 1099-NEC (included) and why, in a sentence. Warnings say what to fix
before you file: no form, no tax number, backup withholding (24%) may apply, a replaced
form, an expired W-8, money in another currency. Only US dollars paid through the
providers your 1099 table counts are reportable, against the year's threshold
(threshold_minor). No tax number and no legal name: the files themselves (the IRS
IRIS CSV, a general CSV and Copy B) are made in the dashboard, with two-step verification.
BoltClaw never files, never withholds and never checks a tax number with the IRS.
Parameters
| Name | In | Type | |
|---|---|---|---|
year |
path | string |
A calendar year, four digits (2026). A year before 2026, or after this one, is 400; anything that is not four digits is 404. (required; min 4; max 4) |
Answers
| Status | Body | |
|---|---|---|
| 200 | TaxYear |
The year. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/payouts/{id}/invoices
List a payout's invoices
Scope referrals:read. Another workspace's answers 404.
The invoices for one payout, newest first: the self-billed invoice you issued in the
promoter's name, the promoter's own uploaded invoice, and any they replaced. Each says
whether it was accepted, disputed (with the promoter's reason) or replaced. Read a PDF with
POST /referrals/invoices/{id}/link.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | InvoiceList |
The payout's invoices. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/invoices/{id}/link
Get a download link for an invoice
Scope referrals:read. Accepts Idempotency-Key. Another workspace's answers 404.
A link to one invoice's PDF, good for ten minutes and one download. It names a promoter's business, so it needs the payouts permission and every link is recorded in the activity log. Treat the link as a secret; ask again for a new one.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | FileLink |
The link. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/payouts/{id}/statement-link
Get a download link for a payout statement
Scope referrals:read. Accepts Idempotency-Key. Another workspace's answers 404.
A link to the payout's statement as a PDF (who paid whom, how much, when and for which period), good for ten minutes and one download. The statement is what a promoter without a self-billed or uploaded invoice keeps. Treat the link as a secret; ask again for a new one.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | FileLink |
The link. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/payouts/{id}/invoice
Issue a paid payout's self-billed invoice
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
For a program that self-bills: issues the self-billed invoice of a paid payout that has
none (its promoter's or your own invoicing details were missing when it was paid). A
payout that already has one answers that invoice, so a repeat changes nothing. 409 with
what is missing when it cannot be issued: the payout is not paid, the promoter does not
self-bill with this program, your invoicing details are incomplete, or the promoter is in
your own country and your domestic VAT rate is not set. Moves no money.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Invoice |
The invoice. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/invoices/{id}/reissue
Issue a corrected invoice for a disputed one
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
A promoter disputed a self-billed invoice within its 14 days. Fix what they said was wrong
(ask them to correct their own details, or correct yours in the dashboard), then call this:
the disputed invoice becomes void and a new one is issued, with the next number in the
promoter's series, from the details as they are now. It starts its own 14 days. Only a
disputed self-billed invoice can be replaced (409 otherwise). Recorded in the activity
log. Moves no money.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 201 | Invoice |
The corrected invoice. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/brand
Get the brand your partners see
Scope referrals:read.
Your brand as partners see it on your own partner portal, the join page, the in-app panel,
the friend's welcome and their emails: its name, logos, colours, corner radius, support
address, sender name and whether "Powered by BoltClaw" shows. Without program_id, the
workspace's brand, which every program uses; with it, that program's own override, where a
field left null follows the workspace's brand. effective is what partners of that
program actually see, both merged. support_email_verified says whether the support
address was confirmed with a code: only then is it the reply-to of partners' emails. See
White-label portal.
Parameters
| Name | In | Type | |
|---|---|---|---|
program_id |
query | uuid |
A program's own override. Leave it out for the workspace's brand. |
Answers
| Status | Body | |
|---|---|---|
| 200 | Brand |
The brand. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/brand
Change the brand your partners see
Scope referrals:write.
Send only what you change; null clears a field (a program's override then follows the
workspace's brand again). name up to 80 characters; accent and background as
#rrggbb; radius from 0 to 24; sender_name up to 60 characters, the name partners'
emails come from (the address stays BoltClaw's); support_email, which becomes the
reply-to of partners' emails only after you confirm it with a code (changing it unconfirms
it); show_powered_by, off unless you turn it on. A logo or icon is uploaded first with
POST /referrals/brand/uploads, then saved here by sending the path it answered as
logo_light_path, logo_dark_path or favicon_path (or null to remove it). With
program_id, the change is that program's override. Recorded in the activity log with the
names of the fields changed. An AI assistant asks a person first, unless your organisation
lets assistants change it without asking.
Request body (required)
| Field | Type | |
|---|---|---|
program_id |
uuid | null |
Change this program's override instead of the workspace's brand. |
name |
string | null |
No control characters and no angle brackets. (max 80) |
accent |
string | null |
|
background |
string | null |
|
radius |
integer | null |
(>= 0; <= 24) |
support_email |
email | null |
Unconfirmed until its code comes back. |
show_powered_by |
boolean | null |
|
sender_name |
string | null |
No control characters and none of these: " < > @ , ; : \ (max 60) |
logo_light_path |
string | null |
The path an upload answered, or null to remove the logo. |
logo_dark_path |
string | null |
The path an upload answered, or null to remove the logo for dark backgrounds. |
favicon_path |
string | null |
The path an upload answered, or null to remove the icon. |
Answers
| Status | Body | |
|---|---|---|
| 200 | Brand |
The brand. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/brand/uploads
Get an upload URL for a logo or icon
Scope referrals:write. Accepts Idempotency-Key.
A signed URL for one file of your brand: logo_light, logo_dark or favicon, as a PNG,
JPEG or WebP image of at most 2 MiB (no SVG). PUT the file's bytes to upload_url with
its content type, then save it with PATCH /referrals/brand, sending path as the slot's
field. Nothing changes until that PATCH: an upload that is never saved is not shown to
anyone. The URL works for one file and expires (expires_at); treat it as a secret.
upload_url is null in a replay of the same Idempotency-Key: ask for a new one.
Request body (required)
| Field | Type | |
|---|---|---|
program_id |
uuid | null |
For a program's override. |
slot |
BrandFileSlot |
logo_light: the logo on light backgrounds. logo_dark: on dark ones. favicon: the browser tab's icon. (required; one of logo_light, logo_dark, favicon) |
content_type |
string |
(required; one of image/png, image/jpeg, image/webp) |
{
"slot": "logo_light",
"content_type": "image/png"
}
Answers
| Status | Body | |
|---|---|---|
| 201 | BrandUpload |
Where to upload. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/brand/support-email/verification
Send a code to confirm the support address
Scope referrals:write. Accepts Idempotency-Key.
Emails a six-digit code to the support address saved on the brand (the workspace's, or the
program's with program_id), good for 15 minutes. Confirm it with
POST /referrals/brand/support-email/verification/confirm; until then partners' emails
have no reply-to. At most five codes a day for one brand and twenty for the workspace
(429 past that). 409 when the address is already confirmed, 400 when none is saved.
The answer repeats the address the code went to.
Request body (required)
| Field | Type | |
|---|---|---|
program_id |
uuid | null |
A program's override. |
Answers
| Status | Body | |
|---|---|---|
| 200 | BrandEmailCodeSent |
The code was sent. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/brand/support-email/verification/confirm
Confirm the support address with its code
Scope referrals:write. Accepts Idempotency-Key.
The six-digit code from the email POST /referrals/brand/support-email/verification sent.
Each code has five tries; a wrong, used or expired one is 400 (send a new one). Once
confirmed, the address is the reply-to of partners' emails about the program, until it is
changed. Recorded in the activity log.
Request body (required)
| Field | Type | |
|---|---|---|
program_id |
uuid | null |
|
code |
string |
The six digits from the email. (required) |
{
"code": "<code>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | Brand |
The brand, with the address confirmed. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/domains
List your own link and portal domains
Scope referrals:read.
Every domain of your own connected to your referral programs: a links domain
(go.example.com, where links read https://go.example.com/<code>) or a partner portal
(partners.example.com), each with where it stands and the exact DNS records still to set.
See Your own domain.
Answers
| Status | Body | |
|---|---|---|
| 200 | DomainList |
The domains. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/domains
Connect one of your own domains
Scope referrals:write. Accepts Idempotency-Key.
Claims a subdomain of yours (go.example.com for links, partners.example.com for the
partner portal; never a domain's root, a wildcard or an IP address) for every program
(all_programs, the default) or the ones in program_ids. Nothing is served yet: the
answer's records are the exact DNS records to add at your DNS host, in order. First a
TXT record, _boltclaw.<host> with the value bc-verify=<token>, which proves the domain
is yours; once it is seen, the CNAME record our hosting provider recommends for it appears
in records (it differs per domain: never use one from another domain), and sometimes a
second TXT record our hosting provider asks for. Check progress with
POST /referrals/domains/{id}/check. A domain counts against your plan's domains from the
moment it is added (402 past it); 409 when it is already yours, or connected to
another account.
An AI assistant always asks a person first: it decides where your links and portal will answer. In the dashboard, adding a domain also needs two-step verification; over the API that approval stands in for it.
Request body (required)
| Field | Type | |
|---|---|---|
host |
string |
A subdomain of yours, such as go.example.com. Never a domain's root, a wildcard or an IP address. (required; max 300) |
kind |
DomainKind |
links: your referral links answer on it (https://go.example.com/<code>). portal: your partner portal and join pages, and links too. (required; one of links, portal) |
all_programs |
boolean | null |
Serve every program, new ones included. True when left out. |
program_ids |
array | null |
The programs it serves, when all_programs is false (1 to 50). (<= 50 items) |
{
"host": "<host>",
"kind": "links"
}
Answers
| Status | Body | |
|---|---|---|
| 201 | Domain |
The domain, with the records to set. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 402 | ErrorEnvelope |
PLAN_LIMIT: the organisation's plan does not allow more of this. The message names the allowance. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/domains/{id}
Get one of your domains
Scope referrals:read. Another workspace's answers 404.
One domain: where it stands (status, and problem, a sentence saying what is in the
way), the exact DNS records to set and whether each is in place, whether a CAA record on
your domain keeps the certificate from being issued, and, once active, its address.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Domain |
The domain. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
DELETE /v1/referrals/domains/{id}
Remove one of your domains
Scope referrals:write. Another workspace's answers 404.
The domain stops answering at once: links on it stop working (each still works at its
https://boltclaw.ai/r/<code> address) and its portal pages are gone. It is then taken off
our hosting provider, so no record of ours is left pointing at it; remove your own DNS
records for it too. Recorded in the activity log. An AI assistant always asks a person
first.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | DomainRemoved |
Removed. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/domains/{id}/check
Check a domain's DNS records now
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Looks at the domain's records now instead of waiting for the next check (pending domains are looked at every ten minutes, active ones every day), and moves it on when they are in place: the TXT record proves the domain is yours, the CNAME record routes it, and then the certificate is issued. At most once every 15 seconds per domain: a check asked sooner answers the domain as it stands. Nothing is ever removed by a check.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Domain |
The domain, as it stands after the check. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/listing
Get a program's directory listing
Scope referrals:read. Another workspace's answers 404.
What the partner directory shows about the program, and whether it is listed. The
directory is a page inside BoltClaw that 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 do. missing names every condition not met yet (an active program anybody may
apply to, terms, a reward for partners, an organisation at least a week old and not
suspended, and a real sale through your billing system or a finished setup checklist);
listing is refused until it is empty. preview is the card partners will see. state is
listed, unlisted, held (reports from three different accounts hid it until
BoltClaw reviews it) or delisted. See Recruiting partners.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ListingSettings |
The listing. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/programs/{id}/listing
Change a program's directory listing
Scope referrals:write. Another workspace's answers 404.
Sets what the partner directory shows (category, pitch of at most 280 characters,
countries, languages, the logo's logo_path from
POST /referrals/programs/{id}/listing/logo) and whether the program is listed. The
reward summary, cookie window and payout schedule come from the program itself, never
from text you type. Turning listed on is 409 with every reason while a condition is
not met. An AI assistant asks a person first, because listing publishes the program to
every signed-in partner; your organisation may let assistants change it without asking.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
listed |
boolean |
|
category |
DirectoryCategory |
(one of software, ai_tools, education, finance, health, ecommerce, creator_tools, agencies, other) |
pitch |
string | null |
Plain text: no HTML. (max 280) |
countries |
array | null |
Two-letter country codes the program welcomes partners from. (<= 60 items) |
languages |
array | null |
Two-letter language codes, like en or de. (<= 20 items) |
logo_path |
string | null |
The path from the logo upload, or null to remove it. (max 200) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ListingSettings |
The listing, as it stands now. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/listing/logo
Get a URL to upload the listing's logo
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
A signed URL for one PNG, JPEG or WebP file of at most 2 MiB. PUT the file's bytes
there with its content type, then save it with PATCH /referrals/programs/{id}/listing,
sending the answer's path as logo_path. The file is accepted only if its bytes are an
image of that type. upload_url is null in a replay of the same Idempotency-Key.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
content_type |
string |
(required; one of image/png, image/jpeg, image/webp) |
{
"content_type": "image/png"
}
Answers
| Status | Body | |
|---|---|---|
| 201 | ImageUpload |
Where to upload the file. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/broadcasts
List a program's broadcasts
Scope referrals:read. Another workspace's answers 404.
The program's messages to its promoters, newest first (at most 50): drafts, ones being sent and sent ones, with who they went to and how many were emailed, muted or failed.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | BroadcastList |
The broadcasts. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/broadcasts
Write a broadcast to a program's promoters
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
A draft, to everyone approved in the program, one group or level, or a list of up to 500
promoters you pick. Nothing is sent until POST /referrals/broadcasts/{id}/send. The
body is a small Markdown subset: paragraphs, bold, italic, [links](https://...)
and lists; no HTML, no images, https links only, and every link is shown with its domain.
{name}, {link} and {code} are filled with each promoter's own display name, referral
link and discount code. Subject at most 120 characters, body at most 5,000.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
subject |
string |
(required; max 120) |
body |
string |
Markdown subset: paragraphs, bold, italic, links and lists. No HTML, no images, https links only. {name}, {link} and {code} are filled per promoter. (required; max 5000) |
audience |
BroadcastAudience |
{
"subject": "<subject>",
"body": "<body>"
}
Answers
| Status | Body | |
|---|---|---|
| 201 | Broadcast |
The draft. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/broadcasts/{id}
Get one broadcast
Scope referrals:read. Another workspace's answers 404.
One broadcast: its subject and Markdown, who it goes to, the hosts it links to, whether
its current text was test-sent, and once sent, how many promoters it reached: emailed,
muted (they still see it on their program page), your own users in the in-app panel
(referrers, never emailed by BoltClaw), failed and skipped.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Broadcast |
The broadcast. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/broadcasts/{id}
Change a draft broadcast
Scope referrals:write. Another workspace's answers 404.
Changes a draft's subject, body or audience. A sent broadcast is a record and is 409:
write a new one.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
subject |
string |
(max 120) |
body |
string |
(max 5000) |
audience |
BroadcastAudience |
Answers
| Status | Body | |
|---|---|---|
| 200 | Broadcast |
The draft. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/broadcasts/{id}/test
Send yourself a test of a draft
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Emails the draft, as it reads now, to the address of the person who made this API key
and nobody else, with sample values in {name}, {link} and {code}. At most ten per
draft, one every 15 seconds. The dashboard sends a broadcast only after a test of its
current text.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | BroadcastTested |
The draft, with where the test went. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/broadcasts/{id}/send
Send a broadcast to a program's promoters
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Sends the draft. Every approved promoter it names sees it on their program page; those
with a BoltClaw account are emailed (unless they muted the program's messages: every email
has a one-click unsubscribe), a few a minute; your own users in the in-app panel are never
emailed by BoltClaw, so referral.broadcast.sent tells your webhook, and
GET /referrals/broadcasts/{id}/referrers names them for your own message. A program may
send 2 broadcasts in any 24 hours and 10 in any 30 days (429 past either). An AI
assistant always asks a person first: it messages people.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Broadcast |
The broadcast, sending or sent. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/broadcasts/{id}/referrers
List your own users a broadcast reached
Scope referrals:read. Another workspace's answers 404.
The referrers (your own users, made by your server) a sent broadcast reached, 200 a page,
by your own id for each (external_id), with their name, link and code, so your system can
send them the message itself. BoltClaw emails none of them.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
cursor |
query | string |
The next_cursor from the previous page. Opaque; an invalid one is 400. (max 512) |
Answers
| Status | Body | |
|---|---|---|
| 200 | BroadcastReferrerList |
A page of referrers. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/imports/preview
Check a partner list before importing it
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Checks up to 500 rows (as rows, or a csv with a header row: name, email, and
optionally code, group, rate, balance, currency) against the program and writes
nothing: rows that would fail and why (a code taken, a person already invited or
promoting, a group that does not exist, a balance in another currency). Addresses are
hashed on arrival and never stored. Send a larger file in parts. See
Recruiting partners.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
rows |
ImportRow[] |
(<= 500 items; >= 1 items) |
csv |
string |
A CSV with a header row; at most 500 rows a call. (max 65000) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ImportPreview |
What an import would do. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/imports
List a program's imports
Scope referrals:read. Another workspace's answers 404.
The partner lists brought into the program, newest first, with how many people each invited and how many claimed.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ImportList |
The imports. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/imports
Import a partner list
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Makes an invite reserved for each row, all or nothing (409 with the first row's reason
when any fails: preview first). Each invite holds that person's vanity code (no one else
can take it meanwhile), group, custom rate and opening balance. BoltClaw sends nobody an
email: the answer has each person's personal link, and you send it from your own email.
When they sign in through it they are approved (if your plan has room) with their code,
group and rate, and an opening balance becomes a bonus paid by hand ("Balance brought
over") only then. import_id continues an import begun by an earlier part of the same
file. A file with opening balances also needs referrals:payouts on the key. An AI
assistant always asks a person first. Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
rows |
ImportRow[] |
(<= 500 items; >= 1 items) |
csv |
string |
A CSV with a header row; at most 500 rows a call. (max 65000) |
import_id |
uuid |
Continue an import begun by an earlier part of the file. |
Answers
| Status | Body | |
|---|---|---|
| 201 | ImportCommitted |
The import, with each person's link. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/imports/{id}/invites
List an import's personal links
Scope referrals:write. Another workspace's answers 404.
Everyone in one import with their personal link and whether they claimed it, 1,000 a page,
for the file you send from your own email. The links are credentials (each joins one
person), so this needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
cursor |
query | string |
The next_cursor from the previous page. Opaque; an invalid one is 400. (max 512) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ImportInviteList |
A page of invites. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/library
List a program's file library
Scope referrals:read. Another workspace's answers 404.
The images, text snippets and links partners see on their program page (an item with a
group_id only by that group's), in order. An image item is shown once its file is in
(ready).
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | LibraryItemList |
The library. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/library
Add to a program's file library
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
An image (its file follows through POST /referrals/library/{id}/upload), a snippet
of at most 2,000 characters whose {link} and {code} partners see filled with their
own (with the program's disclosure line after it), or a link to a brand guide or press
kit (https only; its domain is shown). group_id shows it to one group only; in_panel
shows a snippet to your own users in the in-app panel too. At most 200 items a program.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
kind |
string |
(required; one of image, snippet, link) |
title |
string |
(required; max 120) |
body |
string |
A snippet's text. (max 2000) |
url |
string |
A link's https address. (max 2048) |
group_id |
uuid | null |
|
in_panel |
boolean |
A snippet only: shown to your own users in the in-app panel too. |
position |
integer |
(>= 0; <= 10000) |
{
"kind": "image",
"title": "<title>"
}
Answers
| Status | Body | |
|---|---|---|
| 201 | LibraryItem |
The item. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/library/{id}
Change a library item
Scope referrals:write. Another workspace's answers 404.
Changes an item's title, text, link, group, place in the order, or whether a snippet is in
the panel. For an image, image_path saves the file uploaded through
POST /referrals/library/{id}/upload; it is accepted only if its bytes are a PNG, JPEG or
WebP image.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
title |
string |
(max 120) |
body |
string |
(max 2000) |
url |
string |
(max 2048) |
group_id |
uuid | null |
|
in_panel |
boolean |
|
position |
integer |
(>= 0; <= 10000) |
image_path |
string |
An image item's file, from its upload. (max 200) |
Answers
| Status | Body | |
|---|---|---|
| 200 | LibraryItem |
The item. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
DELETE /v1/referrals/library/{id}
Delete a library item
Scope referrals:write. Another workspace's answers 404.
The item and its file are gone; partners no longer see it. An AI assistant asks a person first, unless your organisation lets assistants do it without asking.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | LibraryItemDeleted |
Deleted. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/library/{id}/upload
Get a URL to upload a library image
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
A signed URL for an image item's file: one PNG, JPEG or WebP of at most 2 MiB (no SVG,
HTML or PDF). PUT the bytes there with their content type, then save it with
PATCH /referrals/library/{id} and the answer's path as image_path. upload_url is
null in a replay of the same Idempotency-Key.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
content_type |
string |
(required; one of image/png, image/jpeg, image/webp) |
{
"content_type": "image/png"
}
Answers
| Status | Body | |
|---|---|---|
| 201 | ImageUpload |
Where to upload the file. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/link-hosts
List the sites a program's links may go to
Scope referrals:read. Another workspace's answers 404.
A partner's link can open any page of your site: https://boltclaw.ai/r/<code>?to=/pricing
goes to your product URL's host plus /pricing, and &host=docs.example.com to another
of your hosts. Those other hosts are the program's allowed_hosts, each used only once
you prove it is yours with the TXT record shown here (_boltclaw.<host> with the value
bc-verify=<token>), then POST /referrals/programs/{id}/link-hosts/check. Anything
else falls back to the product URL: a link is never an open redirect.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | LinkHostList |
The hosts. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/link-hosts/check
Check a site's TXT record now
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Looks up _boltclaw.<host> now for one of the program's allowed hosts; when it holds the
value shown, links may go to that host. A check that finds the record gone takes that
away again. At most once every 10 seconds per host.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
host |
string |
One of the program's allowed hosts. (required; max 300) |
{
"host": "<host>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | LinkHostList |
The hosts, after the check. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/leaderboard
Read a program's leaderboard
Scope referrals:read. Another workspace's answers 404.
Every promoter with something counted, ranked by the program's leaderboard metric over its
period (or the metric and period you ask for), with each one's membership id, name and,
for one of your own users, your external_id. This is your view: promoters see only the
first places, each named as its promoter chose (hidden, initials or their name), and money
only when the board shows amounts. What counts is counted activity: a sale no person or
rule holds, not refunded in full, not disputed; on a waitlist, confirmed friends. Counted
again at most every 10 minutes. See Campaigns.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
metric |
query | BoardMetric |
What to rank by. Left out, the program's own setting. (one of customers, sales, revenue, friends) |
period |
query | BoardPeriod |
Over which window. Left out, the program's own setting. (one of month, 30d, quarter, all) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Leaderboard |
The leaderboard. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/contests
List a program's contests
Scope referrals:read. Another workspace's answers 404.
A program's contests, newest first, each with its prizes and what it is now (scheduled,
running, ended, awarded or cancelled; the clock decides, not a job).
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ContestList |
The contests. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/contests
Create a contest
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
A window (up to a year), a metric as a leaderboard's, who may enter (every promoter, or one group or level), a minimum to qualify, and prizes by place: cash (paid as a bonus by hand) or a reward that is not cash (credit, a gift card, a free period or a perk). Standings count only what happens inside the window. Nothing is paid until a person awards the prizes after it ends. At most 10 contests a program may be scheduled or running at once.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
description |
string | null |
(max 1000) |
starts_at |
date-time |
(required) |
ends_at |
date-time |
At most 366 days after the start. (required) |
metric |
BoardMetric |
customers: paying customers brought; sales: payments; revenue: their amount, in minor units; friends: on a waitlist, friends who joined and confirmed. (one of customers, sales, revenue, friends) |
group_id |
uuid | null |
|
prizes |
ContestPrizeInput[] |
(required; <= 20 items; >= 1 items) |
minimum |
integer |
(>= 0) |
name |
string |
(required; min 1; max 80) |
{
"starts_at": "2026-03-04T17:02:10.885210+00:00",
"ends_at": "2026-03-04T17:02:10.885210+00:00",
"prizes": [],
"name": "<name>"
}
Answers
| Status | Body | |
|---|---|---|
| 201 | Contest |
The contest. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/contests/{id}
Read a contest and its standings
Scope referrals:read. Another workspace's answers 404.
The contest, its standings (each promoter's place among those who reached the minimum,
whether they are under review, and the prize the place wins as things stand) and whether
the prizes may be awarded now (award.possible), and if not, why and from when.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ContestDetail |
The contest. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/contests/{id}
Change a contest
Scope referrals:write. Another workspace's answers 404.
Before it starts, anything. Once it has started, only its name and description (409 for
anything else): its window, what it counts, who may enter, its minimum and its prizes stay
as promoters saw them. Once it is awarded or cancelled, nothing (409).
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
description |
string | null |
(max 1000) |
starts_at |
date-time |
|
ends_at |
date-time |
At most 366 days after the start. |
metric |
BoardMetric |
customers: paying customers brought; sales: payments; revenue: their amount, in minor units; friends: on a waitlist, friends who joined and confirmed. (one of customers, sales, revenue, friends) |
group_id |
uuid | null |
|
prizes |
ContestPrizeInput[] |
(<= 20 items; >= 1 items) |
minimum |
integer |
(>= 0) |
name |
string |
(min 1; max 80) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Contest |
The contest. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/contests/{id}/cancel
Cancel a contest
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Ends a contest that was not awarded, with a reason promoters in it are shown. No prize is paid. An AI assistant always asks a person first. Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
reason |
string |
(required; min 3; max 500) |
{
"reason": "<reason>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | Contest |
The contest, cancelled. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/contests/{id}/award
Award a contest's prizes
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Gives each winning place its prize, once: a cash prize as a bonus paid by hand ("Contest:
409, with the reason) before the contest ends, while any winner is
under review, and until the last hold on what the winners counted has passed
(award.awardable_at on the contest). BoltClaw never awards by itself. Needs the
referrals.payouts permission. A key that is not an AI assistant's must also hold the
referrals:payouts scope (403 INSUFFICIENT_SCOPE otherwise), as an import with opening
balances must; an AI assistant always asks a person first, and the person who approves may
not be one of the winners. Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ContestAward |
The contest, awarded, and what each place was given. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/entries
List a waitlist's entries
Scope referrals:read. Another workspace's answers 404.
A waitlist program's line, in place order: each entry's place (null until they confirm),
confirmed friends, whether they are disqualified and why, and who referred them. Places move
as friends confirm, so a page is the line as it was when read. No address is ever in an
entry: people who joined on the hosted page are exported to CSV in the dashboard only.
filter is all (default), confirmed, unconfirmed, disqualified or invited.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
filter |
query | string |
(one of all, confirmed, unconfirmed, disqualified, invited) |
limit |
query | integer |
Page size, 1 to 200. Above 200 is clamped; anything else is the default. (default 50; >= 1; <= 200) |
cursor |
query | string |
The next_cursor from the previous page. Opaque; an invalid one is 400. (max 512) |
Answers
| Status | Body | |
|---|---|---|
| 200 | WaitlistEntryList |
A page of entries. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/entries
Add a person to a waitlist, or update them
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Your own sign-up form's call: your id for the person (external_id), the referral code
they arrived with if any, and later verified: true once they confirmed their address
(that cannot be taken back). 201 for a new entry, 200 for an existing one. An entry
counts toward the organisation's referrers, never its partners. An email, when you send
one, is kept only as a keyed fingerprint for the self-referral check: never stored or
returned. A key may create at most a few hundred new entries an hour (429 past it).
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
external_id |
string |
(required; min 1; max 200) |
name |
string | null |
(max 200) |
email |
string |
Kept only as a keyed fingerprint for the self-referral check. (max 320) |
code |
string |
A vanity code for their own link, on creation only. (max 40) |
referral_code |
string |
The code of the link they arrived with. (max 40) |
verified |
boolean |
|
status |
WaitlistEntryStatus |
(one of waiting, invited, joined) |
{
"external_id": "<external_id>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | WaitlistEntry |
The entry, updated. |
| 201 | WaitlistEntry |
The entry, new. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 402 | ErrorEnvelope |
PLAN_LIMIT: the organisation's plan does not allow more of this. The message names the allowance. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/entries/{id}
Read a waitlist entry
Scope referrals:read. Another workspace's answers 404.
One entry, with the person's own view of their place (state), as the in-app panel shows it to them.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | WaitlistEntryDetail |
The entry. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/entries/{id}
Confirm an entry, or set its status
Scope referrals:write. Another workspace's answers 404.
verified: true once the person confirmed their address (it moves the friend who referred
them up; it cannot be taken back); status is waiting, invited or joined, for your own
record. Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
verified |
boolean |
|
status |
WaitlistEntryStatus |
(one of waiting, invited, joined) |
Answers
| Status | Body | |
|---|---|---|
| 200 | WaitlistEntry |
The entry. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/entries/{id}/disqualify
Disqualify a waitlist entry
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Takes the entry out of the line (it keeps its row) and stops it moving anybody up, with a reason the person never sees. An AI assistant always asks a person first. Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
reason |
string |
(required; min 3; max 500) |
{
"reason": "<reason>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | WaitlistEntry |
The entry. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/entries/{id}/requalify
Put a disqualified entry back in line
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Undoes a disqualification, with a reason. The entry takes its place by its join order again. Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
reason |
string |
(required; min 3; max 500) |
{
"reason": "<reason>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | WaitlistEntry |
The entry. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/entries/invite
Invite waitlist entries
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Marks up to 500 entries invited and emails the ones who joined on the hosted page your
message (a subject of one line, at most 140 characters, and a body in the broadcasts'
Markdown subset). Their addresses never leave BoltClaw. An entry from your own form is
marked only, for your own system to email. An AI assistant always asks a person first.
Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
entry_ids |
uuid[] |
(required; <= 500 items; >= 1 items) |
subject |
string |
One line. (required; min 1; max 140) |
message |
string |
The broadcasts' Markdown subset: paragraphs, bold, italic, lists and https links. (required; max 6000) |
{
"entry_ids": "3f8c1e42-9b7a-4d61-a0f5-2c9e1b7d4a83",
"subject": "<subject>",
"message": "<message>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | WaitlistInvited |
How many were invited and emailed. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/second-tier
Read a program's second tier
Scope referrals:read. Another workspace's answers 404.
Whether a partner earns a share of what the partners they recruit earn, and how much: at most 20% of the recruit's own cash commission on sales, for 1 to 24 months after the recruit joined, one level only, never on a partner's own purchases. With the clause the terms carry while it is on, and why it is fenced. See Two-tier.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | SecondTierSettings |
The second tier. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/programs/{id}/second-tier
Switch a program's second tier on or off
Scope referrals:write. Another workspace's answers 404.
enabled: true with percent_bps (1 to 2000) and months (1 to 24) pays a recruiter that
share of each recruit's cash commission on sales, from the next sale on; the clause is added
to the program's terms and every approved partner is asked to accept the new version before
their next payout. enabled: false stops it (what was earned stays). Off by default.
Switching it on is always asked first for an AI assistant. Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
enabled |
boolean |
(required) |
percent_bps |
integer |
(>= 1; <= 2000) |
months |
integer | null |
(>= 1; <= 24) |
{
"enabled": true
}
Answers
| Status | Body | |
|---|---|---|
| 200 | SecondTierSettings |
The second tier. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/waitlist/end
End a waitlist
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Ends the waitlist for good: nobody new can join, through the hosted page or through
POST /referrals/programs/{id}/entries, and everybody already in keeps their place and
may still be updated or invited. The addresses the hosted page holds are deleted 90 days
after the end (POST /referrals/programs/{id}/waitlist/forget deletes them now); once an
address is gone, its entrant's own link stops working. Ending again keeps the first end.
A program that is not a waitlist is 409. To stop new entries for a while instead, set
waitlist.closed on the program. An AI assistant always asks a person first. Recorded in
the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | WaitlistEnded |
When the waitlist ended (forgotten is 0). |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/waitlist/forget
Delete a waitlist's addresses now
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Deletes every address the waitlist's hosted page holds, and every entrant's own link, now,
and ends the waitlist if it had not ended (as POST /referrals/programs/{id}/waitlist/end
does). Entries stay in the line by your own external_id; nothing can be emailed to them
any more. It cannot be undone. A program that is not a waitlist is 409. An AI assistant
always asks a person first. Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | WaitlistEnded |
When the waitlist ended, and how many entries' addresses were deleted by this call. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/waitlist/rotate-page
Give the hosted waitlist page a new address
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Makes a new random address for the waitlist's hosted page and stops the old one at once,
for a page whose address was posted somewhere it should not be or is being flooded.
Referral links (/r/<code>) keep working: they always send people to the current address.
Update the address wherever you published it. An AI assistant always asks a person first: a
new address stops the one already published.
Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | WaitlistPageAddress |
The page's new address. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/codes
List a program's discount codes
Scope referrals:read. Another workspace's answers 404.
Every partner discount code of the program, newest first (up to 500, in one page), and the
discount a friend gets with one (friend_discount, from the program's friend's discount
rule, or null when it gives none). A shop's own system reads it to keep its coupons in step:
Bolt Claw's WooCommerce plugin reads it every hour. A code that is not active was switched
off: switch it off in your shop too. stripe_promotion_code and shopify_discount are the
code in your own Stripe or in the program's linked Shopify shop, once it is one there.
Needs referrals:read.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | DiscountCodeList |
The codes, and the friend's discount. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/invites
List a program's invites
Scope referrals:read. Another workspace's answers 404.
Every partner invite of the program and whether it is still usable, newest first, a page at a time. An invite's link is never shown again here: whoever holds it may join, so it is shown once, when the invite is made.
Needs referrals:read.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
limit |
query | integer |
Page size, 1 to 100. Above 100 is clamped; anything else is the default. (default 50; >= 1; <= 100) |
cursor |
query | string |
The next_cursor from the previous page. Opaque; an invalid one is 400. (max 512) |
Answers
| Status | Body | |
|---|---|---|
| 200 | InviteSummaryList |
The invites, without their links. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/invites
Create an invite to a program
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
A link that lets one person (or up to max_uses people) join the program as a partner,
optionally until expires_at (within a year). In an invite-only program it is the only
way in. Bolt Claw emails nobody: send the link from your own system, a Zap or a message.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (optional)
| Field | Type | |
|---|---|---|
max_uses |
integer |
How many people may join with it. 1 by default. (>= 1; <= 100000) |
expires_at |
date-time | null |
Within a year. |
Answers
| Status | Body | |
|---|---|---|
| 201 | Invite |
The invite, with its link. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/shopify-codes
Create the program's codes in its Shopify shop
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Keeps every partner code of the program in step with the Shopify shop linked to it through
Bolt Claw's Shopify app: an active code becomes a discount in the shop, at the friend's
discount (percent or amount off, once per customer, or for that many months of a
subscription), and a code switched off in Bolt Claw is switched off in the shop. A code the
shop already has with the same text is matched, not made again; running this twice makes
nothing twice. At most 50 codes a call: remaining says how many wait for the next one.
With no linked shop, or no friend's discount rule, nothing is made and reason says why.
Linking a shop is done from the shop's own Shopify admin, never through this API. Recorded
in the activity log. An AI assistant's key is always asked first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ShopifyCodesResult |
What was made, matched and switched off. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/privacy-requests
List a program's privacy requests
Scope referrals:read. Another workspace's answers 404.
What Shopify's privacy webhooks asked of this program, newest first (up to 200, in one
page): a customer asking for their data (data_request, waiting for you to answer it within
30 days of received_at, so due_by), and the erasures Bolt Claw did when Shopify asked
(customer_redact, shop_redact), with counts only. Read a data request to see what Bolt
Claw holds, give it to your customer, then mark it answered.
Needs referrals:read.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PrivacyRequestList |
The requests. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/privacy-requests/{id}
Read a privacy request
Scope referrals:read. Another workspace's answers 404.
One request, and for a data request still waiting, everything Bolt Claw holds about the
customer it named in this program (data): your customer id, who referred them, their
sales, refunds and rewards, and the referral link they were given. Bolt Claw holds no name,
email address, postal address or phone number, and data.note says so in words you can pass
on. Each read of data is recorded in the activity log.
Needs referrals:read.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PrivacyRequestDetail |
The request, and what Bolt Claw holds. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/privacy-requests/{id}/answer
Mark a privacy request answered
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Says you gave your customer what Bolt Claw holds about them. The Shopify customer and order
ids the request named are forgotten in the same step, and the request reads answered.
Only a waiting data request is answered (400 otherwise). Recorded in the activity log.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PrivacyRequest |
The request, answered. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/data-subjects/{external_id}
Look up what is held about one of your people
Scope referrals:read. Another workspace's answers 404.
Answers a privacy request from one of your own customers or in-app referrers: everything
Bolt Claw holds about them in this workspace, by the id your system uses for them (the
external_id you sent with their conversions or when you made them a referrer, or the
payment processor's customer id). For a customer: each program they were referred in, their
conversions and the rewards they were given. For a referrer: their display name, status,
links, commissions and rewards. An email fingerprint is shown only as held or not, never its
value; Bolt Claw holds no name, email address, postal address or phone number of a customer.
Each lookup is recorded in the activity log, with counts only. Nobody with that id is 404.
Needs referrals:read.
Parameters
| Name | In | Type | |
|---|---|---|---|
external_id |
path | string |
The id your system uses for the person: the external_id you sent with their conversions or when you made them a referrer, or the payment processor's customer id. Percent-encode it in the path. An id nobody in this workspace has is 404. The decoded id is at most 255 characters; the encoded segment may be longer. (required; min 1; max 2300) |
Answers
| Status | Body | |
|---|---|---|
| 200 | DataSubject |
Everything held about the person in this workspace. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
DELETE /v1/referrals/data-subjects/{external_id}
Erase one of your people's referral data
Scope referrals:write. Another workspace's answers 404.
Erases a customer's or an in-app referrer's referral data at their request, in every program of this workspace. Their ids and fingerprints are removed and cannot come back; amounts, commissions and payouts stay as anonymous rows, because your books need them. A referrer's name, payout details and panel sessions go and their links stop counting. Copies of webhook bodies that carried the id are scrubbed. Recorded in the activity log, with counts only. An AI assistant's key is always asked first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
external_id |
path | string |
The id your system uses for the person: the external_id you sent with their conversions or when you made them a referrer, or the payment processor's customer id. Percent-encode it in the path. An id nobody in this workspace has is 404. The decoded id is at most 255 characters; the encoded segment may be longer. (required; min 1; max 2300) |
Answers
| Status | Body | |
|---|---|---|
| 200 | DataSubjectErased |
Erased, with what was erased counted. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/terms/draft
Draft program terms
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Writes the program's terms from Bolt Claw's template: you answer a few questions (your
business's legal name and contact address, who may join, the minimum age, countries left
out, how many months money held for a missing tax form waits before it may be forfeited, the
governing law and courts) and every other fact is read from the program's own settings: the
cookie window, attribution, the hold, the minimum payout, the schedule, tax forms, invoices,
self-billing, the second tier, levels and the disclosure line. Nothing is saved. To use the
text, send it as terms to PATCH /v1/referrals/programs/{id}. The text ends with a notice
that it is based on a template and is not legal advice. See Program terms.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
answers |
object |
(required) |
{
"answers": {}
}
Answers
| Status | Body | |
|---|---|---|
| 200 | TermsDraft |
The drafted terms, and the settings they were read from. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/export
Export a report as a CSV download link
Scope referrals:read. Another workspace's answers 404.
A single-use link to one of the program's reports as a CSV file, valid for ten minutes:
commissions (each with the sentence saying why it is that amount), conversions,
partners (partners and your own in-app referrers; an email only when the partner chose to
share it with you), payouts, grants (rewards that are not cash) or contests (each
contest's standings and prizes). Filter by from and to (a date like 2026-10-01, or an
instant) and by status (for conversions, the event). The file is never in the response:
open url within ten minutes, once. It is built when it is opened, from what is true then,
and only if the key that asked is still live and its creator may still read the report.
At most 25,000 rows a file (narrow the dates for more; a file cut there says so in its name), and 60 links an hour for each key (a platform setting; the dashboard counts each person apart, so an assistant never uses up yours). Recorded in the activity log with the filters, never a row.
Needs referrals:read. The payouts report also needs referrals:payouts (which no AI
assistant holds) and the permission to pay partners.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
type |
query | string |
(required; one of commissions, conversions, partners, payouts, grants, contests) |
from |
query | string |
Rows at or after this date (2026-10-01) or instant. (max 40) |
to |
query | string |
Rows before this date or instant. (max 40) |
status |
query | string |
One status of that report's rows: a commission's, a partner's, a payout's, a grant's or a contest's; for conversions, the event (lead, sale, refund, dispute, bonus). (max 20) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ReportLink |
The link, once. Only its fingerprint is kept. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/pause
Pause a program
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Stops a program taking new clicks, signups and sales: its links still open your site, but nothing new is credited until it is resumed. What partners already earned is kept, and payouts of it go on. A stop, so it is never asked first and keeps working during maintenance.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Program |
The program, paused. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/resume
Resume a program
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Starts a paused or archived program again: its links credit clicks, signups and sales from now on. Nothing that happened while it was paused is credited afterwards. An AI assistant resumes a paused program at once; bringing back an archived one asks a person first (archiving is a person's decision, and so is undoing it).
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Program |
The program, active. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/archive
Archive a program
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Ends a program: it is frozen, its links stop crediting anything, partners can no longer join, and it no longer counts against your plan's programs. What partners earned is kept and can still be paid. Resuming it starts it again. An AI assistant always asks a person first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Program |
The program, archived. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/signup-link/rotate
Give a program a new signup link
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Makes a new signup link for the program and stops the old one at once, for a link that was posted somewhere it should not be. Partners already in the program, and their referral links, are not affected; invites stay valid. An AI assistant always asks a person first: a new link stops the one already shared. Recorded in the activity log.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Program |
The program, with its new signup link. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/partners/{id}/ban
Ban a partner
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Shuts a partner out of the program: their links and codes stop crediting, and what they are owed and not yet paid is not paid. Only a person decides this; a fraud rule never bans anybody. An AI assistant always asks a person first. Recorded in the activity log.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Partner |
The partner, banned. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/partners/{id}/unban
Lift a partner's ban
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Lets a banned partner back into the program, approved. Their links and codes credit again from now on. An AI assistant always asks a person first, as for approving a partner; the partner themselves can never be the person who decides. Recorded in the activity log.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Partner |
The partner, approved again. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/partners/{id}/codes
Give a partner a discount code
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
A discount code that credits this partner when a customer types it at checkout. Leave
code out for the program's suggestion (its code prefix and the partner's name). For
approved partners only; a code is unique in the program, ignoring case. When the program
has a friend's discount and your Stripe is connected, the code is also made as a
promotion code in your Stripe, and in a Shopify shop linked to the program; otherwise it
only tells BoltClaw which partner a code belongs to. An AI assistant always asks a person
first (never the partner it is for): a code changes what customers pay and who is paid.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (optional)
| Field | Type | |
|---|---|---|
code |
string | null |
3 to 40 letters, digits and hyphens. Left out, the program's suggestion. (max 64) |
Answers
| Status | Body | |
|---|---|---|
| 201 | DiscountCode |
The code. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/codes/{id}
Link a code to a Stripe promotion code
Scope referrals:write. Another workspace's answers 404.
Tells BoltClaw which promotion code in your own Stripe this code is (promo_…), so a sale
where a customer typed it at Stripe Checkout is credited even before Stripe's own events
taught BoltClaw. null forgets it. Usually unnecessary: BoltClaw learns it from Stripe's
promotion-code events. An AI assistant always asks a person first, and the approval page
names the promotion code: every sale that uses it is credited to this partner, your own
campaign codes included.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
stripe_promotion_code |
string | null |
promo_… in your own Stripe, or null to forget it. (required; max 255) |
{
"stripe_promotion_code": "<stripe_promotion_code>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | DiscountCode |
The code. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/codes/{id}/deactivate
Switch a discount code off
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Stops a code crediting its partner: a sale where a customer types it is no longer attributed. What it earned before is kept. A code switched off stays off; give the partner a new one instead. A stop, so it is never asked first and keeps working during maintenance.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | DiscountCode |
The code, switched off. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/links
List a program's referral links
Scope referrals:read. Another workspace's answers 404.
Every referral link of the program (/r/<code>), or one partner's with partner_id: the
partner's main link and the links BoltClaw placed for them (a bio block, a post, an
automation, shown by type only), archived ones included. Newest first, a page at a time.
Needs referrals:read.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
partner_id |
query | uuid |
|
limit |
query | integer |
Page size, 1 to 100. Above 100 is clamped; anything else is the default. (default 50; >= 1; <= 100) |
cursor |
query | string |
The next_cursor from the previous page. Opaque; an invalid one is 400. (max 512) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ReferralLinkList |
The links. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/links/{id}/archive
Archive a referral link
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Stops one of a partner's links crediting new clicks (it still opens your site). What it earned is kept. An AI assistant asks a person first unless your organisation lets assistants archive links without asking.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ReferralLink |
The link, archived. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/invites/{id}/revoke
Revoke an invite
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Stops an invite letting anyone join. People who already joined with it stay. A stop, so it is never asked first and keeps working during maintenance.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | InviteSummary |
The invite, revoked, without its link. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/commissions/{id}/approve
Approve a commission before its hold ends
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Makes a pending commission payable now, before the program's refund window ends, with your
reason. A refund after this still takes it back from the next payout. Not for a commission
a fraud flag is holding: decide the flag instead. The permission to pay partners is
needed; a key that is not an AI assistant's also needs referrals:payouts, and an AI
assistant always asks a person first (never the partner it pays). Recorded in the activity
log with the reason.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
reason |
string |
Why it is paid before its hold ends. Kept in the activity log. (required; max 500) |
{
"reason": "<reason>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | Commission |
The commission, approved. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/payouts/{id}/cancel
Cancel a payout
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Takes an unpaid payout out of its batch, as cancelled (or failed, when you tried to pay
it and the money did not arrive). Its commissions go back to Ready to pay, so nothing a
partner earned is lost. A payout a provider is still handling is 409 until the provider
answers. The permission to pay partners is needed. Cancelling a draft is a stop. Marking
one failed, or cancelling one that was exported or sent, lets its commissions be paid
again (it may already have been paid by hand): an AI assistant asks a person first, who
decides at a two-step session and is never the partner it pays, and a key needs
referrals:payouts for it. Recorded in the activity log.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (optional)
| Field | Type | |
|---|---|---|
status |
string |
cancelled (the default), or failed when you tried to pay it and the money did not arrive. (one of cancelled, failed) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Payout |
The payout. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/levels/recalculate
Recalculate partners' levels now
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Moves the program's partners to the highest level they qualify for, now, instead of at the daily run: never down past a level that keeps what was earned, and never a partner you pinned. Answers how many moved. Each move is recorded in the activity log.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | LevelRecalculation |
How many partners moved. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/rewards
List a program's reward rules
Scope referrals:read. Another workspace's answers 404.
Every reward rule of the program: for everyone, for a group or level, and for single
partners, with the conditions each applies under and a one-sentence summary. See
Reward rules for how one rule is picked for each sale.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | RewardList |
The program's rules. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/rewards
Add a reward rule
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
A rule for everyone (scope program), a group or level, or one partner, with conditions
on a sale's products, prices, plans, intervals, first payment or renewal, currency and
amount. Each audience may have one rule with no conditions per event (409 for a second).
A bonus is a flat amount paid once per window when a partner crosses its milestone.
A program holds at most 200 rules. The new rule applies to sales from now on.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
event |
string |
(required; one of lead, sale, bonus) |
scope |
string |
program (everyone) unless you say otherwise. (one of program, group, partner) |
group_id |
uuid |
With scope group. |
partner_id |
uuid |
With scope partner: one partner's own rate. |
kind |
string |
(one of percent, flat) |
percent_bps |
integer | null |
1 to 10000, for percent. |
amount_minor |
integer | null |
For flat; a bonus's must be above 0. |
duration |
string |
Ignored for a bonus (one of once, months, lifetime) |
months |
integer | null |
1 to 120, for months. |
cap_minor |
integer | null |
The most it pays for one customer; null for no ceiling. |
conditions |
RewardConditions |
When a rule applies. Every key you send must match; a rule with none is its audience's default for its event. A sale without items matches no rule that names products, prices, plans or intervals. A signup takes none; a bonus takes exactly milestone. |
priority |
integer |
-1000 to 1000; 0 by default. |
label |
string | null |
At most 60 characters after trimming. (max 200) |
reward_type |
RewardType |
cash when left out. Fixed once the rule exists. |
beneficiary |
string |
promoter when left out. Fixed once the rule exists. (one of promoter, referee) |
period_count |
integer | null |
For free_period: 1 to 365 days, or 1 to 36 months. |
period_unit |
string | null |
For free_period. (one of day, month, null) |
perk_text |
string | null |
For perk: at most 120 characters after trimming. (max 400) |
discount_duration |
string | null |
For discount: how long it lasts. (one of once, months, forever, null) |
discount_months |
integer | null |
For a discount of months: 1 to 36. |
{
"event": "lead"
}
Answers
| Status | Body | |
|---|---|---|
| 201 | Reward |
The rule. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/rewards/{id}
Update a reward rule
Scope referrals:write. Another workspace's answers 404.
Changes the fields you send, in place. A rule's event and audience are fixed: add a new rule for a different one. What was already earned is a stored amount and never changes; the new terms apply to sales from now on.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
kind |
string |
(one of percent, flat) |
percent_bps |
integer | null |
|
amount_minor |
integer | null |
|
duration |
string |
(one of once, months, lifetime) |
months |
integer | null |
|
cap_minor |
integer | null |
|
conditions |
RewardConditions |
When a rule applies. Every key you send must match; a rule with none is its audience's default for its event. A sale without items matches no rule that names products, prices, plans or intervals. A signup takes none; a bonus takes exactly milestone. |
priority |
integer |
|
label |
string | null |
(max 200) |
period_count |
integer | null |
|
period_unit |
string | null |
(one of day, month, null) |
perk_text |
string | null |
(max 400) |
discount_duration |
string | null |
(one of once, months, forever, null) |
discount_months |
integer | null |
Answers
| Status | Body | |
|---|---|---|
| 200 | Reward |
The rule. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
DELETE /v1/referrals/rewards/{id}
Delete a reward rule
Scope referrals:write. Another workspace's answers 404.
Removes the rule. What it already earned stays earned; it stops paying from now on, and the next most specific rule pays instead. An AI assistant's key is always asked first.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ReferralDeleted |
Deleted. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/groups
List a program's groups and levels
Scope referrals:read. Another workspace's answers 404.
Plain groups you place partners in, and levels: groups with a rank and a minimum that the daily level job moves partners into. Each with how many partners are in it now.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | GroupList |
The program's groups and levels. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/groups
Add a group or level
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
A plain group (no rank), or a level (rank 1 to 100, and above the first a minimum).
Give it its own rates with POST /referrals/programs/{id}/rewards and scope group.
Names and ranks are unique in a program; a program holds at most 50 groups and levels.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
name |
string |
1 to 60 characters after trimming; unique in the program. (required; max 200) |
description |
string | null |
(max 1000) |
is_default |
boolean |
|
rank |
integer | null |
1 to 100 makes it a level; null for a plain group. |
minimum |
LevelMinimum |
What a partner needs to reach a level: revenue in amount_minor, or sales or customers in count, over the last window_days (30 to 365), or all time when null. Counted net of refunds. Null: everyone qualifies. |
keep_earned |
boolean |
{
"name": "<name>"
}
Answers
| Status | Body | |
|---|---|---|
| 201 | Group |
The group. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/groups/{id}
Update a group or level
Scope referrals:write. Another workspace's answers 404.
Changes the fields you send. A minimum is only for a level; a group without a rank has none.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
name |
string |
(max 200) |
description |
string | null |
(max 1000) |
is_default |
boolean |
|
rank |
integer | null |
|
minimum |
LevelMinimum |
What a partner needs to reach a level: revenue in amount_minor, or sales or customers in count, over the last window_days (30 to 365), or all time when null. Counted net of refunds. Null: everyone qualifies. |
keep_earned |
boolean |
Answers
| Status | Body | |
|---|---|---|
| 200 | Group |
The group. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
DELETE /v1/referrals/groups/{id}
Delete a group or level
Scope referrals:write. Another workspace's answers 404.
Removes the group and its own rules. Its partners stay in the program, in no group, and what they earned stays earned. An AI assistant's key is always asked first.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ReferralDeleted |
Deleted. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/partners/{id}
Move a partner to a group
Scope referrals:write. Another workspace's answers 404.
Puts the partner in a group or level of their program (group_id), or in none (null).
pinned: true keeps the daily level job from moving them again.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
group_id |
uuid | null |
A group or level of the partner's program; null for none. |
pinned |
boolean |
True keeps the daily level job from moving the partner. |
Answers
| Status | Body | |
|---|---|---|
| 200 | Partner |
The partner. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/partners/{id}/bonuses
Pay a partner a bonus
Scope referrals:write. Idempotency-Key is required. Another workspace's answers 404.
A flat fee or one-off bonus by hand ("200.00 for the launch video"), in the program's
currency, with a reason the partner reads. It holds for hold_days (0 by default, so it
is ready to pay at once), then is paid like any commission. Only an approved partner can
be paid one. Recorded in the activity log, and announced as referral.bonus.created.
An AI assistant's key is always asked first.
Idempotency-Key is required: a retried network timeout must never pay a partner twice.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
amount_minor |
integer |
Minor units of the program's currency. (required; >= 1) |
reason |
string |
The partner sees it as the line's description. Recorded in the activity log. (required; min 1; max 280) |
hold_days |
integer |
Days before it may be paid. 0 (the default) makes it payable at once. (>= 0; <= 365) |
{
"amount_minor": 1,
"reason": "<reason>"
}
Answers
| Status | Body | |
|---|---|---|
| 201 | Commission |
The bonus, as a commission. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/embed-tokens
Create a referral panel session for one of your users
Scope referrals:embed. Accepts Idempotency-Key. Another workspace's answers 404.
Call this from YOUR server when a signed-in user opens the page that shows their referrals.
It makes the user a referrer in the program if they are not one yet (by the external_id
you send, which is your own id for that user), and returns a short-lived token your page
hands to the Bolt Claw panel: BoltClawReferrals.mount(element, { token, embedKey }). See
Referral panel.
The token is shown once, in this response, and is the only thing that ever goes to the
browser. It works for that one referrer in that one program, lives ttl_seconds (60 to
86400, default 3600), and ends early if you call POST /referrals/programs/{id}/embed/revoke-sessions
or the referrer is erased. A replay of the same Idempotency-Key returns token: null:
mint a new one instead.
Send mode: "preview" and no referrer for a session that shows sample data, to try your
look and copy before launch.
Needs referrals:embed (the "Referral panel" key preset), or referrals:write. Never put
this key in a browser or a mobile app, and an AI assistant's key can never mint a session.
The panel must be switched on for the program (embed_enabled), or the answer is 409.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
referrer |
object |
The user the session is for; they are made a referrer if they are not one. Leave it out only for mode preview. |
ttl_seconds |
integer |
How long the token works, in seconds. (default 3600; >= 60; <= 86400) |
mode |
string |
panel for a real referrer; preview shows sample data and needs no referrer. (default panel; one of panel, preview) |
Answers
| Status | Body | |
|---|---|---|
| 200 | EmbedTokenCreated |
The session. token is shown here and nowhere else. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 402 | ErrorEnvelope |
PLAN_LIMIT: the organisation's plan does not allow more of this. The message names the allowance. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/referrers
List a program's referrers
Scope referrals:read. Another workspace's answers 404.
The people you gave a referral link through the panel or the API, newest first, with what
each link has done. A referrer is one of YOUR users; they have no Bolt Claw account and
are not counted as partners. search matches the start of your external_id.
Needs referrals:read, or referrals:embed.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
status |
query | ReferrerStatus |
approved earns rewards. pending is waiting for you (when the program does not approve new referrers at once, embed.approve_referrers); it can share its link and earns nothing yet. banned earns nothing; an erased referrer is also banned. (one of approved, pending, banned) |
search |
query | string |
The start of the external_id, at most 200 characters. (max 200) |
limit |
query | integer |
Page size, 1 to 100. Above 100 is clamped; anything else is the default. (default 50; >= 1; <= 100) |
cursor |
query | string |
The next_cursor from the previous page. Opaque; an invalid one is 400. (max 512) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ReferrerList |
A page of referrers. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/referrers
Create or update a referrer
Scope referrals:embed. Accepts Idempotency-Key. Another workspace's answers 404.
Makes one of your users a referrer, or updates the one your external_id already names:
201 when it was created, 200 when it already existed. Only the fields you send change;
send null to clear one. The email, if you send one, is only used to recognise the
same person again and is never stored, returned or shown to anyone. A new referrer is
announced as referral.referrer.created.
code is the vanity part of their link (/r/jane), 3 to 40 lowercase letters and digits
with single hyphens, chosen only when the referrer is created: after that it is already
in links they have shared, and sending one is 409. 409 also when the code is taken.
You do not need this before minting a panel session, which creates the referrer itself; use it to create referrers ahead of time or to keep their name current.
Needs referrals:embed, or referrals:write. A referrer past the plan's allowance is
402.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
external_id |
string |
Your own id for the user. Printable characters only, no space at either end. (required; min 1; max 200) |
name |
string | null |
A first name or display name for the panel's greeting, plain text. null clears it. (max 60) |
email |
string | null |
Only used to recognise the same person again (a fingerprint is kept, never the address). Never returned. (max 400) |
code |
string | null |
The vanity code for their link: 3 to 40 lowercase letters and digits, single hyphens between words. Only when the referrer is created. (max 40) |
{
"external_id": "<external_id>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | ReferrerView |
The referrer already existed; the fields you sent are updated. |
| 201 | ReferrerView |
The referrer was created. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 402 | ErrorEnvelope |
PLAN_LIMIT: the organisation's plan does not allow more of this. The message names the allowance. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/referrers/{external_id}
Read one referrer's panel
Scope referrals:read. Another workspace's answers 404.
Exactly what the panel shows this referrer, as data: their link, the offer, their numbers,
a history row for each friend (up to 50, newest first) with a stable reason_code, how
they are paid, and the share text. Use it to build your own screen. {external_id} is
your own id for the user and must be percent-encoded.
The View has no names, emails or ids of your customers: a friend is "Friend 3". An erased
referrer, or one that does not exist, is 404.
Needs referrals:read, or referrals:embed.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
external_id |
path | string |
Your own id for the user, as you sent it in external_id. It can hold any printable characters, so percent-encode it in the path (user%2F1). An id that is not one of your referrers is 404. The decoded id is at most 200 characters; the encoded segment may be longer. (required; min 1; max 1800) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ReferrerPanelView |
The referrer's panel View. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/referrers/{id}
Approve or ban a referrer
Scope referrals:write. Another workspace's answers 404.
Sets a referrer's status to approved or banned. {id} is the referrer's id from
ReferrerView, not your external_id. A banned referrer earns nothing from then on and
keeps what was already earned; approving one again can be 402 if the plan's referrer
allowance is full. An erased referrer cannot be changed (409). An AI assistant bans one at
once; approving one asks a person first, as letting a banned partner back in does.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
status |
string |
(required; one of approved, banned) |
{
"status": "approved"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | ReferrerView |
The referrer. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 402 | ErrorEnvelope |
PLAN_LIMIT: the organisation's plan does not allow more of this. The message names the allowance. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
DELETE /v1/referrals/referrers/{id}
Erase a referrer
Scope referrals:write. Another workspace's answers 404.
Erases a person's referral data on their request. The name, the payout details, the
recognition of their address and their panel sessions are removed and cannot come back;
what they earned stays in the commission and payout records as an anonymous row, because
your books need it. Their link stops counting. {id} is the referrer's id from
ReferrerView. An AI assistant's key is always asked first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ReferralDeleted |
Erased. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/embed/revoke-sessions
End every live panel session
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Every panel token minted for this program stops working at once, wherever it is held; each
open panel shows "This link to your referrals has expired" and asks your page for a new
one. Use it if a token may have leaked. Sessions you mint afterwards work as usual.
embed.generation in the program goes up by one.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Program |
The program, with its new embed.generation. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/embed/rotate-key
Rotate the panel's public key
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Gives the program a new public embed.key. The key is not a secret (it is in your page
and in the panel's address) but it names the panel's settings, so rotating it makes every
install that still carries the old one stops showing the panel until you update embedKey
there. Sessions already minted are not ended by a rotation; to end those as well, call
POST /referrals/programs/{id}/embed/revoke-sessions. An AI assistant always asks a person
first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Program |
The program, with its new embed.key and embed.panel_url. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/grants
List a program's rewards that are not cash
Scope referrals:read. Another workspace's answers 404.
The free months, credit, perks, gift cards and friend's discounts the program has given or
owes, newest first. Each says what it is (summary), whose it is (recipient: one of your
own users with a link by your external_id, an affiliate by their name in the program, or
the friend by your id for them), and where it stands.
A grant waits out the program's hold as pending, then is ready: give it in your own
system and say so with POST /referrals/grants/{id}/fulfil, or let Bolt Claw give it
through your Stripe if you set a restricted key. A refund or dispute of the sale cancels
one not yet given and marks one already given reversed. A friend's discount is recorded
as fulfilled with the sale, when the sale shows it was used.
Needs referrals:read.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
status |
query | GrantStatus |
pending waits out the program's hold; ready is yours to give; fulfilled was given; failed could not be given yet; cancelled by a refund or dispute before it was given; reversed by one after. (one of pending, ready, fulfilled, failed, cancelled, reversed) |
type |
query | GrantType |
(one of credit, free_period, discount, perk, gift_card) |
recipient |
query | string |
promoter for the people who shared, referee for the friends. (one of promoter, referee) |
limit |
query | integer |
Page size, 1 to 100. Above 100 is clamped; anything else is the default. (default 50; >= 1; <= 100) |
cursor |
query | string |
The next_cursor from the previous page. Opaque; an invalid one is 400. (max 512) |
Answers
| Status | Body | |
|---|---|---|
| 200 | GrantList |
A page of grants. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/grants/{id}
Read one reward that is not cash
Scope referrals:read. Another workspace's answers 404.
One grant, as the list shows it. Read it from a referral.grant.* webhook event's
grant_id before you give it.
Needs referrals:read.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Grant |
The grant. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/grants/{id}/fulfil
Say a reward was given
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
You gave the reward in your own system (a month added, credit applied, a perk sent); this
records it with your reference, tells the person on their page and raises
referral.grant.fulfilled. Only a ready grant can be given: one still in its hold, or
cancelled, is 409. Sending the same reference again answers the grant as it is and
changes nothing, so a retry is safe; a different reference for a grant already given is
409. Recorded in the activity log. An AI assistant's key is always asked first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
reference |
string |
Your reference for the reward you gave, like an order or credit note number. The same one again is safe. (required; min 1; max 200) |
note |
string | null |
A note for your own team. (max 500) |
{
"reference": "<reference>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | Grant |
The grant, now fulfilled. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/grants/{id}/fail
Say a reward could not be given
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
The reward cannot be given as it stands (the account was closed, the perk is out of
stock): this records your reason, shown to you and not to the person, and leaves the
grant failed, so you can still give it later with POST /referrals/grants/{id}/fulfil.
A grant already given or cancelled is 409. Recorded in the activity log. An AI
assistant's key is always asked first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
reason |
string |
Why it cannot be given. Shown to you (required; min 1; max 500) |
{
"reason": "<reason>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | Grant |
The grant, now failed. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 413 | ErrorEnvelope |
PAYLOAD_TOO_LARGE: the body is over 64 KB. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/grants/{id}/send
Give a gift card through your Tremendous
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Gives a ready gift card to the promoter who earned it, from YOUR Tremendous account, at
the email in their payout details, and records it fulfilled with Tremendous's order id.
The order's id is made from the grant's, so pressing again never buys a second card. A
friend's gift card cannot be sent this way (BoltClaw holds no address for a friend).
202 means Tremendous did not answer in time: try again in ten minutes.
Needs referrals:payouts, or an AI assistant with a person's approval, given by someone
signed in with two-step verification who is not the promoter it pays. A grant whose
promoter or sale has an open fraud flag waits. Recorded in the activity log.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | Grant |
The grant, given. |
| 202 | Grant |
Tremendous did not answer in time; the grant waits. Try again in ten minutes. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/offers/{code}
Read the friend's offer for a referral link
Scope referrals:read.
What a person who arrived through this link is offered, in the words the welcome shows
(offer), and whether THIS customer may have it at your checkout (eligible). Call it
from your server before you create a checkout: with customer_id (your own id for the
customer, when they have one) and new_customer, a customer who is the referrer
themselves, or was already referred by someone else, is not eligible, with a stable
reason_code and a sentence to show instead of an error.
coupon is the coupon Bolt Claw made for the friend's discount in your own Stripe (with a
restricted key), ready for discounts: [{ coupon }]; offer.stripe.promotion_code is the
referrer's own promotion code, when they have one.
A link that does not exist, is archived, or is another workspace's answers 200 with
offer: null and reason_code: unknown_code, exactly as one that never existed. coupon
is null whenever eligible is false.
Needs referrals:read, or referrals:embed.
Parameters
| Name | In | Type | |
|---|---|---|---|
code |
path | string |
The code at the end of a referral link (/r/jane), lower case. (required; min 3; max 40) |
customer_id |
query | string |
Your own id for the customer, at most 255 characters. (min 1; max 255) |
new_customer |
query | string |
false when the customer has paid you before: the offer is for new customers. (one of true, false) |
Answers
| Status | Body | |
|---|---|---|
| 200 | OfferAnswer |
The offer and whether this customer may have it. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/customers/{external_id}/referral
Read who invited a customer
Scope referrals:read. Another workspace's answers 404.
For your own screens ("Invited by Jane"): whether this customer of yours arrived through
the program, when, and how (link or discount_code). invited_by is the inviter's
name only when they agreed to it being shown; otherwise null. A customer nobody referred
answers with every field null; one the program has never seen is 404.
Needs referrals:read, or referrals:embed.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
external_id |
path | string |
Your own id for the customer, as you sent it in a conversion's customer.id. Percent-encode it in the path. The decoded id is at most 255 characters; the encoded segment may be longer. (required; min 1; max 2300) |
Answers
| Status | Body | |
|---|---|---|
| 200 | CustomerReferral |
The customer's referral. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/stripe-codes
Create the program's codes in Stripe
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Makes one coupon in your own Stripe from the friend's discount rule ("Referral: 20% off for
3 months"), then a promotion code for every active code of the program that Stripe does not
have yet, so a friend can type their referrer's code at Stripe Checkout. A code that
already exists in Stripe with the same text is linked, not made again; running this twice
makes nothing twice. Needs a restricted Stripe key, set in the dashboard (Integration tab);
without one, or without a friend's discount rule, the answer is 409. Recorded in the
activity log. An AI assistant's key is always asked first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | StripeCodesResult |
What was made and linked. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/integration-status
Read a program's live setup status
Scope referrals:read. Another workspace's answers 404.
Whether each part of the setup works, from what Bolt Claw has actually seen for this
program: the website tag, billing (any connected billing system, or sales through this
API), signups, sales, discount codes, the in-app panel and the friend's welcome. Each part
is connected, warnings (connected, with something to fix) or not_connected, with one
sentence saying what was seen and one saying what to do. A part the program's own setup
does not use is skipped and does not count toward overall. Test data never makes a part
green: only live events do.
integrations lists every connected billing system as its card shows it: which one, how it
proves its deliveries, when the last verified event arrived and how many were refused since.
No credential is ever returned, not even in part.
Needs referrals:read.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | IntegrationStatus |
The program's setup, part by part. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/integration-status/tag-check
Check the website tag on the program's product page
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Fetches the program's product URL once, from Bolt Claw's server, and looks for the website
tag (/referrals/v2.js, or the older v1.js) in a script tag, then updates the setup
status. Only https, only the program's own hosts, never a private address (checked after
the name is resolved, and again on every redirect), five seconds and 512 KB at most. A tag
your site adds with JavaScript is not visible to this check: open your site with the verify
link from the dashboard instead. A few checks per program an hour.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | TagCheck |
What the check found. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/test-sale
Send a test sale through the program
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Runs the real path with TEST data, step by step: the program's test partner ("Test partner", made once) and its link, a click on it, a signup, a sale of 100.00 in the program's currency with two items, then a partial refund of 25.00. Each step answers with what it caused: the conversion with its commissions and grants, the friend's offer, and how many webhook deliveries it queued for endpoints that receive test events.
Test data is never payable, never counts toward a limit or a total, emails nobody, and
reaches only webhook endpoints that switched test events on. Clear test data deletes it
all. The program must be active.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 201 | TestSale |
Every step, in order. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
DELETE /v1/referrals/programs/{id}/test-data
Delete a program's test data
Scope referrals:write. Another workspace's answers 404.
Deletes every TEST row of the program: the conversions, commissions, rewards, customers and
clicks that test sales, test: true conversions, a test-mode Stripe or a sandbox recorded,
and the program's test partner with its link. Live data is never touched. Recorded in the
activity log. An AI assistant's key is always asked first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | TestDataCleared |
How many of each were deleted. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/clicks
Record a click your own server saw
Scope conversions:write. Accepts Idempotency-Key.
For a partner's link that opens straight on your own site (?via=<code>), reported by your
server, for example by the SDK's referralsMiddleware(). Send the link's code and what your
server saw: the visitor's IP address and user agent are hashed and classed exactly as a
click on /r/<code> is, and never stored. It counts toward the click allowance like any
click, and earns nothing by itself: a click is never a conversion. A robot is answered
recorded: false and stored nowhere; a code that is not one of your links is 404.
Needs conversions:write, like reporting a conversion.
Request body (required)
| Field | Type | |
|---|---|---|
code |
string |
The partner link's code (?via=). (required; min 1; max 64) |
ip |
string |
The visitor's IP address (max 64) |
user_agent |
string |
Classed as mobile (max 1024) |
referrer |
string |
The referring page. Only its host is kept. (max 2048) |
country |
string |
An ISO 3166 two-letter country code, if your edge sets one. (max 8) |
landing_path |
string |
The path the visitor landed on. Not stored. (max 2048) |
{
"code": "<code>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | ReferralClickRecorded |
Not stored (a robot, or the month's click allowance is used). |
| 201 | ReferralClickRecorded |
Recorded. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/flags
List a program's review queue
Scope referrals:read. Another workspace's answers 404.
The flags waiting for a decision (or, with status, the ones already decided), newest
first, up to 200. A flag is raised by a rule, never decided by one: the risk engine only
ever HOLDS (a longer hold on what a conversion earned, or risk_review for a person to
look at), and nothing here bans, rejects or confirms anybody by itself. Each flag names
the partner by their name in this program, and the conversion, commission or link it is
about; score is the risk score (0 to 100) behind a risk_review flag.
Filter with rule, partner_id, min_score and older_than_days.
Needs referrals:read.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
status |
query | string |
open (the default), dismissed or confirmed. (one of open, dismissed, confirmed) |
rule |
query | FlagRule |
Which rule raised a flag. risk_review is the risk engine asking a person (the score reached the program's review_at); self_referral_card is one card paying for the promoter's own customers; payout_change is a payout built inside the window after a partner changed where they are paid; manual_hold is "Hold this promoter's payouts". other is a rule this version of the API does not name yet. (one of self_referral, click_velocity, duplicate_payout, risk_review, self_referral_card, payout_change, manual_hold, other) |
partner_id |
query | uuid |
One partner's membership id. |
min_score |
query | integer |
(>= 0; <= 100) |
older_than_days |
query | integer |
Only flags at least this many days old. (>= 0; <= 3650) |
reason |
query | string |
Only risk_review flags whose score had this reason, for example fast_conversion. (one of fast_conversion, paid_ads, datacenter_click, disposable_customer, email_lookalike, no_click_pattern, code_leak_spike, refund_farming, conversion_rate_outlier, new_partner) |
Answers
| Status | Body | |
|---|---|---|
| 200 | FlagList |
The flags. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/flags/{id}
Read one flag in full
Scope referrals:read. Another workspace's answers 404.
Everything a person needs to decide a flag: the score and every reason as a sentence (what was seen, in counts and times: "Sale 4 seconds after the click", never an address, a card or an email), the timeline of the customer's click, signup, sale and refunds with the seconds between them, and the partner's history in this program (age, clicks and customers in 30 days, refunds in 60, and their other flags). The partner never sees any of it: their side says only "Under review".
Needs referrals:read.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | FlagDetail |
The flag, its reasons, timeline and the partner's history. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/flags/{id}/dismiss
Dismiss a flag (not fraud)
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Not fraud: the flag is closed and what it held goes on. A held commission matures at its
hold (or on the next sweep if that has passed) and joins a payout like any other. A
flag already decided is 409. Recorded in the activity log with your note. An AI
assistant's key is always asked first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (optional)
| Field | Type | |
|---|---|---|
note |
string | null |
Kept on the flag and in the activity log. (max 500) |
Answers
| Status | Body | |
|---|---|---|
| 200 | FlagResolution |
The flag, now dismissed. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/flags/{id}/confirm
Confirm a flag as fraud
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Fraud: every commission the flag holds that is still pending or approved becomes
fraud, which never reaches a payout, and a reward not yet given is cancelled. With
ban_partner: true the partner is banned from the program too; nothing bans anybody
unless you say so. A commission already in a payout is 409. Recorded in the activity log
with your note. An AI assistant's key is always asked first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (optional)
| Field | Type | |
|---|---|---|
note |
string | null |
(max 500) |
ban_partner |
boolean |
Ban the partner from the program too (default false). |
Answers
| Status | Body | |
|---|---|---|
| 200 | FlagResolution |
The flag, now confirmed, and what it did. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 409 | ErrorEnvelope |
CONFLICT: the request clashes with the object's current state (the message says how). IDEMPOTENCY_KEY_CONFLICT / IDEMPOTENCY_KEY_IN_FLIGHT: the Idempotency-Key was used with another request, or its first request is still running. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/flags/dismiss
Dismiss many flags at once
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Dismisses up to 200 open flags of one program with one reason, each decided and recorded in
the activity log on its own. A flag that is not this program's, not open, or holds a
commission already in a payout is left as it was and listed in skipped with why. An AI
assistant's key is always asked first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
flag_ids |
uuid[] |
(required; <= 200 items; >= 1 items) |
note |
string |
One reason for all of them. (required; min 1; max 500) |
{
"flag_ids": "3f8c1e42-9b7a-4d61-a0f5-2c9e1b7d4a83",
"note": "<note>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | FlagsResolution |
How many were decided, and the ones left as they were. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/programs/{id}/flags/confirm
Confirm many flags as fraud at once
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
Confirms up to 200 open flags of one program as fraud with one reason, each decided and
recorded in the activity log on its own: what each held becomes fraud, and with
ban_partners: true their partners are banned. Flags left as they were are listed in
skipped with why. An AI assistant's key is always asked first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
flag_ids |
uuid[] |
(required; <= 200 items; >= 1 items) |
note |
string |
One reason for all of them. (required; min 1; max 500) |
ban_partners |
boolean |
Ban each flag's partner too (default false). |
{
"flag_ids": "3f8c1e42-9b7a-4d61-a0f5-2c9e1b7d4a83",
"note": "<note>"
}
Answers
| Status | Body | |
|---|---|---|
| 200 | FlagsResolution |
How many were decided, and the ones left as they were. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
POST /v1/referrals/partners/{id}/hold-payouts
Hold a partner's payouts
Scope referrals:write. Accepts Idempotency-Key. Another workspace's answers 404.
"Hold this promoter's payouts": opens a manual_hold flag on the partner, and nothing they
earned is paid while it is open. A stop, never a decision: what they earned stays theirs
until you dismiss or confirm the flag. Holding a partner already held answers the open
flag with already: true. Recorded in the activity log.
One API key may place at most 30 holds an hour (a platform setting), answered 429 RATE_LIMITED past it: only a person releases a hold. The dashboard is not counted.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (optional)
| Field | Type | |
|---|---|---|
note |
string | null |
Kept on the flag and in the activity log. (max 500) |
Answers
| Status | Body | |
|---|---|---|
| 200 | PayoutHold |
Already held; the open flag. |
| 201 | PayoutHold |
Held; the new flag. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/risk-policy
Read a program's fraud settings
Scope referrals:read. Another workspace's answers 404.
What holds this program's money: the score at which a conversion's earnings wait longer
(hold_at, and extra_hold_days), the score at which a person is asked (review_at), the
fewest seconds from click to signup, sale or shop order that look human, whether paid
search on the brand is allowed (with campaign ids that are), and whether new partners wait
longer. policy is what is in force (your values, else the platform's); custom is only
what you set. alert_email emails the program's managers when a promoter needs a look, at
most once per promoter a day.
Needs referrals:read.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Answers
| Status | Body | |
|---|---|---|
| 200 | RiskPolicyView |
The settings. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
PATCH /v1/referrals/programs/{id}/risk-policy
Change a program's fraud settings
Scope referrals:write. Another workspace's answers 404.
Replaces what you set with policy (send null to go back to the platform's values) and
switches the alert email. Every value is checked: review_at is never below hold_at, a
campaign id is letters, digits and _.:-. Applies from the next conversion; what was
already assessed keeps its hold. Recorded in the activity log with the values before and
after. An AI assistant's key is always asked first.
Needs referrals:write.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
Request body (required)
| Field | Type | |
|---|---|---|
policy |
any |
The fields to change: only those you send change, and one sent as null goes back to the platform's value. policy: null clears every field the program set. |
alert_email |
boolean |
Answers
| Status | Body | |
|---|---|---|
| 200 | RiskPolicyView |
The settings now. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 428 | ErrorEnvelope |
APPROVAL_REQUIRED on the first try, APPROVAL_PENDING on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries approval with a url: show it to the person, and once they approve, send the identical request again with the approval id in BoltClaw-Approval. Only an AI assistant's request is ever answered this way. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
GET /v1/referrals/programs/{id}/stats
Read a program's stats
Scope referrals:read. Another workspace's answers 404.
Clicks, leads, sales, revenue and commission over a window, in total, per partner and, with bucket, over time.
Parameters
| Name | In | Type | |
|---|---|---|---|
id |
path | uuid |
An id this API gave you. Anything that is not one of yours is 404. (required) |
from |
query | string |
(max 64) |
to |
query | string |
(max 64) |
bucket |
query | string |
(one of day, week, month) |
Answers
| Status | Body | |
|---|---|---|
| 200 | ProgramStats |
The stats. |
| 400 | ErrorEnvelope |
VALIDATION_ERROR: a body that is not JSON, a field that breaks its rule (named in fields), a bad query value or cursor, or a malformed Idempotency-Key. |
| 401 | ErrorEnvelope |
UNAUTHORIZED: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | ErrorEnvelope |
INSUFFICIENT_SCOPE: the key lacks this operation's scope. Or ORGANISATION_SUSPENDED: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | ErrorEnvelope |
NOT_FOUND: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 429 | ErrorEnvelope |
RATE_LIMITED: too many requests. Wait for Retry-After seconds. |
| 500 | ErrorEnvelope |
INTERNAL_ERROR: ours. Safe to retry; send the same Idempotency-Key on a POST. |
How a program is doing — answers 200
Clicks, conversions, commissions and payouts over time. No partner account and no customer appears in it.
curl "https://api.boltclaw.ai/v1/referrals/programs/4e8a2b71-39cd-4f06-8a52-71b0d4c9e283/stats?from=2026-03-01&to=2026-04-01&bucket=day" \
-H "Authorization: Bearer bc_live_EXAMPLExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
const res = await fetch("https://api.boltclaw.ai/v1/referrals/programs/4e8a2b71-39cd-4f06-8a52-71b0d4c9e283/stats?from=2026-03-01&to=2026-04-01&bucket=day", {
headers: {
Authorization: `Bearer ${process.env.BOLTCLAW_KEY}`,
},
});
// 200
const data = await res.json();
Shapes
The objects these operations send and answer.
AutoSendCancelled
| Field | Type | |
|---|---|---|
cancelled |
boolean |
(required) |
BillingProvider
A billing system Bolt Claw connects to directly.
One of: stripe, paddle, polar, whop, revenuecat, woocommerce, fastspring, lemonsqueezy, shopify, chargebee, recurly, gumroad.
BoardMetric
customers: paying customers brought; sales: payments; revenue: their amount, in minor units; friends: on a waitlist, friends who joined and confirmed.
One of: customers, sales, revenue, friends.
BoardNameChoice
How a promoter chose to appear to the program's other promoters. hidden is "A promoter", the default for a partner; one of your own users starts initials.
One of: hidden, initials, display.
BoardPeriod
month and quarter are calendar ones (UTC); 30d the last 30 days; all since the program began.
One of: month, 30d, quarter, all.
BonusCreate
| Field | Type | |
|---|---|---|
amount_minor |
integer |
Minor units of the program's currency. (required; >= 1) |
reason |
string |
The partner sees it as the line's description. Recorded in the activity log. (required; min 1; max 280) |
hold_days |
integer |
Days before it may be paid. 0 (the default) makes it payable at once. (>= 0; <= 365) |
Brand
| Field | Type | |
|---|---|---|
scope |
string |
workspace: the brand of every program. program: one program's override. (required; one of workspace, program) |
program_id |
uuid | null |
(required) |
name |
string | null |
What this brand sets; null follows the workspace's brand (or the program's own name). (required) |
accent |
string | null |
#rrggbb, used for buttons and links. (required) |
background |
string | null |
#rrggbb, the portal's page colour. (required) |
radius |
integer | null |
Corner radius in pixels, 0 to 24. (required) |
sender_name |
string | null |
The name partners' emails come from. The address stays BoltClaw's. (required) |
show_powered_by |
boolean | null |
(required) |
support_email |
email | null |
(required) |
support_email_verified |
boolean |
Confirmed with a code; only then is it the reply-to of partners' emails. (required) |
logo_light_url |
uri | null |
(required) |
logo_dark_url |
uri | null |
(required) |
favicon_url |
uri | null |
(required) |
effective |
BrandEffective |
The brand partners of this scope actually see, the program's override over the workspace's. (required) |
updated_at |
date-time | null |
(required) |
BrandEffective
The brand partners of this scope actually see, the program's override over the workspace's.
| Field | Type | |
|---|---|---|
name |
string | null |
(required) |
logo_light_url |
uri | null |
(required) |
logo_dark_url |
uri | null |
(required) |
favicon_url |
uri | null |
(required) |
accent |
string | null |
#rrggbb. (required) |
background |
string | null |
#rrggbb. (required) |
radius |
integer | null |
Corner radius in pixels, 0 to 24. (required) |
show_powered_by |
boolean |
Whether "Powered by BoltClaw" shows. Off unless you turn it on. (required) |
sender_name |
string | null |
The name partners' emails come from; null for the default sender. (required) |
reply_to |
email | null |
The confirmed support address partners' replies go to; null until one is confirmed. (required) |
BrandEmailCodeConfirm
| Field | Type | |
|---|---|---|
program_id |
uuid | null |
|
code |
string |
The six digits from the email. (required) |
BrandEmailCodeSent
| Field | Type | |
|---|---|---|
sent |
boolean |
(required; one of true) |
to |
email |
The support address the code went to: your own. (required) |
BrandFileSlot
logo_light: the logo on light backgrounds. logo_dark: on dark ones. favicon: the browser tab's icon.
One of: logo_light, logo_dark, favicon.
BrandScope
An empty object for the workspace's brand.
| Field | Type | |
|---|---|---|
program_id |
uuid | null |
A program's override. |
BrandUpdate
Send only the fields you want to change; null clears one. An empty object is 400.
| Field | Type | |
|---|---|---|
program_id |
uuid | null |
Change this program's override instead of the workspace's brand. |
name |
string | null |
No control characters and no angle brackets. (max 80) |
accent |
string | null |
|
background |
string | null |
|
radius |
integer | null |
(>= 0; <= 24) |
support_email |
email | null |
Unconfirmed until its code comes back. |
show_powered_by |
boolean | null |
|
sender_name |
string | null |
No control characters and none of these: " < > @ , ; : \ (max 60) |
logo_light_path |
string | null |
The path an upload answered, or null to remove the logo. |
logo_dark_path |
string | null |
The path an upload answered, or null to remove the logo for dark backgrounds. |
favicon_path |
string | null |
The path an upload answered, or null to remove the icon. |
BrandUpload
| Field | Type | |
|---|---|---|
upload_url |
uri | null |
PUT the file's bytes here, with its content type. One file; treat it as a secret. Null in a replay of the same Idempotency-Key: ask for a new one. (required) |
path |
string |
Send it to PATCH /referrals/brand as the slot's field once the file is uploaded. (required) |
slot |
BrandFileSlot |
logo_light: the logo on light backgrounds. logo_dark: on dark ones. favicon: the browser tab's icon. (required; one of logo_light, logo_dark, favicon) |
expires_at |
date-time |
(required) |
BrandUploadRequest
| Field | Type | |
|---|---|---|
program_id |
uuid | null |
For a program's override. |
slot |
BrandFileSlot |
logo_light: the logo on light backgrounds. logo_dark: on dark ones. favicon: the browser tab's icon. (required; one of logo_light, logo_dark, favicon) |
content_type |
string |
(required; one of image/png, image/jpeg, image/webp) |
Broadcast
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
program_id |
uuid |
(required) |
subject |
string |
(required) |
body |
string |
(required) |
audience |
BroadcastAudience |
(required) |
status |
string |
(required; one of draft, sending, sent) |
via |
string |
(required; one of dashboard, api) |
link_hosts |
string[] |
Every host the message links to. (required) |
preview |
string |
The first line, with sample values in the merge fields. (required) |
test_sends |
integer |
(required) |
tested_at |
date-time | null |
(required) |
tested_current |
boolean |
Whether the current text was test-sent. (required) |
sent_at |
date-time | null |
(required) |
finished_at |
date-time | null |
(required) |
counts |
object |
(required) |
created_at |
date-time |
(required) |
updated_at |
date-time |
(required) |
BroadcastAudience
| Field | Type | |
|---|---|---|
kind |
string |
(required; one of all, group, partners) |
group_id |
uuid |
With group: the group or level. |
partner_ids |
uuid[] |
With partners: the promoters you picked. (<= 500 items) |
BroadcastCreate
| Field | Type | |
|---|---|---|
subject |
string |
(required; max 120) |
body |
string |
Markdown subset: paragraphs, bold, italic, links and lists. No HTML, no images, https links only. {name}, {link} and {code} are filled per promoter. (required; max 5000) |
audience |
BroadcastAudience |
BroadcastList
| Field | Type | |
|---|---|---|
data |
Broadcast[] |
(required) |
BroadcastReferrer
| Field | Type | |
|---|---|---|
referrer_id |
uuid |
(required) |
external_id |
string |
Your own id for the user. (required) |
display_name |
string |
(required) |
link |
string | null |
(required) |
code |
string | null |
(required) |
BroadcastReferrerList
| Field | Type | |
|---|---|---|
data |
BroadcastReferrer[] |
(required) |
next_cursor |
string | null |
(required) |
BroadcastTested
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
program_id |
uuid |
(required) |
subject |
string |
(required) |
body |
string |
(required) |
audience |
BroadcastAudience |
(required) |
status |
string |
(required; one of draft, sending, sent) |
via |
string |
(required; one of dashboard, api) |
link_hosts |
string[] |
Every host the message links to. (required) |
preview |
string |
The first line, with sample values in the merge fields. (required) |
test_sends |
integer |
(required) |
tested_at |
date-time | null |
(required) |
tested_current |
boolean |
Whether the current text was test-sent. (required) |
sent_at |
date-time | null |
(required) |
finished_at |
date-time | null |
(required) |
counts |
object |
(required) |
created_at |
date-time |
(required) |
updated_at |
date-time |
(required) |
sent_to |
string |
The key creator's own address, where the test went. (required) |
BroadcastUpdate
| Field | Type | |
|---|---|---|
subject |
string |
(max 120) |
body |
string |
(max 5000) |
audience |
BroadcastAudience |
CodeCreate
| Field | Type | |
|---|---|---|
code |
string | null |
3 to 40 letters, digits and hyphens. Left out, the program's suggestion. (max 64) |
CodeUpdate
| Field | Type | |
|---|---|---|
stripe_promotion_code |
string | null |
promo_… in your own Stripe, or null to forget it. (required; max 255) |
Commission
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
status |
CommissionStatus |
(required; one of pending, approved, payable, paid, reversed, fraud) |
amount_minor |
integer |
Net of reversals. Negative for a downward adjustment. (required) |
gross_minor |
integer |
(required) |
reversed_minor |
integer |
(required) |
currency |
string |
(required) |
hold_until |
date-time | null |
(required) |
approved_at |
date-time | null |
(required) |
reversed_at |
date-time | null |
(required) |
created_at |
date-time |
(required) |
reason |
string | null |
(required) |
adjustment |
boolean |
(required) |
under_review |
boolean |
(required) |
risk |
object | null |
Why the fraud engine held this commission longer (hold) or sent it to a person (review): the score and every reason as a sentence, from counts and times. Null when it held nothing. See Fraud and risk. |
partner |
PartnerRef |
(required) |
conversion |
object | null |
(required) |
basis_text |
string |
Why it is this amount: "30% of 49.00 on Yearly plans (Gold level), capped at 120.00 per customer". Program rate for a commission recorded before reward rules had reasons. (required) |
capped_minor |
integer |
What the program's monthly ceiling per partner held back from this commission. (required) |
bonus |
string | null |
milestone for a bonus a partner earned by crossing a line, manual for one you paid by hand (which has no conversion), null for anything else. (required; one of milestone, manual, null) |
test |
boolean |
From TEST data, earned by the program's test partner. Never payable. |
tier |
integer |
2 for a recruiter's share of what a partner they recruited earned (the second tier); 1 for everything else. (one of 1, 2) |
CommissionAdjust
| Field | Type | |
|---|---|---|
amount_minor |
integer |
Signed minor units; not 0. (required) |
reason |
string |
Trimmed; recorded in the activity log. (required; min 1; max 500) |
CommissionApproveRequest
| Field | Type | |
|---|---|---|
reason |
string |
Why it is paid before its hold ends. Kept in the activity log. (required; max 500) |
CommissionList
| Field | Type | |
|---|---|---|
data |
Commission[] |
(required) |
next_cursor |
NextCursor |
Pass back as cursor for the next page; null on the last page. (required) |
CommissionStatus
One of: pending, approved, payable, paid, reversed, fraud.
Contest
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
program_id |
uuid |
(required) |
name |
string |
(required) |
description |
string | null |
(required) |
starts_at |
date-time |
(required) |
ends_at |
date-time |
(required) |
metric |
BoardMetric |
customers: paying customers brought; sales: payments; revenue: their amount, in minor units; friends: on a waitlist, friends who joined and confirmed. (required; one of customers, sales, revenue, friends) |
group_id |
uuid | null |
Only one group or level may enter; null for every promoter. (required) |
prizes |
ContestPrize[] |
(required) |
minimum |
integer |
What a promoter must reach to win a prize. (required) |
status |
ContestStatus |
(required; one of scheduled, running, ended, awarded, cancelled) |
status_text |
string |
(required) |
ended_at |
date-time | null |
(required) |
awarded_at |
date-time | null |
(required) |
cancelled_at |
date-time | null |
(required) |
cancel_reason |
string | null |
(required) |
created_at |
date-time |
(required) |
updated_at |
date-time |
(required) |
ContestAward
| Field | Type | |
|---|---|---|
contest |
Contest |
(required) |
awarded |
object[] |
(required) |
ContestCreate
| Field | Type | |
|---|---|---|
description |
string | null |
(max 1000) |
starts_at |
date-time |
(required) |
ends_at |
date-time |
At most 366 days after the start. (required) |
metric |
BoardMetric |
customers: paying customers brought; sales: payments; revenue: their amount, in minor units; friends: on a waitlist, friends who joined and confirmed. (one of customers, sales, revenue, friends) |
group_id |
uuid | null |
|
prizes |
ContestPrizeInput[] |
(required; <= 20 items; >= 1 items) |
minimum |
integer |
(>= 0) |
name |
string |
(required; min 1; max 80) |
ContestDetail
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
program_id |
uuid |
(required) |
name |
string |
(required) |
description |
string | null |
(required) |
starts_at |
date-time |
(required) |
ends_at |
date-time |
(required) |
metric |
BoardMetric |
customers: paying customers brought; sales: payments; revenue: their amount, in minor units; friends: on a waitlist, friends who joined and confirmed. (required; one of customers, sales, revenue, friends) |
group_id |
uuid | null |
Only one group or level may enter; null for every promoter. (required) |
prizes |
ContestPrize[] |
(required) |
minimum |
integer |
What a promoter must reach to win a prize. (required) |
status |
ContestStatus |
(required; one of scheduled, running, ended, awarded, cancelled) |
status_text |
string |
(required) |
ended_at |
date-time | null |
(required) |
awarded_at |
date-time | null |
(required) |
cancelled_at |
date-time | null |
(required) |
cancel_reason |
string | null |
(required) |
created_at |
date-time |
(required) |
updated_at |
date-time |
(required) |
standings |
ContestStanding[] |
(required) |
computed_at |
date-time | null |
(required) |
award |
object |
(required) |
ContestList
| Field | Type | |
|---|---|---|
data |
Contest[] |
(required) |
ContestPrize
| Field | Type | |
|---|---|---|
rank |
integer |
(required; >= 1; <= 100) |
through_rank |
integer |
The same prize for every place from rank to this one. (>= 1; <= 100) |
kind |
string |
(required; one of cash, grant) |
amount_minor |
integer |
Cash, credit or a gift card, in the program's currency's minor units. (>= 1) |
reward_type |
string |
A grant's type. (one of credit, gift_card, free_period, perk) |
perk_text |
string |
A perk's words: "A hoodie". (max 120) |
period_count |
integer |
A free period's length: up to 365 days or 36 months. (>= 1; <= 365) |
period_unit |
string |
(one of day, month) |
place |
string |
"1st", "2nd to 5th". (required) |
text |
string |
"500.00 USD", "A hoodie", "1 month free". (required) |
ContestPrizeInput
| Field | Type | |
|---|---|---|
rank |
integer |
(required; >= 1; <= 100) |
through_rank |
integer |
The same prize for every place from rank to this one. (>= 1; <= 100) |
kind |
string |
(required; one of cash, grant) |
amount_minor |
integer |
Cash, credit or a gift card, in the program's currency's minor units. (>= 1) |
reward_type |
string |
A grant's type. (one of credit, gift_card, free_period, perk) |
perk_text |
string |
A perk's words: "A hoodie". (max 120) |
period_count |
integer |
A free period's length: up to 365 days or 36 months. (>= 1; <= 365) |
period_unit |
string |
(one of day, month) |
ContestStanding
| Field | Type | |
|---|---|---|
rank |
integer |
(required) |
place |
integer | null |
Place among those who reached the minimum (what a prize goes by). (required) |
qualified |
boolean |
(required) |
value |
integer |
(required) |
partner_id |
uuid |
(required) |
kind |
string |
(required; one of partner, referrer) |
display_name |
string |
(required) |
external_id |
string | null |
(required) |
appears_as |
BoardNameChoice |
How a promoter chose to appear to the program's other promoters. hidden is "A promoter", the default for a partner; one of your own users starts initials. (required; one of hidden, initials, display) |
under_review |
boolean |
(required) |
prize |
string | null |
The prize this place wins as things stand. (required) |
ContestStatus
One of: scheduled, running, ended, awarded, cancelled.
ContestUpdate
| Field | Type | |
|---|---|---|
description |
string | null |
(max 1000) |
starts_at |
date-time |
|
ends_at |
date-time |
At most 366 days after the start. |
metric |
BoardMetric |
customers: paying customers brought; sales: payments; revenue: their amount, in minor units; friends: on a waitlist, friends who joined and confirmed. (one of customers, sales, revenue, friends) |
group_id |
uuid | null |
|
prizes |
ContestPrizeInput[] |
(<= 20 items; >= 1 items) |
minimum |
integer |
(>= 0) |
name |
string |
(min 1; max 80) |
Conversion
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
event |
ConversionEvent |
(required; one of lead, sale, refund, dispute) |
amount_minor |
integer |
Minor units. 0 for a lead; for a refund, this refund alone. (required) |
currency |
string | null |
(required) |
occurred_at |
date-time |
(required) |
attributed |
boolean |
Whether a partner was credited. (required) |
partner |
object | null |
(required) |
commission |
object | null |
(required) |
commissions |
object[] |
Every commission the conversion created, net of reversals, each with why it is that amount. A sale whose items matched two reward rules earns two; commission is the first of them. (required) |
duplicate |
boolean |
True when this event_id had been reported before and nothing changed. (required) |
grants |
object[] |
The rewards that are not cash this conversion gave, to the promoter or to the friend, each in words. A friend's discount is here only when the sale shows it was used. |
referee_referrer |
object | null |
Invite after purchase: the friend who just made their first payment now has a referral link of their own, so your thank-you page can ask them to invite their friends. Null unless the program asks (invite_after_purchase), you named the customer by your own id, and the link exists. |
referral_refused |
string | null |
A referral code that was sent and not applied, and why: late_code when it came more than the program's late_code_days after the customer signed up. Null otherwise. (one of late_code, null) |
test |
boolean |
TEST data: a test sale, test: true, or a test-mode or sandbox event from your billing system. Credited to the program's test partner only; never payable, never counted. |
ConversionCreate
| Field | Type | |
|---|---|---|
program_id |
uuid |
(required) |
event |
ConversionEvent |
(required; one of lead, sale, refund, dispute) |
event_id |
string |
YOUR id for this event, and the idempotency key. Reporting it again changes nothing, so a retry sends the same one; a new one is a new conversion. (required; min 1; max 255) |
occurred_at |
date-time |
|
customer |
object |
|
referral |
object |
|
amount |
integer |
Minor units. (>= 0) |
currency |
string |
|
refund_of |
string |
The sale's event_id. (min 1; max 255) |
items |
SaleItem[] |
A sale's lines, so a reward rule can pay per product, price, plan or interval. Their amounts must add up to the sale's amount (400 otherwise). Leave them out and the sale is matched as a whole. (<= 100 items) |
test |
boolean |
Record this as TEST data: credited to the program's test partner whoever's code it carries, never payable, never counted toward a limit or a total, emailing nobody, and sent only to webhook endpoints that receive test events. A test event_id and a live one never collide, and neither do test and live customers. Deleted with DELETE /v1/referrals/programs/{id}/test-data. |
ConversionEvent
One of: lead, sale, refund, dispute.
CurrencyTotal
| Field | Type | |
|---|---|---|
currency |
string |
(required) |
amount_minor |
integer |
(required) |
CustomerReferral
| Field | Type | |
|---|---|---|
invited_by |
string | null |
The inviter's name (required) |
invited_at |
date-time | null |
(required) |
via |
string | null |
(required; one of link, discount_code, null) |
DataSubject
Everything Bolt Claw holds about one of your customers or in-app referrers in this workspace.
| Field | Type | |
|---|---|---|
external_id |
string |
The id you looked up. (required) |
customers |
object[] |
Each program the person was referred in, as a customer. (required) |
referrers |
object[] |
Each program the person is an in-app referrer in. (required) |
DataSubjectErased
What was erased, counted. Amounts and commissions stay as anonymous rows.
| Field | Type | |
|---|---|---|
external_id |
string |
(required) |
customers |
integer |
Customer records turned into anonymous ones. (required) |
conversions |
integer |
Their conversions (required) |
referrers |
integer |
In-app referrers erased. (required) |
webhook_payloads |
integer |
Stored webhook bodies whose copy of the id was scrubbed. (required) |
DirectoryCategory
One of: software, ai_tools, education, finance, health, ecommerce, creator_tools, agencies, other.
DiscountCode
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
code |
string |
3 to 40 letters (required) |
active |
boolean |
False once the code was switched off. (required) |
created_at |
date-time |
(required) |
partner |
object | null |
(required) |
stripe_promotion_code |
string | null |
The promotion code this code is in your own Stripe. (required) |
shopify_discount |
string | null |
The discount this code is in the program's linked Shopify shop. (required) |
shopify_error |
string | null |
Why the last Shopify sync could not make it there. (required; one of no_friend_discount, refused, unavailable, taken, null) |
DiscountCodeList
| Field | Type | |
|---|---|---|
data |
DiscountCode[] |
(required) |
next_cursor |
NextCursor |
Pass back as cursor for the next page; null on the last page. (required) |
friend_discount |
any |
(required) |
DnsRecord
| Field | Type | |
|---|---|---|
type |
string |
(required; one of TXT, CNAME) |
name |
string |
The record's full name, as your DNS host may ask for it. (required) |
value |
string | null |
What it must hold; null for the CNAME until the TXT record is seen, when the target for this domain is known. (required) |
purpose |
string |
ownership: proves the domain is yours. routing: points it at us. provider: a second TXT record our hosting provider asks for. (required; one of ownership, routing, provider) |
done |
boolean |
In place, as last checked. (required) |
Domain
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
host |
string |
Lower case, international names in their punycode form. (required) |
kind |
DomainKind |
links: your referral links answer on it (https://go.example.com/<code>). portal: your partner portal and join pages, and links too. (required; one of links, portal) |
status |
DomainStatus |
pending_ownership: waiting for the TXT record that proves it is yours. pending_dns: waiting for the CNAME record. pending_certificate: the certificate is being issued. active: answering. detached: its records stopped pointing at us, so it stopped answering; put them back to reconnect it, or it is removed after 7 days (remove_after). (required; one of pending_ownership, pending_dns, pending_certificate, active, detached) |
status_text |
string |
The status, in a few words. (required) |
all_programs |
boolean |
It serves every program, new ones included. (required) |
program_ids |
uuid[] |
The programs it serves when all_programs is false; empty otherwise. (required) |
records |
DnsRecord[] |
Every DNS record it needs, in the order to set them. (required) |
problem |
string | null |
What is in the way, as a sentence; null when nothing is. (required) |
caa_warning |
boolean |
A CAA record on your domain does not allow letsencrypt.org, which issues the certificate. (required) |
url |
uri | null |
https://<host> once active. (required) |
checked_at |
date-time | null |
(required) |
activated_at |
date-time | null |
(required) |
detached_at |
date-time | null |
(required) |
remove_after |
date-time | null |
When a detached domain is removed. (required) |
created_at |
date-time |
(required) |
DomainCreate
| Field | Type | |
|---|---|---|
host |
string |
A subdomain of yours, such as go.example.com. Never a domain's root, a wildcard or an IP address. (required; max 300) |
kind |
DomainKind |
links: your referral links answer on it (https://go.example.com/<code>). portal: your partner portal and join pages, and links too. (required; one of links, portal) |
all_programs |
boolean | null |
Serve every program, new ones included. True when left out. |
program_ids |
array | null |
The programs it serves, when all_programs is false (1 to 50). (<= 50 items) |
DomainKind
links: your referral links answer on it (https://go.example.com/<code>). portal: your partner portal and join pages, and links too.
One of: links, portal.
DomainList
| Field | Type | |
|---|---|---|
data |
Domain[] |
(required) |
DomainRemoved
| Field | Type | |
|---|---|---|
removed |
boolean |
(required; one of true) |
host |
string |
(required) |
DomainStatus
pending_ownership: waiting for the TXT record that proves it is yours. pending_dns:
waiting for the CNAME record. pending_certificate: the certificate is being issued.
active: answering. detached: its records stopped pointing at us, so it stopped
answering; put them back to reconnect it, or it is removed after 7 days (remove_after).
One of: pending_ownership, pending_dns, pending_certificate, active, detached.
EmbedConfig
The panel's look, copy and sharing. Every key is optional; a key left out keeps its
default, and an unknown key is 400. Sent whole: a new embed_config replaces the old
one. At most 8 KB once written as JSON.
| Field | Type | |
|---|---|---|
accent |
string |
The accent colour, like #2A9D8F. |
radius |
integer |
Corner radius in pixels. (>= 0; <= 24) |
theme |
string |
auto follows the visitor's device. (one of light, dark, auto) |
link_mode |
string |
boltclaw (default): the referrer's link goes through the program's own /r/{code} redirect, which counts clicks. direct: your own site with ?via={code}, which names no other domain; clicks are then counted only when your server reports them. (one of boltclaw, direct) |
invite_from_own_system |
boolean |
Shows a "send from my own system" field. The panel hands the address a referrer types to your page (the inviteRequested event) and nothing else: Bolt Claw never sees or emails it. |
channels |
ShareChannel[] |
The share buttons to show, in order. Left out, all nine. (<= 9 items) |
copy |
EmbedCopy |
Words you replace in the panel. Plain text only, never HTML. Any key left out keeps the panel's own English. Each value is 1 to the number of characters shown beside it. |
messages |
EmbedMessages |
The text each share button pre-fills, at most 280 characters, plain text. {link} is replaced by the referrer's link (a message without it gets the link on a new line) and {program} by the program's name. |
email_subject |
string |
(max 120) |
email_body |
string |
(max 1000) |
EmbedCopy
Words you replace in the panel. Plain text only, never HTML. Any key left out keeps the panel's own English. Each value is 1 to the number of characters shown beside it.
| Field | Type | |
|---|---|---|
headline |
string |
(max 120) |
subline |
string |
(max 240) |
step_1 |
string |
(max 120) |
step_2 |
string |
(max 120) |
step_3 |
string |
(max 120) |
copy_button |
string |
(max 30) |
copied_label |
string |
(max 30) |
share_button |
string |
(max 30) |
email_button |
string |
(max 30) |
email_title |
string |
(max 60) |
terms_label |
string |
(max 40) |
empty_title |
string |
(max 80) |
empty_body |
string |
(max 240) |
payout_title |
string |
(max 80) |
payout_body |
string |
(max 240) |
rules_title |
string |
(max 60) |
history_title |
string |
(max 60) |
expired_title |
string |
(max 80) |
expired_body |
string |
(max 240) |
EmbedMessages
The text each share button pre-fills, at most 280 characters, plain text. {link} is
replaced by the referrer's link (a message without it gets the link on a new line) and
{program} by the program's name.
| Field | Type | |
|---|---|---|
native |
string |
(max 280) |
whatsapp |
string |
(max 280) |
x |
string |
(max 280) |
linkedin |
string |
(max 280) |
facebook |
string |
(max 280) |
messenger |
string |
(max 280) |
telegram |
string |
(max 280) |
sms |
string |
(max 280) |
email |
string |
(max 280) |
EmbedSettings
A program's in-app referral panel. Change it with embed_enabled, embed_origins, embed_config and approve_referrers on PATCH /referrals/programs/{id}.
| Field | Type | |
|---|---|---|
enabled |
boolean |
Whether sessions can be minted and the panel loads. Off, both fail. (required) |
key |
string |
The public embed key your page passes as embedKey. Not a secret; rotate it with POST /referrals/programs/{id}/embed/rotate-key. (required) |
panel_url |
string |
The address the panel's iframe loads. (required) |
origins |
string[] |
The sites allowed to frame the panel, like https://app.example.com. https only (http://localhost:3000, with a port, is allowed for development), no wildcard, no path. Any other site that tries to frame it is refused by the browser. (required; <= 10 items) |
generation |
integer |
Goes up by one each time you end every live session. (required; >= 1) |
approve_referrers |
boolean |
True (default): a new referrer is approved at once. False: pending until you approve. (required) |
config |
EmbedConfig |
The panel's look, copy and sharing. Every key is optional; a key left out keeps its default, and an unknown key is 400. Sent whole: a new embed_config replaces the old one. At most 8 KB once written as JSON. (required) |
EmbedTokenCreate
| Field | Type | |
|---|---|---|
referrer |
object |
The user the session is for; they are made a referrer if they are not one. Leave it out only for mode preview. |
ttl_seconds |
integer |
How long the token works, in seconds. (default 3600; >= 60; <= 86400) |
mode |
string |
panel for a real referrer; preview shows sample data and needs no referrer. (default panel; one of panel, preview) |
EmbedTokenCreated
| Field | Type | |
|---|---|---|
token |
string | null |
bcet_ and 43 characters. Shown once: hand it to the panel and do not store or log it. Null in a replay of the same Idempotency-Key: mint a new one. (required) |
expires_at |
date-time |
(required) |
referrer |
any |
The referrer the session is for; null for a preview session. (required) |
FileLink
| Field | Type | |
|---|---|---|
url |
uri | null |
Downloads the PDF once, within ten minutes. Treat it as a secret. Null in a replay of the same Idempotency-Key: ask for a new one. (required) |
expires_at |
date-time |
(required) |
Flag
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
rule |
FlagRule |
Which rule raised a flag. risk_review is the risk engine asking a person (the score reached the program's review_at); self_referral_card is one card paying for the promoter's own customers; payout_change is a payout built inside the window after a partner changed where they are paid; manual_hold is "Hold this promoter's payouts". other is a rule this version of the API does not name yet. (required; one of self_referral, click_velocity, duplicate_payout, risk_review, self_referral_card, payout_change, manual_hold, other) |
score |
integer | null |
The risk score behind a risk_review flag; null for the other rules. (required; >= 0; <= 100) |
status |
string |
(required; one of open, dismissed, confirmed) |
subject |
string |
(required; one of click, partner, conversion, commission) |
created_at |
date-time |
(required) |
resolved_at |
date-time | null |
(required) |
note |
string | null |
Your note (required) |
partner |
object | null |
The partner it is about, by their name in this program. (required) |
commission |
object | null |
(required) |
conversion |
object | null |
(required) |
link |
object | null |
A velocity flag's link, by its code and type. (required) |
FlagConfirm
| Field | Type | |
|---|---|---|
note |
string | null |
(max 500) |
ban_partner |
boolean |
Ban the partner from the program too (default false). |
FlagDecision
| Field | Type | |
|---|---|---|
note |
string | null |
Kept on the flag and in the activity log. (max 500) |
FlagDetail
| Field | Type | |
|---|---|---|
flag |
Flag |
(required) |
score |
integer | null |
(required; >= 0; <= 100) |
reasons |
object[] |
Every rule that counted, with its weight and what it saw, as a sentence. (required) |
assessed_at |
date-time | null |
(required) |
timeline |
object[] |
The customer's click, signup, sale, refunds and disputes in order, with the seconds between them. (required) |
history |
object | null |
The partner's history in this program. (required) |
FlagList
| Field | Type | |
|---|---|---|
data |
Flag[] |
(required; <= 200 items) |
FlagResolution
| Field | Type | |
|---|---|---|
flag |
Flag |
(required) |
commissions |
integer |
Commissions this decision moved to fraud; 0 for a dismissal. (required) |
banned |
boolean |
(required) |
FlagRule
Which rule raised a flag. risk_review is the risk engine asking a person (the score
reached the program's review_at); self_referral_card is one card paying for the
promoter's own customers; payout_change is a payout built inside the window after a
partner changed where they are paid; manual_hold is "Hold this promoter's payouts".
other is a rule this version of the API does not name yet.
One of: self_referral, click_velocity, duplicate_payout, risk_review, self_referral_card, payout_change, manual_hold, other.
FlagsBulk
| Field | Type | |
|---|---|---|
flag_ids |
uuid[] |
(required; <= 200 items; >= 1 items) |
note |
string |
One reason for all of them. (required; min 1; max 500) |
FlagsBulkConfirm
| Field | Type | |
|---|---|---|
flag_ids |
uuid[] |
(required; <= 200 items; >= 1 items) |
note |
string |
One reason for all of them. (required; min 1; max 500) |
ban_partners |
boolean |
Ban each flag's partner too (default false). |
FlagsResolution
| Field | Type | |
|---|---|---|
decided |
integer |
(required) |
commissions |
integer |
Commissions moved to fraud. (required) |
grants |
integer |
Rewards that are not cash (required) |
banned |
integer |
(required) |
skipped |
object[] |
(required) |
ForfeitRequest
| Field | Type | |
|---|---|---|
reason |
string |
Why, recorded in the activity log. (required; min 3; max 500) |
ForfeitResult
| Field | Type | |
|---|---|---|
partner_id |
uuid |
(required) |
forfeited |
integer |
How many commissions were given up. (required) |
totals |
CurrencyTotal[] |
What was given up, per currency. (required) |
Grant
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
program_id |
uuid |
(required) |
beneficiary |
string |
(required; one of promoter, referee) |
reward_type |
GrantType |
(required; one of credit, free_period, discount, perk, gift_card) |
value |
object |
(required) |
summary |
string |
What it is, in a few words: "1 month free", "10.00 USD in credit". (required) |
status |
GrantStatus |
pending waits out the program's hold; ready is yours to give; fulfilled was given; failed could not be given yet; cancelled by a refund or dispute before it was given; reversed by one after. (required; one of pending, ready, fulfilled, failed, cancelled, reversed) |
status_text |
string |
Where it stands (required) |
hold_until |
date-time | null |
(required) |
ready_at |
date-time | null |
(required) |
fulfilled_at |
date-time | null |
(required) |
ended_at |
date-time | null |
When it was cancelled or reversed. (required) |
fulfilment |
string | null |
How it was or will be given; merchant is your own system. (required; one of merchant, stripe, tremendous, null) |
reference |
string | null |
Your (or Stripe's) reference for it (required) |
reason |
string | null |
Why it was cancelled (required) |
recipient |
object |
Who receives it, as you know them: one of your own users with a link (referrer, by your external_id), an affiliate (partner, by their name in the program), or the friend (customer, by your own id for them). (required) |
partner |
object | null |
The promoter the sale was credited to. (required) |
customer_external_id |
string | null |
The friend (required) |
conversion |
object | null |
(required) |
created_at |
date-time |
(required) |
updated_at |
date-time |
(required) |
test |
boolean |
From TEST data. Never given through Stripe, never counted. |
GrantFail
| Field | Type | |
|---|---|---|
reason |
string |
Why it cannot be given. Shown to you (required; min 1; max 500) |
GrantFulfil
| Field | Type | |
|---|---|---|
reference |
string |
Your reference for the reward you gave, like an order or credit note number. The same one again is safe. (required; min 1; max 200) |
note |
string | null |
A note for your own team. (max 500) |
GrantList
| Field | Type | |
|---|---|---|
data |
Grant[] |
(required) |
next_cursor |
NextCursor |
Pass back as cursor for the next page; null on the last page. (required) |
GrantStatus
pending waits out the program's hold; ready is yours to give; fulfilled was given;
failed could not be given yet; cancelled by a refund or dispute before it was given;
reversed by one after.
One of: pending, ready, fulfilled, failed, cancelled, reversed.
GrantType
One of: credit, free_period, discount, perk, gift_card.
Group
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
name |
string |
(required) |
description |
string | null |
(required) |
is_default |
boolean |
A newly approved partner starts here. (required) |
rank |
integer | null |
A level's place on the ladder (1 is the lowest); null for a plain group. (required) |
level |
boolean |
(required) |
minimum |
LevelMinimum |
What a partner needs to reach a level: revenue in amount_minor, or sales or customers in count, over the last window_days (30 to 365), or all time when null. Counted net of refunds. Null: everyone qualifies. (required) |
keep_earned |
boolean |
A partner who reached this level is never moved down by the daily job. (required) |
partners |
integer |
Approved and pending partners in it now. (required) |
summary |
string |
(required) |
created_at |
date-time |
(required) |
updated_at |
date-time |
(required) |
GroupCreate
| Field | Type | |
|---|---|---|
name |
string |
1 to 60 characters after trimming; unique in the program. (required; max 200) |
description |
string | null |
(max 1000) |
is_default |
boolean |
|
rank |
integer | null |
1 to 100 makes it a level; null for a plain group. |
minimum |
LevelMinimum |
What a partner needs to reach a level: revenue in amount_minor, or sales or customers in count, over the last window_days (30 to 365), or all time when null. Counted net of refunds. Null: everyone qualifies. |
keep_earned |
boolean |
GroupList
| Field | Type | |
|---|---|---|
data |
Group[] |
Every group and level of the program, at most 50, levels first by rank. (required; <= 51 items) |
GroupUpdate
Every field optional; a field left out keeps its value.
| Field | Type | |
|---|---|---|
name |
string |
(max 200) |
description |
string | null |
(max 1000) |
is_default |
boolean |
|
rank |
integer | null |
|
minimum |
LevelMinimum |
What a partner needs to reach a level: revenue in amount_minor, or sales or customers in count, over the last window_days (30 to 365), or all time when null. Counted net of refunds. Null: everyone qualifies. |
keep_earned |
boolean |
HistoryStage
Where the friend is: joined, paid, rewarded (the reward is paid out) or not_counted.
One of: joined, paid, rewarded, not_counted.
ImageUpload
| Field | Type | |
|---|---|---|
upload_url |
string | null |
PUT the file here. Null in a replay of the same Idempotency-Key. (required) |
path |
string |
(required) |
expires_at |
date-time |
(required) |
ImageUploadCreate
| Field | Type | |
|---|---|---|
content_type |
string |
(required; one of image/png, image/jpeg, image/webp) |
Import
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
program_id |
uuid |
(required) |
invites |
integer |
(required) |
with_balance |
integer |
(required) |
claimed |
integer |
(required) |
created_at |
date-time |
(required) |
ImportCommit
| Field | Type | |
|---|---|---|
rows |
ImportRow[] |
(<= 500 items; >= 1 items) |
csv |
string |
A CSV with a header row; at most 500 rows a call. (max 65000) |
import_id |
uuid |
Continue an import begun by an earlier part of the file. |
ImportCommitted
| Field | Type | |
|---|---|---|
import_id |
uuid |
(required) |
invites |
ImportInvite[] |
(required) |
ImportInvite
| Field | Type | |
|---|---|---|
id |
string |
(required) |
line |
integer | null |
(required) |
name |
string |
(required) |
code |
string | null |
(required) |
group_id |
string | null |
(required) |
rate_bps |
integer | null |
(required) |
balance |
object | null |
(required) |
link |
string |
This person's personal link. Send it from your own email. (required) |
state |
string |
(required; one of open, waiting, claimed, revoked) |
claimed_at |
date-time | null |
(required) |
ImportInviteList
| Field | Type | |
|---|---|---|
data |
ImportInvite[] |
(required) |
next_cursor |
string | null |
(required) |
ImportList
| Field | Type | |
|---|---|---|
data |
Import[] |
(required) |
ImportPreview
| Field | Type | |
|---|---|---|
rows |
integer |
(required) |
valid |
integer |
(required) |
errors |
ImportRowError[] |
(required) |
with_balance |
integer |
(required) |
ImportRequest
Either rows or csv, not both.
| Field | Type | |
|---|---|---|
rows |
ImportRow[] |
(<= 500 items; >= 1 items) |
csv |
string |
A CSV with a header row; at most 500 rows a call. (max 65000) |
ImportRow
| Field | Type | |
|---|---|---|
line |
integer |
The row's line in your file, echoed in errors. (>= 1) |
name |
string |
(required; max 80) |
email |
string |
Hashed on arrival, never stored, never emailed. (required; max 254) |
code |
string |
A vanity code held for this person. (max 40) |
group |
string |
A group or level of the program, by name. (max 80) |
rate_bps |
integer |
A custom rate for this person, in basis points. (>= 1; <= 10000) |
balance_minor |
integer |
An opening balance owed, paid as a bonus when they claim. (>= 1) |
currency |
string |
The balance's currency: the program's own. |
ImportRowError
| Field | Type | |
|---|---|---|
line |
integer |
(required) |
field |
string |
(required; one of name, email, code, group, rate, balance, currency, row) |
message |
string |
(required) |
Integration
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
provider |
BillingProvider |
A billing system Bolt Claw connects to directly. (required; one of stripe, paddle, polar, whop, revenuecat, woocommerce, fastspring, lemonsqueezy, shopify, chargebee, recurly, gumroad) |
webhook_url |
string |
(required) |
has_secret |
boolean |
(required) |
customer_metadata_key |
string |
(required) |
status |
string |
disconnected: a Stripe App connection that was uninstalled or disconnected. (required; one of active, disabled, disconnected) |
last_event_at |
date-time | null |
(required) |
created_at |
date-time |
(required) |
codes_and_credits |
object |
Whether a restricted Stripe key is set, which lets Bolt Claw make codes and give credit and free months in your Stripe. The key itself is never returned, not even in part. |
payload_format |
string | null |
The shape of the last verified Stripe event. thin events carry no data; choose Snapshot in Stripe. (one of snapshot, thin, null) |
auth_mode |
string |
How a delivery proves itself: a signing secret, Basic credentials Bolt Claw generated (Chargebee), a header token Bolt Claw generated (RevenueCat), the Stripe App (oauth), an unsigned ping confirmed through the provider's API with your token (Gumroad), or Bolt Claw's Shopify app (shopify_app), linked from the shop's own Shopify admin. (one of signing_secret, basic, header_token, oauth, api_key, shopify_app) |
test_mode |
boolean |
The Sandbox switch. Every event of this integration is recorded as test data. |
credential |
object |
Whether the credential is saved (or generated), and since when. Never the value. |
read_key |
object |
Recurly's read-only key or Gumroad's token. Whether one is saved, never the value. |
provider_account |
string | null |
Recurly's region (us or eu), or the Shopify shop that sent the first delivery. |
shopify |
object | null |
Bolt Claw's Shopify app, linked to this program from the shop's own Shopify admin. Null for every other kind of integration. connected is false once it was unlinked or the shop uninstalled the app. |
app |
object | null |
The Stripe App connection. Null for every other kind of integration. |
health |
object |
What the setup status reads. Fixed words and counts; nothing from a delivery. |
IntegrationStatus
| Field | Type | |
|---|---|---|
program_id |
uuid |
(required) |
checked_at |
date-time |
(required) |
overall |
SetupState |
(required; one of connected, warnings, not_connected) |
parts |
SetupPart[] |
(required) |
integrations |
Integration[] |
(required) |
test_conversions |
integer |
Test conversions the program holds now. DELETE /v1/referrals/programs/{id}/test-data deletes them. (required) |
Invite
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
url |
string |
The link to send. Bolt Claw emails nobody. (required) |
max_uses |
integer |
(required) |
used_count |
integer |
(required) |
expires_at |
date-time | null |
(required) |
created_at |
date-time |
(required) |
state |
string |
(required; one of active, expired, used) |
InviteCreate
| Field | Type | |
|---|---|---|
max_uses |
integer |
How many people may join with it. 1 by default. (>= 1; <= 100000) |
expires_at |
date-time | null |
Within a year. |
InviteSummary
An invite without its link (shown once, when the invite is made).
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
max_uses |
integer |
(required) |
used_count |
integer |
(required) |
expires_at |
date-time | null |
(required) |
created_at |
date-time |
(required) |
state |
string |
(required; one of active, expired, used) |
InviteSummaryList
| Field | Type | |
|---|---|---|
data |
InviteSummary[] |
(required) |
next_cursor |
NextCursor |
Pass back as cursor for the next page; null on the last page. (required) |
Invoice
An invoice for one payout: one you issued in the promoter's name (self_billed), or their
own (uploaded). Read its PDF through POST /referrals/invoices/{id}/link.
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
payout_id |
uuid |
(required) |
partner_id |
uuid |
(required) |
kind |
string |
(required; one of self_billed, uploaded) |
status |
string |
issued: accepted unless the promoter disputes it by accept_by (14 days). accepted. disputed: paused until you issue a corrected one. void: replaced (see replaced_by). received: the promoter's own uploaded invoice. (required; one of issued, accepted, disputed, void, received) |
number |
string | null |
A self-billed invoice's number, in the promoter's own series with you, without gaps (SB1A2B3C4D-00042). Null for an uploaded one. (required) |
currency |
string |
(required) |
gross_minor |
integer |
The payout's amount. (required) |
net_minor |
integer |
(required) |
vat_minor |
integer |
(required) |
vat_bps |
integer | null |
The VAT rate in basis points, for a domestic supply. (required) |
vat_treatment |
string |
domestic: the promoter is in your own country and charges your VAT rate. reverse_charge: cross-border business to business, so you account for the VAT (the invoice says "Reverse charge"). outside_scope: you are outside the EU and the UK. none: an uploaded invoice. (required; one of domestic, reverse_charge, outside_scope, none) |
issued_at |
date-time |
(required) |
accept_by |
date-time | null |
(required) |
accepted_at |
date-time | null |
(required) |
disputed_at |
date-time | null |
(required) |
dispute_reason |
string | null |
(required; one of details_wrong, amount_wrong, vat_wrong, not_mine, other, null) |
dispute_note |
string | null |
What the promoter wrote, up to 500 characters. (required) |
replaced_by |
uuid | null |
(required) |
InvoiceList
| Field | Type | |
|---|---|---|
payout_id |
uuid |
(required) |
data |
Invoice[] |
(required) |
Leaderboard
| Field | Type | |
|---|---|---|
program_id |
uuid |
(required) |
settings |
any |
The program's setting; null when promoters see no leaderboard. (required) |
metric |
BoardMetric |
customers: paying customers brought; sales: payments; revenue: their amount, in minor units; friends: on a waitlist, friends who joined and confirmed. (required; one of customers, sales, revenue, friends) |
period |
BoardPeriod |
month and quarter are calendar ones (UTC); 30d the last 30 days; all since the program began. (required; one of month, 30d, quarter, all) |
title |
string |
"paying customers, this month". (required) |
since |
date-time | null |
(required) |
until |
date-time | null |
(required) |
ranked |
integer |
How many promoters have something counted. (required) |
computed_at |
date-time | null |
(required) |
rows |
LeaderboardRow[] |
(required) |
LeaderboardFields
The leaderboard promoters see, merged onto the current setting; null switches it off. You always see your own.
| Field | Type | |
|---|---|---|
metric |
BoardMetric |
customers: paying customers brought; sales: payments; revenue: their amount, in minor units; friends: on a waitlist, friends who joined and confirmed. (one of customers, sales, revenue, friends) |
period |
BoardPeriod |
month and quarter are calendar ones (UTC); 30d the last 30 days; all since the program began. (one of month, 30d, quarter, all) |
show |
string |
(one of counts, amounts) |
size |
integer |
(>= 10; <= 100) |
panel |
boolean |
LeaderboardRow
| Field | Type | |
|---|---|---|
rank |
integer |
(required; >= 1) |
value |
integer |
The count (required) |
partner_id |
uuid |
The membership id. (required) |
kind |
string |
referrer: one of your own users. (required; one of partner, referrer) |
display_name |
string |
(required) |
external_id |
string | null |
Your own id (required) |
appears_as |
BoardNameChoice |
How a promoter chose to appear to the program's other promoters. hidden is "A promoter", the default for a partner; one of your own users starts initials. (required; one of hidden, initials, display) |
under_review |
boolean |
An open fraud flag of theirs in the program. (required) |
last_activity_at |
date-time | null |
(required) |
LevelMinimum
What a partner needs to reach a level: revenue in amount_minor, or sales or
customers in count, over the last window_days (30 to 365), or all time when null.
Counted net of refunds. Null: everyone qualifies.
| Field | Type | |
|---|---|---|
metric |
string |
(required; one of revenue, sales, customers) |
amount_minor |
integer |
(>= 0) |
count |
integer |
(>= 0) |
window_days |
integer | null |
LevelRecalculation
| Field | Type | |
|---|---|---|
moved |
integer |
Partners moved to another level. (required) |
LibraryItem
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
program_id |
uuid |
(required) |
kind |
string |
(required; one of image, snippet, link) |
title |
string |
(required) |
body |
string | null |
A snippet's text, with {link} and {code} as written. (required) |
url |
string | null |
(required) |
host |
string | null |
(required) |
image_url |
string | null |
(required) |
ready |
boolean |
False for an image item whose file is not in yet. (required) |
group_id |
string | null |
(required) |
in_panel |
boolean |
(required) |
position |
integer |
(required) |
created_at |
date-time |
(required) |
updated_at |
date-time |
(required) |
LibraryItemCreate
| Field | Type | |
|---|---|---|
kind |
string |
(required; one of image, snippet, link) |
title |
string |
(required; max 120) |
body |
string |
A snippet's text. (max 2000) |
url |
string |
A link's https address. (max 2048) |
group_id |
uuid | null |
|
in_panel |
boolean |
A snippet only: shown to your own users in the in-app panel too. |
position |
integer |
(>= 0; <= 10000) |
LibraryItemDeleted
| Field | Type | |
|---|---|---|
deleted |
boolean |
(required; one of true) |
id |
uuid |
(required) |
LibraryItemList
| Field | Type | |
|---|---|---|
data |
LibraryItem[] |
(required) |
LibraryItemUpdate
| Field | Type | |
|---|---|---|
title |
string |
(max 120) |
body |
string |
(max 2000) |
url |
string |
(max 2048) |
group_id |
uuid | null |
|
in_panel |
boolean |
|
position |
integer |
(>= 0; <= 10000) |
image_path |
string |
An image item's file, from its upload. (max 200) |
LinkHost
| Field | Type | |
|---|---|---|
host |
string |
(required) |
source |
string |
product: the product URL's own host, needing no proof. (required; one of product, allowed) |
verified |
boolean |
(required) |
record |
object | null |
(required) |
checked_at |
date-time | null |
(required) |
problem |
string | null |
(required) |
LinkHostList
| Field | Type | |
|---|---|---|
data |
LinkHost[] |
(required) |
ListingSettings
| Field | Type | |
|---|---|---|
program_id |
uuid |
(required) |
listing_id |
uuid | null |
Null until the listing is first saved. (required) |
listed |
boolean |
(required) |
category |
DirectoryCategory |
(required; one of software, ai_tools, education, finance, health, ecommerce, creator_tools, agencies, other) |
pitch |
string | null |
(required) |
logo_url |
string | null |
(required) |
countries |
string[] |
(required) |
languages |
string[] |
(required) |
listed_since |
string | null |
(required) |
state |
string |
(required; one of unlisted, listed, held, delisted) |
missing |
object[] |
Every condition not met yet. Listing is refused while any is. (required) |
preview |
any |
(required) |
ListingUpdate
| Field | Type | |
|---|---|---|
listed |
boolean |
|
category |
DirectoryCategory |
(one of software, ai_tools, education, finance, health, ecommerce, creator_tools, agencies, other) |
pitch |
string | null |
Plain text: no HTML. (max 280) |
countries |
array | null |
Two-letter country codes the program welcomes partners from. (<= 60 items) |
languages |
array | null |
Two-letter language codes, like en or de. (<= 20 items) |
logo_path |
string | null |
The path from the logo upload, or null to remove it. (max 200) |
Milestone
A bonus's line: revenue in amount_minor (the program's currency), or sales or
customers in count, over the partner's lifetime or each calendar_month (UTC). Paid
once per window, at the sale that crosses it. While it is on hold, a refund or dispute
that takes the partner back under the line takes it back, and crossing again pays it
again; once approved, it stays.
| Field | Type | |
|---|---|---|
metric |
string |
(required; one of revenue, sales, customers) |
amount_minor |
integer |
(>= 1) |
count |
integer |
(>= 1) |
window |
string |
(required; one of lifetime, calendar_month) |
NecProvider
One payout provider, and whether what it paid counts toward your 1099-NEC, with why.
| Field | Type | |
|---|---|---|
provider |
PayoutProviderName |
How a payout is paid. manual: a file you pay from yourself. webhook: your own payout system. The rest: your own account at that provider. BoltClaw never holds the money. (required; one of manual, webhook, paypal, wise, stripe_connect, tremendous, trolley, tango) |
label |
string |
(required) |
included |
boolean |
Counted toward your 1099-NEC, as you set it. (required) |
default_included |
boolean |
(required) |
note |
string | null |
Your own note, where you changed the row. (required) |
reason |
string |
Why it is included or left out by default. (required) |
source |
object |
(required) |
NecReason
Why a person is on your 1099-NEC file or not. reason_text says it in a sentence.
One of: reportable, below_threshold, backup_withholding, not_us_person, corporation, no_form, nothing_reportable, threshold_unset.
NextCursor
Pass back as cursor for the next page; null on the last page.
string | null.
OfferAnswer
| Field | Type | |
|---|---|---|
offer |
any |
(required) |
eligible |
boolean |
(required) |
reason_code |
any |
(required) |
reason |
string | null |
One sentence to show at checkout instead of an error. (required) |
coupon |
string | null |
The friend's coupon in your Stripe, for discounts: [{ coupon }]. (required) |
PanelHistoryRow
| Field | Type | |
|---|---|---|
id |
string |
Stable for a friend, like f3. Not an id of ours or yours. (required) |
label |
string |
Friend 3: friends are numbered in the order they joined, never named. (required) |
stage |
HistoryStage |
Where the friend is: joined, paid, rewarded (the reward is paid out) or not_counted. (required; one of joined, paid, rewarded, not_counted) |
reason_code |
ReasonCode |
The stable code beside each history sentence. Translate by code; the English reason is a convenience. A code is never renamed or reused, and a new one only ever joins the end. awaiting_payment: the friend joined; nothing paid yet. on_hold_until: the friend paid; the reward waits out the program's hold, until hold_until, in case of a refund. reward_ready: the hold is over; it goes out in the next payout. reward_paid: it has been paid. under_review: the program is checking it first. refunded: the friend's payment was refunded, so no reward. no_reward: the program pays nothing for this event. referrer_not_approved: the referrer is not approved, so nothing is earned. (required; one of awaiting_payment, on_hold_until, reward_ready, reward_paid, under_review, refunded, no_reward, referrer_not_approved) |
reason |
string |
One English sentence saying why the row is where it is. Translate by reason_code. (required) |
occurred_at |
date-time |
(required) |
hold_until |
date-time | null |
When the reward is ready, for on_hold_until; otherwise null. (required) |
reward |
object | null |
The reward this friend has earned so far, net of refunds; null when there is none. (required) |
Partner
| Field | Type | |
|---|---|---|
id |
uuid |
The partner's membership in this program. (required) |
display_name |
string |
(required) |
status |
PartnerStatus |
(required; one of pending, approved, rejected, banned) |
country |
string | null |
(required) |
payout_method |
string | null |
The payout method type. Never the details. (required; one of paypal, wise, bank, other, null) |
share_email |
boolean |
(required) |
email |
string | null |
Only when the partner chose to share it. (required) |
terms_version_accepted |
integer | null |
(required) |
applied_at |
date-time |
(required) |
approved_at |
date-time | null |
(required) |
stats |
PartnerStats |
(required) |
group |
object | null |
The group or level the partner is in, or null. (required) |
group_pinned |
boolean |
True when the daily level job leaves this partner where you put them. (required) |
application |
PartnerApplication |
What the partner wrote when they applied, and what the checks saw. Counts only: never which other programs, which accounts, or an address. (required) |
disclosure_acknowledged_at |
date-time | null |
When they last acknowledged your disclosure reminder (shown when they copy a link, then monthly). |
muted_broadcasts |
boolean |
Whether they muted your broadcast emails (one click in any of them). They still see your messages on their program page. |
PartnerApplication
What the partner wrote when they applied, and what the checks saw. Counts only: never which other programs, which accounts, or an address.
| Field | Type | |
|---|---|---|
questions |
string[] |
The program's questions as the partner was asked them; empty when it asked none. (required; <= 3 items) |
answers |
string[] |
In the order of questions. (required; <= 3 items) |
website |
string | null |
(required) |
account_age_days |
integer | null |
Days since the partner's Bolt Claw account was made. (required) |
templated |
integer | null |
"Looks templated": near-identical applications to other programs this week, or null. (required) |
burst |
integer | null |
Accounts that applied from one network and device in 24 hours, or null when it was not a burst. (required) |
PartnerList
| Field | Type | |
|---|---|---|
data |
Partner[] |
(required) |
next_cursor |
NextCursor |
Pass back as cursor for the next page; null on the last page. (required) |
PartnerRef
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
display_name |
string |
(required) |
PartnerStats
| Field | Type | |
|---|---|---|
clicks |
integer |
(required) |
leads |
integer |
(required) |
sales |
integer |
(required) |
revenue_minor |
integer |
(required) |
commission_minor |
integer |
(required) |
conversion_rate |
number | null |
Sales per click, 0 to 1. Null with no clicks. (required) |
epc_minor |
number | null |
Earnings per click, in minor units. Null with no clicks. (required) |
PartnerStatus
One of: pending, approved, rejected, banned.
PartnerUpdate
| Field | Type | |
|---|---|---|
group_id |
uuid | null |
A group or level of the partner's program; null for none. |
pinned |
boolean |
True keeps the daily level job from moving the partner. |
Payout
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
batch_id |
uuid | null |
(required) |
status |
PayoutStatus |
unclaimed: the provider holds it until the promoter claims it (PayPal's sign-up link, a reward email that bounced). returned: it came back before it was paid, and its commissions are back in Ready to pay. (required; one of draft, processing, unclaimed, paid, failed, returned, cancelled) |
provider |
PayoutProviderName |
How a payout is paid. manual: a file you pay from yourself. webhook: your own payout system. The rest: your own account at that provider. BoltClaw never holds the money. (required; one of manual, webhook, paypal, wise, stripe_connect, tremendous, trolley, tango) |
currency |
string |
(required) |
amount_minor |
integer |
(required) |
short_reference |
string |
(required) |
reference |
string | null |
(required) |
paid_at |
date-time | null |
(required) |
created_at |
date-time |
(required) |
updated_at |
date-time |
(required) |
partner |
PartnerRef |
(required) |
country |
string | null |
(required) |
exports |
object | null |
Which export file an open payout would go in; null where it would, else why not. (required) |
delivery |
PayoutDelivery |
How a payout sent through a provider went; null until it was sent. (required) |
PayoutBatch
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
program_id |
uuid |
(required) |
label |
string |
(required) |
created_at |
date-time |
(required) |
sent_at |
date-time | null |
(required) |
totals |
CurrencyTotal[] |
What the batch held when it was made. (required) |
sendable |
object |
(required) |
lines |
object[] |
(required) |
waiting_for_funding |
object[] |
(required) |
auto_send |
object |
(required) |
PayoutBatchResult
| Field | Type | |
|---|---|---|
status |
string |
(required; one of created, empty) |
batch_id |
uuid | null |
(required) |
payouts |
integer |
(required) |
carried |
integer |
Promoters whose total was below the minimum or negative. (required) |
held |
integer |
Approved commissions a flag or a payout-details hold kept out. (required) |
PayoutCancel
| Field | Type | |
|---|---|---|
status |
string |
cancelled (the default), or failed when you tried to pay it and the money did not arrive. (one of cancelled, failed) |
PayoutDelivery
How a payout sent through a provider went; null until it was sent.
| Field | Type | |
|---|---|---|
sent_at |
date-time |
(required) |
provider_ref |
string | null |
The provider's own id for it: what to look for in your provider account. (required) |
awaiting_funding_at |
date-time | null |
Waiting for you to fund it at the provider (Wise, Trolley), since when. (required) |
unclaimed_at |
date-time | null |
(required) |
failure |
object | null |
(required) |
returned_after_paid_at |
date-time | null |
It came back AFTER it was paid. Nothing was changed; it is yours to decide. (required) |
PayoutDetails
| Field | Type | |
|---|---|---|
payout_id |
uuid |
(required) |
amount_minor |
integer |
(required) |
currency |
string |
(required) |
reference |
string |
(required) |
method |
string | null |
(required; one of paypal, wise, bank, other, null) |
details |
object | null |
The promoter's own payout details for this method (an email, or bank fields), in snake_case. (required) |
external_id |
string | null |
For one of your own users (a referrer), your own id for them. (required) |
PayoutHold
| Field | Type | |
|---|---|---|
flag |
Flag |
(required) |
already |
boolean |
True when the partner was already held. (required) |
PayoutList
| Field | Type | |
|---|---|---|
data |
Payout[] |
(required) |
next_cursor |
NextCursor |
Pass back as cursor for the next page; null on the last page. (required) |
PayoutMarkPaid
| Field | Type | |
|---|---|---|
reference |
string |
Your payment reference; trimmed. (required; min 1; max 200) |
expected_minor |
integer |
The amount you were shown. (>= 0) |
PayoutPreview
| Field | Type | |
|---|---|---|
groups |
object[] |
(required) |
promoters |
object[] |
(required) |
waiting |
integer |
(required) |
truncated |
boolean |
More than the 2,000 largest balances are owed. (required) |
PayoutProvider
A payout account a program pays through, by name and state. Never a credential.
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
provider |
string |
(required; one of webhook, paypal, wise, stripe_connect, tremendous, trolley, tango) |
name |
string |
(required) |
mode |
string |
A sandbox connection never pays a batch. (required; one of live, sandbox) |
status |
string |
(required; one of active, error) |
problem |
string | null |
Why it is not working (required) |
verified_at |
date-time | null |
(required) |
last_used_at |
date-time | null |
(required) |
label |
string | null |
The campaign, profile or reward it pays from. (required) |
needs |
string |
What a promoter must give before this provider can pay them. (required; one of email, email_or_iban, stripe_account, trolley_recipient, nothing) |
PayoutProviderList
| Field | Type | |
|---|---|---|
data |
PayoutProvider[] |
(required) |
PayoutProviderName
How a payout is paid. manual: a file you pay from yourself. webhook: your own payout
system. The rest: your own account at that provider. BoltClaw never holds the money.
One of: manual, webhook, paypal, wise, stripe_connect, tremendous, trolley, tango.
PayoutResult
| Field | Type | |
|---|---|---|
status |
string |
(required; one of paid, failed, returned) |
reference |
string |
Your own payment reference (paid only). (max 200) |
reason |
string |
Why it failed or came back (required unless paid). (one of invalid_destination, recipient_not_ready, insufficient_funds, returned, other) |
PayoutSend
| Field | Type | |
|---|---|---|
payout_ids |
uuid[] |
Send only these; every payout in sendable when absent. (<= 5000 items) |
expected_totals |
object |
What you expect to send, per currency, in minor units: {"USD": 12000}. Must equal sendable.totals. (required) |
confirm_large |
uuid[] |
(<= 5000 items) |
PayoutSendResult
| Field | Type | |
|---|---|---|
batch_id |
uuid |
(required) |
claimed |
integer |
(required) |
results |
object[] |
(required) |
PayoutSettings
| Field | Type | |
|---|---|---|
provider |
PayoutProviderName |
How a payout is paid. manual: a file you pay from yourself. webhook: your own payout system. The rest: your own account at that provider. BoltClaw never holds the money. (required; one of manual, webhook, paypal, wise, stripe_connect, tremendous, trolley, tango) |
providers |
PayoutProviderName[] |
The providers a promoter may choose among; the program's own provider is always one. (required) |
schedule |
string |
(required; one of manual, monthly) |
payout_day |
integer |
The day a monthly batch is made (UTC). (required; >= 1; <= 28) |
auto_send |
boolean |
(required) |
auto_send_available |
boolean |
False until BoltClaw allows sending on a schedule, after a legal review; until then a person sends every batch. (required) |
max_payout_minor |
integer | null |
A payout above this needs confirming by name. (required) |
available |
PayoutProviderName[] |
What this program can pay with now (a file, and every live connection that works). (required) |
PayoutSettingsUpdate
| Field | Type | |
|---|---|---|
provider |
PayoutProviderName |
How a payout is paid. manual: a file you pay from yourself. webhook: your own payout system. The rest: your own account at that provider. BoltClaw never holds the money. (one of manual, webhook, paypal, wise, stripe_connect, tremendous, trolley, tango) |
providers |
PayoutProviderName[] |
(<= 8 items) |
auto_send |
boolean |
|
payout_day |
integer |
(>= 1; <= 28) |
max_payout_minor |
integer | null |
(>= 1) |
PayoutStatus
unclaimed: the provider holds it until the promoter claims it (PayPal's sign-up link, a
reward email that bounced). returned: it came back before it was paid, and its commissions
are back in Ready to pay.
One of: draft, processing, unclaimed, paid, failed, returned, cancelled.
PrivacyRequest
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
program_id |
uuid |
(required) |
source |
string |
(required; one of shopify) |
kind |
string |
(required; one of data_request, customer_redact, shop_redact) |
shop |
string |
The shop's myshopify.com name. (required) |
status |
string |
(required; one of open, answered, done) |
result |
object | null |
What an erasure made anonymous, as counts. (required) |
received_at |
date-time |
(required) |
answered_at |
date-time | null |
(required) |
due_by |
date-time |
Thirty days after Shopify asked. (required) |
PrivacyRequestDetail
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
program_id |
uuid |
(required) |
source |
string |
(required; one of shopify) |
kind |
string |
(required; one of data_request, customer_redact, shop_redact) |
shop |
string |
The shop's myshopify.com name. (required) |
status |
string |
(required; one of open, answered, done) |
result |
object | null |
What an erasure made anonymous, as counts. (required) |
received_at |
date-time |
(required) |
answered_at |
date-time | null |
(required) |
due_by |
date-time |
Thirty days after Shopify asked. (required) |
data |
object | null |
For a data request still waiting, what Bolt Claw holds about the customer it named. (required) |
PrivacyRequestList
| Field | Type | |
|---|---|---|
data |
PrivacyRequest[] |
(required) |
next_cursor |
NextCursor |
Pass back as cursor for the next page; null on the last page. (required) |
Program
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
name |
string |
(required) |
product_url |
string |
(required) |
currency |
string |
(required) |
cookie_days |
integer |
(required) |
attribution_model |
string |
(required; one of last_click, first_click) |
hold_days |
integer |
(required) |
min_payout_minor |
integer |
(required) |
payout_schedule |
string |
(required; one of manual, monthly) |
join_mode |
string |
(required; one of apply, invite) |
auto_approve |
boolean |
(required) |
signup_url |
string |
The program's public join page. (required) |
terms |
string | null |
(required) |
terms_version |
integer |
(required) |
preview_title |
string | null |
(required) |
preview_description |
string | null |
(required) |
preview_image_url |
string | null |
(required) |
customer_discount |
string | null |
(required) |
discount_code_prefix |
string | null |
(required) |
status |
string |
(required; one of active, paused, archived) |
created_at |
date-time |
(required) |
updated_at |
date-time |
(required) |
pending_partners |
integer |
(required) |
integrations |
Integration[] |
(required) |
kind |
ProgramKind |
The template a program was made from. It sets the starting values and the words the screens use for promoters; it never switches a feature off. A waitlist program's promoters are the people on its waitlist. (required; one of affiliate, refer_a_friend, creator, ambassador, reseller, shop, waitlist) |
partner_monthly_cap_minor |
integer | null |
The most one partner earns from this program in a calendar month (UTC), bonuses included. Null for no ceiling. (required) |
embed |
EmbedSettings |
A program's in-app referral panel. Change it with embed_enabled, embed_origins, embed_config and approve_referrers on PATCH /referrals/programs/{id}. (required) |
welcome |
WelcomeSettings |
The friend's welcome, as you set it. (required) |
late_code_days |
integer |
How many days after sign-up a referral code may still be entered; 0 for never after. (required) |
lead_grant_daily_cap |
integer |
The most rewards a day (UTC) that signups alone may earn, across the program. (required) |
invite_after_purchase |
boolean |
Whether a friend's first payment gives them a referral link of their own. (required) |
ask_snooze_days |
integer |
How long "Not right now" in the panel lasts before a person is asked again. (required) |
application_questions |
string[] |
Up to three questions a partner answers when they apply, shown on the program's join page. (<= 3 items) |
risk_alert_email |
boolean |
Whether the program's managers are emailed when a promoter needs a look. Set with PATCH /referrals/programs/{id}/risk-policy. |
disclosure_text |
string |
The sentence partners add when they share a link or a code ("I earn a commission if you buy through my link."). Never empty. Partners are reminded of it when they copy a link, when they put one in a post, a bio page or an automation, after every library snippet, and in the in-app panel. |
leaderboard |
any |
The leaderboard promoters see; null when they see none (you always see yours). |
waitlist |
any |
A waitlist program's settings and its hosted page's address; null for any other program. |
second_tier |
any |
The second tier; null while it is off. Set with PATCH /referrals/programs/{id}/second-tier. |
terms_required_version |
integer | null |
The terms version every approved partner must accept before their next payout (a change to the second tier asks it). |
setup |
object |
The answers to "How do you take payments?" and "How do people sign up?", which choose the setup steps. |
ProgramCreate
| Field | Type | |
|---|---|---|
name |
string |
(required; max 200) |
product_url |
string |
An https URL. (required; max 4096) |
currency |
string |
(max 8) |
cookie_days |
integer |
1 to 365. |
attribution_model |
string |
last_click or first_click. (max 20) |
hold_days |
integer |
|
min_payout_minor |
integer |
|
payout_schedule |
string |
manual or monthly. (max 20) |
join_mode |
string |
apply or invite. (max 20) |
auto_approve |
boolean |
|
terms |
string | null |
(max 20000) |
preview_title |
string | null |
(max 400) |
preview_description |
string | null |
(max 1000) |
customer_discount |
string | null |
(max 1000) |
discount_code_prefix |
string | null |
(max 40) |
partner_monthly_cap_minor |
integer | null |
The most one partner earns in a calendar month (UTC), in minor units; null for no ceiling. |
embed_enabled |
boolean |
Turns the in-app referral panel on or off. Off, no session can be minted and the panel does not load. |
embed_origins |
string[] |
The sites allowed to frame the panel, like https://app.example.com. Replaces the list. https only (http://localhost:3000 for development), no wildcard, no path, at most 10. (<= 10 items) |
embed_config |
EmbedConfig |
The panel look, copy and sharing, replacing the old settings whole. Unknown keys are 400. |
approve_referrers |
boolean |
True (default): a new referrer is approved at once. False: they are pending until you approve them. |
welcome_config |
any |
The welcome's look and words, replacing the old settings whole; null for the defaults. Unknown keys are 400. |
welcome_show_name |
boolean |
Show a referrer's name to the friend (default true). An affiliate's is shown only when they agreed. |
welcome_page |
boolean |
A one-screen welcome at the link before your product (default false). |
allowed_hosts |
string[] |
Extra hosts your website tag may read the offer from, like www.example.com, beside the product URL's own and the panel's framing origins. Replaces the list. Host names only, at most 10. An AI assistant's key cannot change it. (<= 10 items) |
late_code_days |
integer |
How many days after sign-up a referral code may still be entered (default 14; 0 for never after). (>= 0; <= 365) |
lead_grant_daily_cap |
integer |
The most rewards a day (UTC) signups alone may earn across the program (default 100). (>= 0; <= 100000) |
invite_after_purchase |
boolean |
Give a friend who makes their first payment a referral link of their own (default false). |
ask_snooze_days |
integer |
How long "Not right now" in the panel lasts (default 30). (>= 1; <= 365) |
setup_payments |
SetupPayments |
(one of stripe, paddle, polar, whop, revenuecat, woocommerce, fastspring, lemonsqueezy, shopify, chargebee, recurly, gumroad, own_system, not_sure, null) |
setup_signups |
SetupSignups |
(one of own_backend, supabase, firebase, clerk, authjs, nocode, checkout_only, null) |
audience_eu_uk |
boolean |
Your audience includes the EU or the UK (default false). The setup status then asks for the tag's consent mode. |
application_questions |
string[] |
Up to three questions a partner answers when they apply (each 1 to 200 characters, no hidden characters). Answers are shown on their application; an empty list asks nothing. (<= 3 items) |
disclosure_text |
string |
The sentence partners add when they share (plain text, 10 to 280 characters). It may be edited, never emptied: partners must tell people they earn from the link (FTC, ASA). A new program starts with one for its currency's region. (min 10; max 280) |
leaderboard |
LeaderboardFields |
The leaderboard promoters see, merged onto the current setting; null switches it off. You always see your own. |
waitlist |
WaitlistFields |
A waitlist program's settings, merged onto the current ones. |
kind |
string |
Start from a template: affiliate, refer_a_friend, creator, ambassador, reseller, shop or waitlist. The program gets that kind's settings and reward rules (and levels, for reseller; the waitlist itself, for waitlist), with anything you send yourself taking precedence, in one step. Left out, the program starts with no rules, as before. (max 40) |
ProgramKind
The template a program was made from. It sets the starting values and the words the
screens use for promoters; it never switches a feature off. A waitlist program's
promoters are the people on its waitlist.
One of: affiliate, refer_a_friend, creator, ambassador, reseller, shop, waitlist.
ProgramList
| Field | Type | |
|---|---|---|
data |
Program[] |
(required) |
next_cursor |
NextCursor |
Pass back as cursor for the next page; null on the last page. (required) |
ProgramStats
| Field | Type | |
|---|---|---|
currency |
string |
(required) |
from |
date-time |
(required) |
to |
date-time |
(required) |
bucket |
string | null |
(required; one of day, week, month, null) |
totals |
PartnerStats |
(required) |
untracked_clicks |
integer |
(required) |
untracked_by_month |
object[] |
(required) |
series |
array | null |
(required) |
partners |
PartnerStats[] |
(required) |
ProgramUpdate
Every field optional; a field left out keeps its value.
| Field | Type | |
|---|---|---|
name |
string |
(max 200) |
product_url |
string |
An https URL. (max 4096) |
currency |
string |
(max 8) |
cookie_days |
integer |
1 to 365. |
attribution_model |
string |
last_click or first_click. (max 20) |
hold_days |
integer |
|
min_payout_minor |
integer |
|
payout_schedule |
string |
manual or monthly. (max 20) |
join_mode |
string |
apply or invite. (max 20) |
auto_approve |
boolean |
|
terms |
string | null |
(max 20000) |
preview_title |
string | null |
(max 400) |
preview_description |
string | null |
(max 1000) |
customer_discount |
string | null |
(max 1000) |
discount_code_prefix |
string | null |
(max 40) |
partner_monthly_cap_minor |
integer | null |
The most one partner earns in a calendar month (UTC), in minor units; null for no ceiling. |
embed_enabled |
boolean |
Turns the in-app referral panel on or off. Off, no session can be minted and the panel does not load. |
embed_origins |
string[] |
The sites allowed to frame the panel, like https://app.example.com. Replaces the list. https only (http://localhost:3000 for development), no wildcard, no path, at most 10. (<= 10 items) |
embed_config |
EmbedConfig |
The panel look, copy and sharing, replacing the old settings whole. Unknown keys are 400. |
approve_referrers |
boolean |
True (default): a new referrer is approved at once. False: they are pending until you approve them. |
welcome_config |
any |
The welcome's look and words, replacing the old settings whole; null for the defaults. Unknown keys are 400. |
welcome_show_name |
boolean |
Show a referrer's name to the friend (default true). An affiliate's is shown only when they agreed. |
welcome_page |
boolean |
A one-screen welcome at the link before your product (default false). |
allowed_hosts |
string[] |
Extra hosts your website tag may read the offer from, like www.example.com, beside the product URL's own and the panel's framing origins. Replaces the list. Host names only, at most 10. An AI assistant's key cannot change it. (<= 10 items) |
late_code_days |
integer |
How many days after sign-up a referral code may still be entered (default 14; 0 for never after). (>= 0; <= 365) |
lead_grant_daily_cap |
integer |
The most rewards a day (UTC) signups alone may earn across the program (default 100). (>= 0; <= 100000) |
invite_after_purchase |
boolean |
Give a friend who makes their first payment a referral link of their own (default false). |
ask_snooze_days |
integer |
How long "Not right now" in the panel lasts (default 30). (>= 1; <= 365) |
setup_payments |
SetupPayments |
(one of stripe, paddle, polar, whop, revenuecat, woocommerce, fastspring, lemonsqueezy, shopify, chargebee, recurly, gumroad, own_system, not_sure, null) |
setup_signups |
SetupSignups |
(one of own_backend, supabase, firebase, clerk, authjs, nocode, checkout_only, null) |
audience_eu_uk |
boolean |
Your audience includes the EU or the UK (default false). The setup status then asks for the tag's consent mode. |
application_questions |
string[] |
Up to three questions a partner answers when they apply (each 1 to 200 characters, no hidden characters). Answers are shown on their application; an empty list asks nothing. (<= 3 items) |
disclosure_text |
string |
The sentence partners add when they share (plain text, 10 to 280 characters). It may be edited, never emptied: partners must tell people they earn from the link (FTC, ASA). A new program starts with one for its currency's region. (min 10; max 280) |
leaderboard |
LeaderboardFields |
The leaderboard promoters see, merged onto the current setting; null switches it off. You always see your own. |
waitlist |
WaitlistFields |
A waitlist program's settings, merged onto the current ones. |
ReasonBody
| Field | Type | |
|---|---|---|
reason |
string |
(required; min 3; max 500) |
ReasonCode
The stable code beside each history sentence. Translate by code; the English reason is a
convenience. A code is never renamed or reused, and a new one only ever joins the end.
awaiting_payment: the friend joined; nothing paid yet. on_hold_until: the friend paid;
the reward waits out the program's hold, until hold_until, in case of a refund.
reward_ready: the hold is over; it goes out in the next payout. reward_paid: it has
been paid. under_review: the program is checking it first. refunded: the friend's
payment was refunded, so no reward. no_reward: the program pays nothing for this event.
referrer_not_approved: the referrer is not approved, so nothing is earned.
One of: awaiting_payment, on_hold_until, reward_ready, reward_paid, under_review, refunded, no_reward, referrer_not_approved.
ReferralClickCreate
| Field | Type | |
|---|---|---|
code |
string |
The partner link's code (?via=). (required; min 1; max 64) |
ip |
string |
The visitor's IP address (max 64) |
user_agent |
string |
Classed as mobile (max 1024) |
referrer |
string |
The referring page. Only its host is kept. (max 2048) |
country |
string |
An ISO 3166 two-letter country code, if your edge sets one. (max 8) |
landing_path |
string |
The path the visitor landed on. Not stored. (max 2048) |
ReferralClickRecorded
| Field | Type | |
|---|---|---|
recorded |
boolean |
(required) |
reason |
string | null |
(required; one of bot, allowance, null) |
program_id |
uuid |
(required) |
ReferralDeleted
| Field | Type | |
|---|---|---|
deleted |
true |
(required) |
ReferralLink
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
code |
string |
(required) |
url |
string |
https://boltclaw.ai/r/<code>, or on your own link domain. (required) |
kind |
string |
(required; one of primary, placement) |
placement |
string | null |
Where BoltClaw placed it, by type only (never the partner's own note). (required; one of bio, post, automation, manual, null) |
archived_at |
date-time | null |
(required) |
created_at |
date-time |
(required) |
partner |
object | null |
(required) |
ReferralLinkList
| Field | Type | |
|---|---|---|
data |
ReferralLink[] |
(required) |
next_cursor |
NextCursor |
Pass back as cursor for the next page; null on the last page. (required) |
ReferrerList
| Field | Type | |
|---|---|---|
data |
ReferrerView[] |
(required) |
next_cursor |
NextCursor |
Pass back as cursor for the next page; null on the last page. (required) |
ReferrerPanelView
Everything the panel draws for one referrer, as data: the same View behind the hosted panel,
the browser client and GET /referrals/programs/{id}/referrers/{external_id}, so your own
screen can show exactly what the panel shows. Keys are snake_case; values are plain text.
| Field | Type | |
|---|---|---|
mode |
string |
preview is sample data. (required; one of panel, preview) |
generated_at |
date-time |
(required) |
referrer |
object |
(required) |
program |
object |
(required) |
notice |
object | null |
A line above everything when something stops all rewards (a paused program, an unapproved referrer); otherwise null. (required) |
offer |
object |
(required) |
stats |
object |
(required) |
history |
PanelHistoryRow[] |
One row per friend, newest first, at most 50. (required; <= 50 items) |
payout |
object |
(required) |
share |
object |
(required) |
look |
object |
(required) |
copy |
EmbedCopy |
Words you replace in the panel. Plain text only, never HTML. Any key left out keeps the panel's own English. Each value is 1 to the number of characters shown beside it. (required) |
grants |
object[] |
The referrer's own rewards that are not cash ("1 month free"), newest first, at most 50, each with where it stands in a sentence. Ids are this View's own (g1). (required; <= 50 items) |
ask |
object |
Whether to ask the referrer to share now, or they said "Not right now" until snoozed_until. (required) |
thank_you |
object |
The words of the compact thank-you card, for a panel opened with mode="thank-you". (required) |
ReferrerStatus
approved earns rewards. pending is waiting for you (when the program does not approve
new referrers at once, embed.approve_referrers); it can share its link and earns nothing
yet. banned earns nothing; an erased referrer is also banned.
One of: approved, pending, banned.
ReferrerStatusUpdate
| Field | Type | |
|---|---|---|
status |
string |
(required; one of approved, banned) |
ReferrerUpsert
| Field | Type | |
|---|---|---|
external_id |
string |
Your own id for the user. Printable characters only, no space at either end. (required; min 1; max 200) |
name |
string | null |
A first name or display name for the panel's greeting, plain text. null clears it. (max 60) |
email |
string | null |
Only used to recognise the same person again (a fingerprint is kept, never the address). Never returned. (max 400) |
code |
string | null |
The vanity code for their link: 3 to 40 lowercase letters and digits, single hyphens between words. Only when the referrer is created. (max 40) |
ReferrerView
One of your own users who holds a referral link, as you see them. Never an email, a fingerprint or a Bolt Claw account: a referrer has none.
| Field | Type | |
|---|---|---|
id |
uuid |
The referrer's membership in this program. Use it with PATCH and DELETE /referrals/referrers/{id}. (required) |
program_id |
uuid |
(required) |
external_id |
string |
Your own id for the user. (required; min 1; max 200) |
name |
string | null |
The first name or display name you sent. Null once erased. (required; max 60) |
code |
string | null |
The code in the referrer's link. (required) |
link_url |
string | null |
The link the referrer shares. (required) |
status |
ReferrerStatus |
approved earns rewards. pending is waiting for you (when the program does not approve new referrers at once, embed.approve_referrers); it can share its link and earns nothing yet. banned earns nothing; an erased referrer is also banned. (required; one of approved, pending, banned) |
erased |
boolean |
True for an erased referrer: a row that keeps what it earned, with no name. (required) |
created_at |
date-time |
(required) |
clicks |
integer |
Clicks on the link (required) |
friends |
integer |
Friends who signed up through the link. (required) |
friends_paid |
integer |
Friends who have paid. (required) |
earned_minor |
integer |
Rewards earned (required) |
currency |
string |
The program's currency. (required) |
ReportLink
| Field | Type | |
|---|---|---|
type |
string |
(required; one of commissions, conversions, partners, payouts, grants, contests) |
url |
string |
Open it once, within ten minutes, to download the CSV. Only its fingerprint is kept. (required) |
expires_at |
date-time |
(required) |
filename |
string |
What the file is called when it downloads, like acme-partners-commissions-2026-10-27.csv. (required) |
Reward
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
event |
string |
(required; one of lead, sale, bonus) |
kind |
string |
(required; one of percent, flat) |
percent_bps |
integer | null |
Basis points: 2000 is 20%. (required) |
amount_minor |
integer | null |
Minor units of the program's currency. (required) |
duration |
string |
(required; one of once, months, lifetime) |
months |
integer | null |
(required) |
updated_at |
date-time |
(required) |
scope |
string |
Who it pays. The most specific scope with a matching rule wins. (required; one of program, group, partner) |
group_id |
uuid | null |
(required) |
partner_id |
uuid | null |
(required) |
label |
string | null |
Your own words for when it applies. (required) |
conditions |
RewardConditions |
When a rule applies. Every key you send must match; a rule with none is its audience's default for its event. A sale without items matches no rule that names products, prices, plans or intervals. A signup takes none; a bonus takes exactly milestone. (required) |
cap_minor |
integer | null |
The most it pays for one customer (required) |
priority |
integer |
Breaks a tie between two equally specific rules; higher wins. (required) |
created_at |
date-time |
(required) |
summary |
string |
The rule in one sentence. (required) |
reward_type |
RewardType |
What a rule gives. cash is a commission, paid out; the rest are grants, given in your own system or through your Stripe. A friend (referee) gets a discount, credit, a free period, a perk or a gift card, never cash; a discount is only ever the friend's, at a sale, once per customer. A bonus is cash. (one of cash, credit, free_period, discount, perk, gift_card) |
beneficiary |
string |
Who it rewards. referee is the friend who arrived through the link. (one of promoter, referee) |
period_count |
integer | null |
A free period's length. |
period_unit |
string | null |
(one of day, month, null) |
perk_text |
string | null |
A perk |
discount_duration |
string | null |
How long a friend's discount lasts. (one of once, months, forever, null) |
discount_months |
integer | null |
|
stripe_coupon_id |
string | null |
The coupon Bolt Claw made for this rule in your own Stripe, when it made one. |
RewardConditions
When a rule applies. Every key you send must match; a rule with none is its audience's
default for its event. A sale without items matches no rule that names products,
prices, plans or intervals. A signup takes none; a bonus takes exactly milestone.
| Field | Type | |
|---|---|---|
products |
string[] |
(<= 100 items) |
prices |
string[] |
(<= 100 items) |
plans |
string[] |
Matched without regard to case. (<= 100 items) |
intervals |
string[] |
(<= 3 items) |
purchase |
string |
The customer's first payment or a renewal. any is the same as leaving it out. (one of first, renewal, any) |
currencies |
string[] |
(<= 100 items) |
min_amount_minor |
integer |
Only sales of at least this. (>= 0) |
milestone |
Milestone |
A bonus's line: revenue in amount_minor (the program's currency), or sales or customers in count, over the partner's lifetime or each calendar_month (UTC). Paid once per window, at the sale that crosses it. While it is on hold, a refund or dispute that takes the partner back under the line takes it back, and crossing again pays it again; once approved, it stays. |
RewardCreate
kind (percent or flat) is required for cash, credit, a gift card and a discount; a
free period and a perk have no amount and take none.
| Field | Type | |
|---|---|---|
event |
string |
(required; one of lead, sale, bonus) |
scope |
string |
program (everyone) unless you say otherwise. (one of program, group, partner) |
group_id |
uuid |
With scope group. |
partner_id |
uuid |
With scope partner: one partner's own rate. |
kind |
string |
(one of percent, flat) |
percent_bps |
integer | null |
1 to 10000, for percent. |
amount_minor |
integer | null |
For flat; a bonus's must be above 0. |
duration |
string |
Ignored for a bonus (one of once, months, lifetime) |
months |
integer | null |
1 to 120, for months. |
cap_minor |
integer | null |
The most it pays for one customer; null for no ceiling. |
conditions |
RewardConditions |
When a rule applies. Every key you send must match; a rule with none is its audience's default for its event. A sale without items matches no rule that names products, prices, plans or intervals. A signup takes none; a bonus takes exactly milestone. |
priority |
integer |
-1000 to 1000; 0 by default. |
label |
string | null |
At most 60 characters after trimming. (max 200) |
reward_type |
RewardType |
cash when left out. Fixed once the rule exists. |
beneficiary |
string |
promoter when left out. Fixed once the rule exists. (one of promoter, referee) |
period_count |
integer | null |
For free_period: 1 to 365 days, or 1 to 36 months. |
period_unit |
string | null |
For free_period. (one of day, month, null) |
perk_text |
string | null |
For perk: at most 120 characters after trimming. (max 400) |
discount_duration |
string | null |
For discount: how long it lasts. (one of once, months, forever, null) |
discount_months |
integer | null |
For a discount of months: 1 to 36. |
RewardList
| Field | Type | |
|---|---|---|
data |
Reward[] |
Every rule of the program, at most 200. (required; <= 201 items) |
RewardType
What a rule gives. cash is a commission, paid out; the rest are grants, given in your own
system or through your Stripe. A friend (referee) gets a discount, credit, a free period,
a perk or a gift card, never cash; a discount is only ever the friend's, at a sale, once
per customer. A bonus is cash.
One of: cash, credit, free_period, discount, perk, gift_card.
RewardUpdate
Every field optional. A rule's event and audience are fixed once it exists, and so are what
it gives and to whom (reward_type, beneficiary): sending either is 400.
| Field | Type | |
|---|---|---|
kind |
string |
(one of percent, flat) |
percent_bps |
integer | null |
|
amount_minor |
integer | null |
|
duration |
string |
(one of once, months, lifetime) |
months |
integer | null |
|
cap_minor |
integer | null |
|
conditions |
RewardConditions |
When a rule applies. Every key you send must match; a rule with none is its audience's default for its event. A sale without items matches no rule that names products, prices, plans or intervals. A signup takes none; a bonus takes exactly milestone. |
priority |
integer |
|
label |
string | null |
(max 200) |
period_count |
integer | null |
|
period_unit |
string | null |
(one of day, month, null) |
perk_text |
string | null |
(max 400) |
discount_duration |
string | null |
(one of once, months, forever, null) |
discount_months |
integer | null |
RiskPolicy
| Field | Type | |
|---|---|---|
hold_at |
integer |
From this score, a conversion's earnings wait extra_hold_days longer. (required) |
review_at |
integer |
From this score a person is asked (risk_review). (required) |
extra_hold_days |
integer |
(required) |
min_seconds_lead |
integer |
Fewer seconds from click to signup than this look automated. (required) |
min_seconds_sale |
integer |
(required) |
min_seconds_shop |
integer |
The same for a shop order. (required) |
allow_paid_search |
boolean |
False when partners may not bid on your brand in paid search. (required) |
campaign_allowlist |
string[] |
Campaign ids that are allowed when paid search is not. (required) |
hold_new_partners |
boolean |
A promoter's first days in the program (90 by default) count toward the score, and their earnings wait a little longer. On unless you switch it off. (required) |
RiskPolicyFields
| Field | Type | |
|---|---|---|
hold_at |
integer |
(>= 1; <= 100) |
review_at |
integer |
Never below hold_at. (>= 1; <= 100) |
extra_hold_days |
integer |
(>= 0; <= 365) |
min_seconds_lead |
integer |
(>= 0; <= 3600) |
min_seconds_sale |
integer |
(>= 0; <= 3600) |
min_seconds_shop |
integer |
(>= 0; <= 3600) |
allow_paid_search |
boolean |
|
campaign_allowlist |
string[] |
(<= 50 items) |
hold_new_partners |
boolean |
RiskPolicyUpdate
| Field | Type | |
|---|---|---|
policy |
any |
The fields to change: only those you send change, and one sent as null goes back to the platform's value. policy: null clears every field the program set. |
alert_email |
boolean |
RiskPolicyView
| Field | Type | |
|---|---|---|
program_id |
uuid |
(required) |
policy |
RiskPolicy |
(required) |
custom |
RiskPolicyFields |
(required) |
alert_email |
boolean |
Email the program's managers when a promoter needs a look (at most once per promoter a day). (required) |
SaleItem
| Field | Type | |
|---|---|---|
product |
string |
Your product id, or Stripe's prod_…. (max 200) |
price |
string |
Your price or plan id, or Stripe's price_…. (max 200) |
plan |
string |
A plan name in your own words, matched without regard to case. (max 200) |
interval |
string |
(one of month, year, one_time) |
quantity |
integer |
(>= 1) |
amount |
integer |
This line's share of the sale, minor units. (required; >= 0) |
SecondTierSettings
| Field | Type | |
|---|---|---|
program_id |
uuid |
(required) |
second_tier |
any |
Null while it is off. (required) |
rule |
string | null |
The rule in one sentence (required) |
terms_version |
integer |
(required) |
terms_required_version |
integer | null |
The terms version partners must accept before their next payout. (required) |
clause |
string |
The paragraph the terms carry while it is on. (required) |
fence |
string |
Why it is limited the way it is. (required) |
SecondTierUpdate
| Field | Type | |
|---|---|---|
enabled |
boolean |
(required) |
percent_bps |
integer |
(>= 1; <= 2000) |
months |
integer | null |
(>= 1; <= 24) |
SetupPart
| Field | Type | |
|---|---|---|
part |
string |
(required; one of tag, billing, signups, sales, codes, panel, welcome) |
label |
string |
(required) |
state |
SetupState |
(required; one of connected, warnings, not_connected) |
skipped |
boolean |
The program's own setup does not use this part; it does not count toward overall. (required) |
summary |
string |
One sentence about what was seen (required) |
fix |
string | null |
What to do next; null when nothing is needed. (required) |
warnings |
object[] |
(required) |
SetupPayments
One of: stripe, paddle, polar, whop, revenuecat, woocommerce, fastspring, lemonsqueezy, shopify, chargebee, recurly, gumroad, own_system, not_sure, null.
SetupSignups
One of: own_backend, supabase, firebase, clerk, authjs, nocode, checkout_only, null.
SetupState
One of: connected, warnings, not_connected.
ShareChannel
A share button. native is the device's own share sheet.
One of: native, whatsapp, x, linkedin, facebook, messenger, telegram, sms, email.
ShopifyCodesResult
| Field | Type | |
|---|---|---|
shop |
string | null |
The linked shop's myshopify.com name. (required) |
created |
integer |
Discounts made in the shop now. (required) |
matched |
integer |
Codes the shop already had with the same text (required) |
deactivated |
integer |
Codes switched off in Bolt Claw (required) |
failed |
integer |
Codes the shop refused or did not answer for. They still work as codes Bolt Claw knows. (required) |
remaining |
integer |
Codes waiting for the next call. (required) |
reason |
string | null |
Why nothing could be synced: no linked shop, no friend's discount rule, or the shop must open the app again. (required; one of not_linked, no_friend_discount, reconnect, null) |
StripeCodesResult
| Field | Type | |
|---|---|---|
coupon |
string |
The coupon behind every code (required) |
created |
integer |
Promotion codes made in Stripe now. (required) |
linked |
integer |
Codes Stripe already had with the same text (required) |
failed |
integer |
Codes Stripe refused. They still work as codes Bolt Claw knows. (required) |
TagCheck
| Field | Type | |
|---|---|---|
result |
string |
(required; one of found_v2, found_v1, not_found, refused, unreachable) |
version |
string | null |
(required; one of v1, v2, null) |
consent |
boolean | null |
Whether the tag waits for consent (data-consent="required"). (required) |
payment_links |
string | null |
Stripe Payment Links or a Buy Button on the page: none, ready (the version 2 tag has data-payment-links="auto", which is the whole integration for them) or missing (there are some and the tag does not reach them; the sentence says what to add). Null when the page was not read. (required; one of none, ready, missing, null) |
sentence |
string |
A fixed sentence to show. (required) |
checked_at |
date-time |
(required) |
TaxFormStatus
| Field | Type | |
|---|---|---|
partner_id |
uuid |
(required) |
display_name |
string |
(required) |
origin |
string |
managed: one of your own users (a referrer). (required; one of account, managed) |
form |
TaxFormSummary |
A promoter's latest form for your business, by type and status; null when they have none. Never its contents. (required) |
requirement_met |
boolean |
False while their payouts wait for a form. (required) |
waiting_since |
date-time | null |
Since when their approved money has waited for a form; null when nothing waits. (required) |
forfeitable_minor |
integer |
The approved money waiting for a form, in minor units, which a person may forfeit. (required) |
TaxFormStatusList
| Field | Type | |
|---|---|---|
program_id |
uuid |
(required) |
tax_forms |
TaxRequirement |
Whether a program's payouts wait for a tax form. off: never. us_only: a US person needs a W-9, and anyone else signs a short statement that they are not a US person. all: everyone needs a W-9, W-8BEN or W-8BEN-E. (required; one of off, us_only, all) |
data |
TaxFormStatus[] |
(required) |
next_cursor |
NextCursor |
Pass back as cursor for the next page; null on the last page. (required) |
disclaimer |
string |
(required) |
TaxFormSummary
A promoter's latest form for your business, by type and status; null when they have none. Never its contents.
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
type |
TaxFormType |
w9: Form W-9, from a US person. w8ben: Form W-8BEN, from a person outside the US. w8bene: Form W-8BEN-E, from an organisation outside the US. foreign_statement: a short statement that they are not a US person, which a program set to us_only accepts instead of a W-8. (required; one of w9, w8ben, w8bene, foreign_statement) |
name |
string |
The form's name, such as Form W-9. (required) |
revision |
string |
The IRS revision it was built from. (required) |
status |
string |
superseded: replaced after a change of name, tax number or address, and not signed again yet. expired: a W-8 past its last day. invalid: it no longer matches what was signed; ask them to sign again. (required; one of signed, superseded, expired, invalid) |
signed_at |
date-time |
(required) |
valid_until |
date-time | null |
A W-8's last day (31 December of the third year after it was signed). Null for a W-9 or the statement. (required) |
TaxFormType
w9: Form W-9, from a US person. w8ben: Form W-8BEN, from a person outside the US.
w8bene: Form W-8BEN-E, from an organisation outside the US. foreign_statement: a short
statement that they are not a US person, which a program set to us_only accepts instead
of a W-8.
One of: w9, w8ben, w8bene, foreign_statement.
TaxProfileStatus
Whether your own tax details are complete. Never the details themselves: your EIN, legal name and addresses are entered and read in the dashboard only.
| Field | Type | |
|---|---|---|
country |
string | null |
Your business's country, two letters. (required) |
domestic_vat_bps |
integer | null |
Your own VAT rate in basis points (2000 is 20%), used for a promoter in your own country. (required) |
payer_complete |
boolean |
Your 1099 payer details (legal name, EIN, US address, telephone) are entered. The 1099 files need them. (required) |
invoice_complete |
boolean |
Your invoicing details (legal name, address, country, VAT number) are entered. Self-billing needs them. (required) |
updated_at |
date-time | null |
(required) |
TaxRequirement
Whether a program's payouts wait for a tax form. off: never. us_only: a US person needs
a W-9, and anyone else signs a short statement that they are not a US person. all:
everyone needs a W-9, W-8BEN or W-8BEN-E.
One of: off, us_only, all.
TaxSettings
| Field | Type | |
|---|---|---|
program_id |
uuid |
(required) |
tax_forms |
TaxRequirement |
Whether a program's payouts wait for a tax form. off: never. us_only: a US person needs a W-9, and anyone else signs a short statement that they are not a US person. all: everyone needs a W-9, W-8BEN or W-8BEN-E. (required; one of off, us_only, all) |
invoice_required |
boolean |
Each payout waits for the promoter's own invoice. A promoter who self-bills never waits for one. (required) |
self_billing |
boolean |
You issue self-billed invoices for the promoters who accept your self-billing agreement. (required) |
profile |
TaxProfileStatus |
Whether your own tax details are complete. Never the details themselves: your EIN, legal name and addresses are entered and read in the dashboard only. (required) |
providers |
NecProvider[] |
Which payout providers count toward your 1099-NEC. (required) |
sources_note |
string |
(required) |
disclaimer |
string |
BoltClaw prepares these forms; it does not give tax advice or file anything for you. (required) |
TaxSettingsUpdate
Send only the fields you want to change. An empty object is 400.
| Field | Type | |
|---|---|---|
tax_forms |
TaxRequirement |
Whether a program's payouts wait for a tax form. off: never. us_only: a US person needs a W-9, and anyone else signs a short statement that they are not a US person. all: everyone needs a W-9, W-8BEN or W-8BEN-E. (one of off, us_only, all) |
invoice_required |
boolean |
|
self_billing |
boolean |
Turning it on needs your invoicing details first (409 until then). |
TaxYear
| Field | Type | |
|---|---|---|
year |
integer |
(required) |
threshold_minor |
integer | null |
The year's 1099-NEC threshold in US cents (200000 for 2026). Null until it is set for the year, and until then no IRS file can be made for it. (required) |
payer_complete |
boolean |
Your 1099 payer details are entered. The files need them. (required) |
recipients |
TaxYearRecipient[] |
(required) |
counts |
object |
(required) |
providers |
NecProvider[] |
(required) |
sources_note |
string |
(required) |
disclaimer |
string |
(required) |
TaxYearRecipient
One person your programs paid in the year (every membership of one account together), and whether they belong on your 1099-NEC. No tax number and no legal name.
| Field | Type | |
|---|---|---|
reference |
string |
Their reference in the files (P- and ten characters). Never an account id. (required) |
partner_ids |
uuid[] |
Their memberships in your programs. (required) |
display_name |
string |
(required) |
origin |
string |
(required; one of account, managed) |
form |
TaxFormSummary |
A promoter's latest form for your business, by type and status; null when they have none. Never its contents. (required) |
included |
boolean |
On your 1099-NEC file. (required) |
listed_for_backup_withholding |
boolean |
No W-9 or no tax number, and something reportable was paid: listed for your accountant with a backup withholding mark (24%). BoltClaw does not withhold. (required) |
reason |
NecReason |
Why a person is on your 1099-NEC file or not. reason_text says it in a sentence. (required; one of reportable, below_threshold, backup_withholding, not_us_person, corporation, no_form, nothing_reportable, threshold_unset) |
reason_text |
string |
(required) |
warnings |
object[] |
(required) |
reportable_minor |
integer |
US cents paid through the providers you report. (required) |
excluded_minor |
integer |
US cents paid through the providers you leave out, such as PayPal. (required) |
other_currencies |
CurrencyTotal[] |
Paid in other currencies through the providers you report. Never counted; listed for you to convert. (required) |
by_provider |
object[] |
How they were paid in the year. (required) |
e_delivery |
boolean |
They agreed to receive their Copy B electronically. Otherwise, send them paper. (required) |
TermsDraft
| Field | Type | |
|---|---|---|
terms |
string |
The assembled terms. Edit them as you wish before saving. (required) |
settings |
object |
What was read from the program's own settings, which the terms describe. (required) |
clauses |
string[] |
The template's clauses the draft holds (required) |
notice |
string |
Based on Bolt Claw's template. Not legal advice; have it checked for the countries your partners live in. (required) |
TermsDraftRequest
| Field | Type | |
|---|---|---|
answers |
object |
(required) |
TestDataCleared
| Field | Type | |
|---|---|---|
conversions |
integer |
(required) |
commissions |
integer |
(required) |
grants |
integer |
(required) |
customers |
integer |
(required) |
clicks |
integer |
(required) |
partners |
integer |
(required) |
TestSale
| Field | Type | |
|---|---|---|
run_id |
string |
(required) |
steps |
TestSaleStep[] |
(required) |
TestSaleStep
| Field | Type | |
|---|---|---|
step |
string |
(required; one of partner, click, lead, sale, refund) |
run_id |
string |
(required) |
summary |
string |
(required) |
partner |
object | null |
(required) |
conversion |
any |
(required) |
offer |
any |
(required) |
webhooks |
object |
(required) |
test |
boolean |
(required; one of true) |
WaitlistEnded
| Field | Type | |
|---|---|---|
forgotten |
integer |
How many entries' addresses and links were deleted by this call. (required) |
ended_at |
date-time | null |
When the waitlist ended (the first end (required) |
WaitlistEntry
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
external_id |
string |
Your own id for the person; the hosted page makes one, wl_…. (required) |
name |
string | null |
(required) |
code |
string | null |
(required) |
link_url |
string | null |
Their link to invite friends. (required) |
source |
string |
(required; one of api, hosted) |
status |
WaitlistEntryStatus |
(required; one of waiting, invited, joined) |
position |
integer | null |
Their place in line; null until they confirm. (required) |
friends |
integer |
Confirmed friends who count. (required) |
verified |
boolean |
(required) |
disqualified |
boolean |
(required) |
disqualify_reason |
string | null |
(required) |
flags |
string[] |
velocity: one of a flood from one address; self: the referrer themselves. Either moves nobody. (required) |
joined_at |
date-time |
(required) |
invited_at |
date-time | null |
(required) |
referred_by |
object | null |
(required) |
WaitlistEntryDetail
| Field | Type | |
|---|---|---|
id |
uuid |
(required) |
external_id |
string |
Your own id for the person; the hosted page makes one, wl_…. (required) |
name |
string | null |
(required) |
code |
string | null |
(required) |
link_url |
string | null |
Their link to invite friends. (required) |
source |
string |
(required; one of api, hosted) |
status |
WaitlistEntryStatus |
(required; one of waiting, invited, joined) |
position |
integer | null |
Their place in line; null until they confirm. (required) |
friends |
integer |
Confirmed friends who count. (required) |
verified |
boolean |
(required) |
disqualified |
boolean |
(required) |
disqualify_reason |
string | null |
(required) |
flags |
string[] |
velocity: one of a flood from one address; self: the referrer themselves. Either moves nobody. (required) |
joined_at |
date-time |
(required) |
invited_at |
date-time | null |
(required) |
referred_by |
object | null |
(required) |
state |
any |
(required) |
WaitlistEntryList
| Field | Type | |
|---|---|---|
data |
WaitlistEntry[] |
(required) |
next_cursor |
NextCursor |
Pass back as cursor for the next page; null on the last page. (required) |
total |
integer |
Entries matching the filter. (required) |
line |
integer |
Confirmed entries (required) |
WaitlistEntryStatus
One of: waiting, invited, joined.
WaitlistEntryUpdate
| Field | Type | |
|---|---|---|
verified |
boolean |
|
status |
WaitlistEntryStatus |
(one of waiting, invited, joined) |
WaitlistEntryUpsert
| Field | Type | |
|---|---|---|
external_id |
string |
(required; min 1; max 200) |
name |
string | null |
(max 200) |
email |
string |
Kept only as a keyed fingerprint for the self-referral check. (max 320) |
code |
string |
A vanity code for their own link, on creation only. (max 40) |
referral_code |
string |
The code of the link they arrived with. (max 40) |
verified |
boolean |
|
status |
WaitlistEntryStatus |
(one of waiting, invited, joined) |
WaitlistFields
A waitlist program's settings, merged onto the current ones.
| Field | Type | |
|---|---|---|
bump |
integer |
Places a confirmed friend moves their referrer up (default 5). (>= 1; <= 1000) |
page |
boolean |
Switch the hosted page at /r/w/<token> on (default false). |
pitch |
string | null |
(max 500) |
closed |
boolean |
WaitlistInvite
| Field | Type | |
|---|---|---|
entry_ids |
uuid[] |
(required; <= 500 items; >= 1 items) |
subject |
string |
One line. (required; min 1; max 140) |
message |
string |
The broadcasts' Markdown subset: paragraphs, bold, italic, lists and https links. (required; max 6000) |
WaitlistInvited
| Field | Type | |
|---|---|---|
invited |
integer |
(required) |
emailed |
integer |
Entries from the hosted page (required) |
entry_ids |
uuid[] |
(required) |
WaitlistPageAddress
| Field | Type | |
|---|---|---|
page_url |
string |
The hosted page's new address. It is unguessable and noindex. (required) |
WelcomeConfig
The welcome's look and words. Text is shown as plain text, never markup: angle brackets, controls and invisible marks are refused. The call to action needs both its label and its address.
| Field | Type | |
|---|---|---|
style |
string |
(one of bar, corner, sheet) |
position |
string |
(one of top, bottom) |
accent |
string |
A colour like #1a2b3c. |
radius |
integer |
(>= 0; <= 24) |
theme |
string |
(one of light, dark, auto) |
headline |
string |
Your headline. {name} is the inviter (or "A friend"), {offer} the gift. (max 120) |
conditions |
string |
The one line of conditions. (max 160) |
cta_label |
string |
(max 40) |
cta_url |
string |
An https address. (max 2048) |
WelcomeSettings
The friend's welcome, as you set it.
| Field | Type | |
|---|---|---|
config |
WelcomeConfig |
The welcome's look and words. Text is shown as plain text, never markup: angle brackets, controls and invisible marks are refused. The call to action needs both its label and its address. (required) |
show_name |
boolean |
Whether a referrer's name (or an affiliate's, when they agreed) is shown to the friend. (required) |
page |
boolean |
Whether a person opening the link first sees a one-screen welcome before your product. (required) |
allowed_hosts |
string[] |
Hosts your website tag may read the offer from, beside the product URL's own. (required) |