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:

  • -L follows the redirect instead of just showing it — useful to confirm the destination page actually loads, but combine it with -i if you still want to see the 302 and its headers along the way.
  • This isn't idempotent. Every request logs a new Click and mints a fresh aff_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 paidreadyForPayment=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 pendingapprovedpaid, 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 paid until payoutHoldDays have 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 PATCH or DELETE on a paid conversion returns 409:

    { "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.