AffiliateRail docs
API

API overview

One consistent REST contract over every object in your program, with a generated, interactive reference.

The AffiliateRail API is a REST API at https://api.affiliaterail.com: predictable resource URLs, JSON in and out, and one consistent contract over every object in your program, from partners and links to commissions, payouts and webhook endpoints. Whatever the dashboard shows you, the API can read, and the daily work of running a program (adding partners, recording sales, approving commissions) can be written.

The interactive reference is generated from the OpenAPI document the API itself publishes, so it cannot drift from the running product. The same contract is published for machines at /v1/openapi.json and, as plain text for models, at /llms.txt and /llms-full.txt.

The contract in one screen

ConcernRule
AuthAuthorization: Bearer rail_live_…. Keys are minted under Settings, API keys and carry scopes. rail_test_… keys work against the same organization and stamp X-Rail-Livemode: false.
Lists{ "data": [...], "has_more": true, "total_count": 42 }, flat, every time. limit 1 to 100 (default 25); starting_after and ending_before take an object id. program_id is required on every list except /v1/programs.
Expandexpand[]=partner&expand[]=sale (or expand=partner,sale) inlines related objects. The same relation name means the same object everywhere; an unknown value is a 400 naming the allowed ones. include[]=stats adds counters where they exist.
MoneyInteger minor units in *_minor fields, always beside a currency. Rates are basis points. Never a float, never a string.
TimeISO 8601, UTC.
IdsPrefixed: prg_, part_, cus_, sale_, com_, pyt_, flw_, whe_. Sample rows are sample_….
IdempotencyAn Idempotency-Key header on any POST replays the first response for 24 hours; the same key with a different request is a 422. See rate limits and errors.
Rate limit25 requests per second per key, with X-RateLimit-* headers on every response.
Errors{ "error": { "type", "code", "message", "param" } }. See the error catalogue.
VersioningEvery response carries X-Rail-Version. Additive changes never bump it.
TenancyAn id from another organization is a 404, never a 403, so ids cannot be probed.

Clicks and referrals are readable on purpose: a partner who can reconstruct visit, lead and conversion can audit their own tracking. Tax forms expose status only; the form itself is the partner's tax identity and never travels over an API key.

A first call

List your partners, expanding their group:

curl "https://api.affiliaterail.com/v1/partners?program_id=prg_...&limit=5&expand[]=group" \
  -H "Authorization: Bearer rail_live_..."
const res = await fetch(
  "https://api.affiliaterail.com/v1/partners?program_id=prg_...&limit=5&expand[]=group",
  { headers: { Authorization: `Bearer ${process.env.RAIL_API_KEY}` } },
);
const { data, has_more, total_count } = await res.json();
import os, requests

res = requests.get(
    "https://api.affiliaterail.com/v1/partners",
    params={"program_id": "prg_...", "limit": 5, "expand[]": "group"},
    headers={"Authorization": f"Bearer {os.environ['RAIL_API_KEY']}"},
)
body = res.json()  # {"data": [...], "has_more": bool, "total_count": int}

Creating a partner

Writes take JSON bodies, honour Idempotency-Key, and return the created object:

curl https://api.affiliaterail.com/v1/partners \
  -H "Authorization: Bearer rail_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: signup-8412" \
  -d '{"program_id": "prg_...", "email": "alice@example.com", "status": "active"}'
const res = await fetch("https://api.affiliaterail.com/v1/partners", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RAIL_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "signup-8412",
  },
  body: JSON.stringify({ program_id: "prg_...", email: "alice@example.com", status: "active" }),
});
const partner = await res.json(); // { id: "part_...", ... }
res = requests.post(
    "https://api.affiliaterail.com/v1/partners",
    headers={
        "Authorization": f"Bearer {os.environ['RAIL_API_KEY']}",
        "Idempotency-Key": "signup-8412",
    },
    json={"program_id": "prg_...", "email": "alice@example.com", "status": "active"},
)
partner = res.json()  # {"id": "part_...", ...}

Going deeper