Developers
API reference
A JSON REST API over HTTPS at https://kickrail.com/api/v1. Reporting and looking up signups works on every plan; everything else needs Growth or Scale.
Authentication#
Send your program's server key as a bearer token. Find it in Settings → Server API key. Keys start with sk_kr_, belong to one program, and must stay on your server.
curl
curl https://kickrail.com/api/v1/affiliates \ -H "Authorization: Bearer sk_kr_..."
There is one key per program. Rolling it in Settings replaces it at once, and the old key stops working immediately. Running several programs? Each has its own key, and a key only sees its own program.
Requests and responses#
- Send bodies as JSON with
Content-Type: application/json. - Times are ISO 8601 in UTC. Money is an integer in the currency's smallest unit (
1500USD is $15.00) with a lowercasecurrency. - Ids carry a prefix:
aff_,ref_,com_,po_,cmp_.
Pagination
List endpoints return the newest first:
{ "object": "list", "data": [ ... ], "has_more": true }To get the next page, pass the last id you received as starting_after, and keep going while has_more is true.
Node
async function* all(path) {
let after;
while (true) {
const url = new URL("https://kickrail.com/api/v1" + path);
url.searchParams.set("limit", "100");
if (after) url.searchParams.set("starting_after", after);
const res = await fetch(url, { headers: { Authorization: "Bearer " + process.env.KICKRAIL_SECRET_KEY } });
const page = await res.json();
yield* page.data;
if (!page.has_more) return;
after = page.data.at(-1).id;
}
}
for await (const c of all("/commissions?status=pending")) console.log(c.id, c.amount_cents);Errors
Errors use standard HTTP status codes with a JSON body: {"detail": "no such affiliate"}. For 422 from validation, detail is a list of the fields that failed.
| Status | Meaning | |
|---|---|---|
| 401 | bad key | Missing, malformed, or rolled server key. |
| 402 | plan | This endpoint needs a higher plan. The message names it. |
| 404 | not found | No object with that id in this program. |
| 409 | conflict | A token or code is already taken. |
| 422 | invalid | A field failed validation. |
Referrals#
/v1/referralsAll plansTell Kickrail who signed up and what referred them. Full guide: Report signups from your server.
| Field | Type | Description |
|---|---|---|
| referral | string | The kr_ref cookie or kickrail_referral form value. |
| via | string | An affiliate link token. |
| code | string | A creator code typed at signup. |
| string | The user's email. | |
| user_id | string | Your id for the user, up to 200 characters. |
| stripe_customer_id | string | cus_... |
| ip | string | Signup IP, for the self-referral check. Stored hashed. |
At least one of email, user_id, stripe_customer_id is required. Returns {"referred": false} or {"referred": true, "created": bool, "referral": {...}}.
/v1/referrals/lookupAll plansQuery with one of email, user_id or stripe_customer_id. Returns {"referred": bool, "referral": {...} | null}.
/v1/referralsGrowth+| Query | Type | Description |
|---|---|---|
| limit | 1 to 100 | How many to return. Default 25. |
| starting_after | id | Return the page after this object. Pass the last id of the previous page. |
| affiliate_id | id | Only this affiliate's referrals. |
| status | lead | customer | Lead: signed up. Customer: has paid. |
/v1/referrals/{id}Growth+The referral object
| Field | Type | Description |
|---|---|---|
| id | string | ref_... |
| affiliate_id | string | Who referred them. |
| campaign_id | string | The campaign it earns under. |
| source | link | code | Whether a link or a creator code made the referral. |
| status | lead | customer | Becomes customer on the first payment. |
| email, user_id, stripe_customer_id | string | null | What Kickrail knows about the person. |
| flagged | boolean | A self-referral check matched and hasn't been cleared. |
| rejected | boolean | You rejected the referral; it earns nothing. |
| first_paid_at | time | null | Their first payment. |
| created_at | time |
Affiliates#
/v1/affiliatesGrowth+| Query | Type | Description |
|---|---|---|
| limit | 1 to 100 | How many to return. Default 25. |
| starting_after | id | Return the page after this object. Pass the last id of the previous page. |
| status | pending | approved | rejected | |
| string | Find one affiliate by email. | |
| campaign_id | id |
/v1/affiliates/{id}Growth+/v1/affiliatesGrowth+Add an affiliate from your own app, for example when a customer opts into your partner program. People without a Kickrail login get one, and the welcome email carries a link to set their password. If they are already an affiliate of this program, that affiliate is returned with created: false.
| Field | Type | Description |
|---|---|---|
| string, required | ||
| name | string, required | 1 to 100 characters. |
| campaign_id | id | Defaults to your default campaign. |
| status | pending | approved | Default approved. |
| token | string | Their link token (?via= value): 3 to 32 of a-z, 0-9, -, _. Generated from the name if left out. |
| code | string | A creator code: 3 to 20 letters and digits. Needs Starter or higher. |
| rate_override | rule | An object: {"type": "percent", "percent": 40}, {"type": "flat", "flat_cents": 1500, "currency": "usd"} or {"type": "none"}. Replaces the campaign rate for this affiliate. |
| metadata | object | Up to 20 string keys and values, for your own ids. |
| send_welcome_email | boolean | Default true. |
curl
curl https://kickrail.com/api/v1/affiliates \
-H "Authorization: Bearer $KICKRAIL_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","name":"Jane Doe","code":"JANE20","metadata":{"crm_id":"42"}}'/v1/affiliates/{id}Growth+Send only the fields you want to change. The response is the updated affiliate, plus a warning string when Stripe refused to create a promotion code for a new code.
| Field | Type | Description |
|---|---|---|
| status | pending | approved | rejected | Approving sends the approval email. |
| campaign_id | id | Move them to another campaign. |
| token | string | Change their main link token. |
| code | string | Change their main creator code. |
| rate_override | rule | An object: {"type": "percent", "percent": 40}, {"type": "flat", "flat_cents": 1500, "currency": "usd"} or {"type": "none"}. Replaces the campaign rate for this affiliate. |
| clear_rate_override | boolean | Send true to go back to the campaign rate. |
| metadata | object | Replaces the stored metadata. |
The affiliate object
JSON
{
"id": "aff_2Lp...",
"object": "affiliate",
"status": "approved",
"email": "[email protected]",
"name": "Jane Doe",
"campaign_id": "cmp_...",
"token": "jane",
"link": "https://yourapp.com/?via=jane",
"code": "JANE20",
"code_redeemable_at_checkout": true,
"links": [
{ "id": "lnk_...", "token": "jane-yt", "url": "https://yourapp.com/pricing?via=jane-yt",
"landing_url": "https://yourapp.com/pricing", "label": "YouTube", "created_at": "..." }
],
"codes": [{ "id": "cod_...", "code": "JANEPOD", "redeemable_at_checkout": true, "created_at": "..." }],
"rate_override": null,
"metadata": { "crm_id": "42" },
"created_at": "2026-10-17T15:04:05Z"
}rate_override comes back in stored form: {"type": "percent", "bps": 4000} (basis points, so 4000 is 40%) or {"type": "flat", "cents": 1500, "currency": "usd"}.
Commissions#
/v1/commissionsGrowth+| Query | Type | Description |
|---|---|---|
| limit | 1 to 100 | How many to return. Default 25. |
| starting_after | id | Return the page after this object. Pass the last id of the previous page. |
| affiliate_id | id | |
| referral_id | id | |
| status | pending | paid | void |
/v1/commissions/{id}Growth+The commission object
| Field | Type | Description |
|---|---|---|
| id | string | com_... |
| kind | lead | sale | clawback | A lead bounty, a commission on a payment, or a negative amount after a refund of a paid commission. |
| affiliate_id, referral_id, campaign_id | string | |
| amount_cents | integer | What the affiliate earns. Negative for a clawback. Shrinks after a partial refund. |
| currency | string | |
| sale_amount_cents | integer | The pre-tax payment it was worked out from. |
| status | pending | paid | void | |
| held_for_review | boolean | Waiting on a self-referral review. |
| source | string | Where it came from: inv:in_... (an invoice), cs:cs_... (a one-off checkout), lead:ref_... (a lead bounty) or clawback:.... |
| due_at | time | When the hold ends and it becomes payable. |
| paid_at, payout_id | time, string | null | Set once it is paid. |
| void_reason | string | null | Why it was voided, for example refunded. |
| created_at | time |
Payouts#
/v1/payoutsGrowth+| Query | Type | Description |
|---|---|---|
| limit | 1 to 100 | How many to return. Default 25. |
| starting_after | id | Return the page after this object. Pass the last id of the previous page. |
/v1/payouts/{id}Growth+/v1/payoutsGrowth+Record that you paid affiliates yourself, in cash or in kind (a discount, account credit). It works like a manual payout in the dashboard: their payable commissions become paid, they get the payout email if it's on, and payout.paid goes out. Commissions still in their hold aren't included. Returns the payout object, or 409 if nothing is payable.
| Field | Type | Description |
|---|---|---|
| affiliate_ids | array, required | 1 to 100 affiliate ids, for example the one from a payout.due event. |
| currency | string | Needed only when those affiliates have payable balances in more than one currency. |
| reference | string | Up to 200 characters, such as a transfer or coupon id. Shown to you and the affiliate. |
The payout object
| Field | Type | Description |
|---|---|---|
| id | string | po_... |
| method | manual | paypal | stripe | |
| status | string | awaiting_funding until a Stripe batch is paid for, processing while PayPal or Stripe settles, then paid, partial or failed. A manual payout you undo becomes undone. |
| currency, total_cents, fee_cents | ||
| reference | string | The reference you typed, or the provider's batch id. |
| items | array | One per affiliate: affiliate_id, amount_cents, commission_ids, status, error. |
| paid_at, created_at | time |
Campaigns#
/v1/campaignsGrowth+All campaigns, oldest first, in one list (no pagination). Each has id, name, rule, duration, hold_days, min_payout_cents, lead_bounty, product_rules, stripe_coupon_id, is_default, public, slug, join_url (null for a private campaign), landing_url, and a readable description such as "30% of every payment for 12 months". Create and edit campaigns in the dashboard.
Portal domains#
Serve the join page and affiliate portal from your own subdomain, as in Settings → Portal domains. Adding domains also needs room on your plan (Starter 1, Growth 3, Scale 10).
/v1/domainsGrowth+Every domain on the program, in one list.
/v1/domainsGrowth+Body: {"host": "partners.yourapp.com"}. Returns the domain with the dns_record to create, and checks it straight away. The first domain becomes primary.
/v1/domains/{host}/verifyGrowth+Checks DNS and HTTPS again, like the Check DNS button. Call it after adding the CNAME; the first check after DNS is verified also requests the certificate.
/v1/domains/{host}/primaryGrowth+Makes it the domain used in affiliate links and emails. Returns the full list.
/v1/domains/{host}Growth+Removes it. If it was primary, the next verified domain takes over.
The domain object
| Field | Type | Description |
|---|---|---|
| host | string | partners.yourapp.com |
| status | string | not_verified until the CNAME is found, waiting_for_https while the certificate is issued, then live. |
| primary | boolean | Used in affiliate links and emails. |
| detail | string | Why it isn't live yet, when it isn't. |
| dns_record | object | {"type": "CNAME", "name": host, "value": "portals.kickrail.com"} |
| join_url | string | The join page on this domain. |
| added_at, verified_at, checked_at | time |
Webhooks
data uses the same objects as this API.