API reference
Everything you need to integrate affiliately.io into your own product.
Building an integration with an LLM or coding agent? Point it at the raw markdown source:
/api.md.
API
A RESTful API for managing your affiliate program: associates, referral links, clicks, and conversions.
Authentication
Endpoints under /api/* are authenticated with HTTP Basic Auth, using your account's API key as the username and API secret as the password:
Authorization: Basic base64(API_KEY:API_SECRET)
With curl, pass -u API_KEY:API_SECRET and it will encode this for you. Your API key and secret are shown once when the account is created (or regenerated from the dashboard's API Credentials page).
Requests without valid credentials return 401:
{ "error": "Invalid or missing credentials" }
Create an associate
Registers a new associate (affiliate) under your account.
curl -u API_KEY:API_SECRET \
-X POST https://affiliately.io/api/associates \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Doe",
"email": "jane@example.com"
}'
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | |
email |
string | yes | |
status |
string | no | active (default) or inactive |
Response — 201 Created:
{
"id": 16,
"accountId": 23,
"name": "Jane Doe",
"email": "jane@example.com",
"status": "active",
"createdAt": "2026-07-07T20:07:16.754Z",
"updatedAt": "2026-07-07T20:07:16.754Z"
}
Create a link
Generates a trackable referral link for one of your associates. code is what shows up in the actual URL a customer clicks (https://affiliately.io/r/CODE) — omit it to get a random one, or supply your own vanity code.
curl -u API_KEY:API_SECRET \
-X POST https://affiliately.io/api/links \
-H "Content-Type: application/json" \
-d '{
"associateId": 17,
"code": "SUMMER10",
"discountPct": 20
}'
| Field | Type | Required | Notes |
|---|---|---|---|
associateId |
integer | yes | Must belong to your account |
code |
string | no | Alphanumeric, 3–32 chars. Auto-generated if omitted. Must be unique |
discountPct |
integer | no | 0–100, default 0. Share of the flat commission passed on as a customer discount instead of associate payout |
destinationUrl |
string | no | Overrides the account's default destination URL for this link |
Response — 201 Created:
{
"id": 16,
"associateId": 17,
"accountId": 24,
"code": "SUMMER10",
"discountPct": 20,
"createdAt": "2026-07-07T20:10:41.800Z",
"updatedAt": "2026-07-07T20:10:41.800Z"
}
If associateId doesn't belong to your account (or doesn't exist), you get 400:
{ "error": "associateId does not reference an existing associate" }
Test a referral link
GET /r/:code is the public, unauthenticated endpoint a customer's browser actually hits when they click a referral link. It logs a click, sets a cookie, and redirects to the destination URL. You can inspect all of this with curl.
By default curl does not follow redirects, so -i (include headers) is enough to see the raw 302 response — the Set-Cookie header and the Location it would have sent a browser to:
curl -i https://affiliately.io/r/SUMMER10
HTTP/1.1 302 Found
Set-Cookie: aff_token=7403952b-4b3e-40e5-aac0-11084b5cfcf1; Max-Age=7776000; Path=/; Expires=...; HttpOnly; SameSite=Lax
Location: https://your-destination.example.com/?aff_token=7403952b-4b3e-40e5-aac0-11084b5cfcf1
...
To capture the cookie into a file (and reuse it in later requests, e.g. simulating a customer's browser before your backend reports a conversion), use -c:
curl -c cookies.txt -o /dev/null -w "status: %{http_code}\nredirected to: %{redirect_url}\n" \
https://affiliately.io/r/SUMMER10
Two things worth knowing:
-Lfollows the redirect instead of just showing it — useful to confirm the destination page actually loads, but combine it with-iif you still want to see the 302 and its headers along the way.- This isn't idempotent. Every request logs a new
Clickand mints a freshaff_token, exactly like a real visitor clicking the link — so repeated test runs create real tracking data, not just a preview.
Clicks
GET /r/:code (above) is how a real visitor's browser generates a click. The /api/clicks endpoints are for managing that data directly — POST is a raw, unauthenticated way to log a click without an actual browser redirect (e.g. from a mobile app or server-side integration); GET/DELETE are authenticated and scoped to your own account's links.
Create a click — public, no auth required:
curl -X POST https://affiliately.io/api/clicks \
-H "Content-Type: application/json" \
-d '{ "linkId": 17 }'
| Field | Type | Required | Notes |
|---|---|---|---|
linkId |
integer | yes | |
cookieToken |
string | no | UUID. Auto-generated if omitted |
ipAddress |
string | no | Defaults to the requester's IP |
userAgent |
string | no | Defaults to the request's User-Agent header |
Response — 201 Created:
{
"id": 42,
"linkId": 17,
"cookieToken": "600aaeb9-6115-4fbc-93d2-3ec216ec4a53",
"ipAddress": "203.0.113.10",
"userAgent": "curl/8.7.1",
"createdAt": "2026-07-07T20:19:50.935Z"
}
List clicks — requires auth, filter by linkId and/or cookieToken:
curl -u API_KEY:API_SECRET "https://affiliately.io/api/clicks?linkId=17"
[
{
"id": 42,
"linkId": 17,
"cookieToken": "600aaeb9-6115-4fbc-93d2-3ec216ec4a53",
"ipAddress": "203.0.113.10",
"userAgent": "curl/8.7.1",
"createdAt": "2026-07-07T20:19:50.000Z",
"deletedAt": null
}
]
Get one click — requires auth:
curl -u API_KEY:API_SECRET https://affiliately.io/api/clicks/42
Delete a click — requires auth. Clicks use soft deletes, so this hides the row from the API rather than erasing it:
curl -u API_KEY:API_SECRET -X DELETE https://affiliately.io/api/clicks/42
Response — 204 No Content.
Conversions
Reported by your own backend when an order completes, using the aff_token cookie value your frontend picked up from the redirect (see Test a referral link). The API looks up the matching click, attributes it to the right associate/link, and computes the payout split — you never send amounts yourself.
Report a conversion — requires auth:
curl -u API_KEY:API_SECRET \
-X POST https://affiliately.io/api/conversions \
-H "Content-Type: application/json" \
-d '{
"cookieToken": "9d2a19ed-54d7-486d-ad19-7561eaff4e02",
"orderReference": "ORDER-1042"
}'
| Field | Type | Required | Notes |
|---|---|---|---|
cookieToken |
string | yes | The aff_token value from the referral redirect |
orderReference |
string | yes | Your own order/invoice id. Must be unique per account |
Response — 201 Created. commissionAmount is your account's flat fee; customerDiscount and associatePayout are split according to the link's discountPct:
{
"id": 38,
"accountId": 26,
"linkId": 18,
"cookieToken": "9d2a19ed-54d7-486d-ad19-7561eaff4e02",
"orderReference": "ORDER-1042",
"commissionAmount": 100,
"associatePayout": 80,
"customerDiscount": 20,
"status": "pending",
"createdAt": "2026-07-07T20:24:10.745Z",
"updatedAt": "2026-07-07T20:24:10.745Z"
}
Error responses:
| Status | Cause |
|---|---|
400 |
No click found for that cookieToken |
409 |
orderReference has already been reported for this account |
410 |
The click's attribution window has expired (past the account's cookieDurationDays) |
List / get conversions — requires auth, list filters by linkId, status, and/or orderReference:
curl -u API_KEY:API_SECRET "https://affiliately.io/api/conversions?status=pending"
curl -u API_KEY:API_SECRET "https://affiliately.io/api/conversions?orderReference=ORDER-1042"
curl -u API_KEY:API_SECRET https://affiliately.io/api/conversions/38
orderReference is how you go from your own order id to the conversion's internal id — useful before a PATCH (e.g. canceling one during the hold period), since that endpoint only accepts the id.
Find conversions ready to be paid — readyForPayment=true returns pending/approved conversions whose payoutHoldDays window has already elapsed (i.e. exactly the set you could successfully PATCH to paid right now, without doing that date math yourself):
curl -u API_KEY:API_SECRET "https://affiliately.io/api/conversions?readyForPayment=true"
This ignores status if both are passed together — readyForPayment already implies pending or approved.
Update status — requires auth. This is how you move a conversion through pending → approved → paid, or mark it rejected:
curl -u API_KEY:API_SECRET \
-X PATCH https://affiliately.io/api/conversions/38 \
-H "Content-Type: application/json" \
-d '{ "status": "approved" }'
approved is purely informational — nothing in the API requires it. There's no enforced state machine beyond the two rules below, so a conversion can go straight from pending to paid once the hold period ends without ever passing through approved. In practice, you'd set approved whenever your own backend considers the sale verified (e.g. payment/fraud checks clear) — independent of the cancellation window — to distinguish "reviewed and legitimate, just waiting out the hold period" from "still unreviewed."
Two rules are enforced server-side, not left to the caller:
Payout hold period. You can't mark a conversion
paiduntilpayoutHoldDayshave passed since it was created — this is your account's cancellation/returns window:{ "error": "This conversion cannot be marked paid until its 14-day hold period ends (eligible at 2026-07-21T20:24:10.000Z)" }Once paid, it's final. Any further
PATCHorDELETEon apaidconversion returns409:{ "error": "This conversion has already been paid and cannot be modified" }
Delete a conversion — requires auth, soft delete, blocked once paid (see above):
curl -u API_KEY:API_SECRET -X DELETE https://affiliately.io/api/conversions/38
Cancel a conversion during the hold period
If a customer cancels before a conversion has been paid, look it up by your own order id, then mark it rejected:
curl -u API_KEY:API_SECRET "https://affiliately.io/api/conversions?orderReference=ORDER-1042"
curl -u API_KEY:API_SECRET \
-X PATCH https://affiliately.io/api/conversions/38 \
-H "Content-Type: application/json" \
-d '{ "status": "rejected" }'
This works at any point before the conversion is paid — unlike paid, rejected isn't gated by payoutHoldDays, so it doesn't matter whether the cancellation happens on day 1 or day 13 of the window.