HeldSway

Affiliate Management API

Manage affiliates, referral links, and commission configuration for your business programmatically.


Prerequisites

All endpoints in this document require a Bearer access token obtained via [POST /v1/auth/token](/docs/api/authentication).

Your API key must be granted the appropriate scopes:

Scope Grants Access To
affiliates:read List and retrieve affiliates, links, commission data
affiliates:write Create, update, delete affiliates and links; manage commission
settings:read Retrieve business settings, branding, and commission tiers
settings:write Update business settings, branding, and commission tiers
Authorization: Bearer <access_token>

The referral link endpoint reference has moved to the dedicated Referral Links page. Use that page for list, create, bulk create, batch status, update, delete, and stats.

Commission Configuration

}’


#### Accepted Response — 202 Accepted

The request was accepted and queued for processing.

```json
{
  "success": true,
  "message": "Bulk creation queued.",
  "data": {
    "batch_id": 17,
    "status": "pending",
    "total_items": 3,
    "status_url": "/v1/affiliates/42/link-batches/17"
  }
}

Error Responses

Status Code Description
404 NOT_FOUND Affiliate not found for this business
422 VALIDATION_ERROR Empty array, missing destination_url, duplicate slugs within batch

The referral link endpoint reference has moved to the dedicated Referral Links page. Use that page for list, create, bulk create, batch status, update, delete, and stats.

Commission Configuration


Get Affiliate Commission

Retrieve the commission rate configuration for a specific affiliate, including the effective resolved rate.

GET /v1/affiliates/{id}/commission

Required scope: affiliates:read Rate limit: 120 requests/minute

Path Parameters

Parameter Type Description
id integer Affiliate ID

Example Request

curl "https://<API_DOMAIN>/v1/affiliates/42/commission" \
  -H "Authorization: Bearer <access_token>"

Success Response — 200 OK

{
  "success": true,
  "message": "OK",
  "data": {
    "affiliate_rate": 10.0,
    "business_default_rate": 8.0,
    "effective_rate": 10.0
  }
}
Field Type Description
affiliate_rate float|null This affiliate’s individual override rate. null if none is set
business_default_rate float|null The business-wide default commission rate
effective_rate float|null The rate that will actually be used (affiliate_rate if set, otherwise business_default_rate)

Error Responses

Status Code Description
404 NOT_FOUND Affiliate not found

Set Affiliate Commission

Set or clear the individual commission rate override for an affiliate.

PUT /v1/affiliates/{id}/commission

Required scope: affiliates:write Rate limit: 60 requests/minute

Path Parameters

Parameter Type Description
id integer Affiliate ID

Request Body

Field Type Required Description
commission_rate float|null Yes Commission percentage (0–100). Send null to remove the override and revert to the business default

Example Request — Set override

curl -X PUT "https://<API_DOMAIN>/v1/affiliates/42/commission" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "commission_rate": 20.0 }'

Example Request — Clear override

curl -X PUT "https://<API_DOMAIN>/v1/affiliates/42/commission" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "commission_rate": null }'

Success Response — 200 OK

{
  "success": true,
  "message": "Commission rate updated.",
  "data": {
    "affiliate_rate": 20.0,
    "business_default_rate": 8.0,
    "effective_rate": 20.0
  }
}

Error Responses

Status Code Description
404 NOT_FOUND Affiliate not found
422 VALIDATION_ERROR commission_rate missing from request body or out of range

List all commission rules attached to a referral link. Rules are returned ordered by priority (highest first), then by creation date.

GET /v1/links/{id}/commission-rules

Required scope: affiliates:read Rate limit: 120 requests/minute

Path Parameters

Parameter Type Description
id integer Referral link ID

Example Request

curl "https://<API_DOMAIN>/v1/links/10/commission-rules" \
  -H "Authorization: Bearer <access_token>"

Success Response — 200 OK

{
  "success": true,
  "message": "OK",
  "data": [
    {
      "id": 5,
      "label": "Black Friday Deal",
      "commission_rate": 20.0,
      "priority": 10,
      "valid_from": "2026-11-28T00:00:00.000000Z",
      "valid_until": "2026-11-30T23:59:59.000000Z",
      "max_conversions": 100,
      "conversion_count": 45,
      "is_active": true,
      "status": "active",
      "created_at": "2026-10-01T09:00:00.000000Z"
    },
    {
      "id": 3,
      "label": "Always-on bonus",
      "commission_rate": 12.0,
      "priority": 0,
      "valid_from": null,
      "valid_until": null,
      "max_conversions": null,
      "conversion_count": 312,
      "is_active": true,
      "status": "active",
      "created_at": "2026-03-01T09:00:00.000000Z"
    }
  ]
}

Error Responses

Status Code Description
404 NOT_FOUND Referral link not found

Create Commission Rule

Attach a new commission rule to a referral link. Rules allow you to define time-limited promotions, conversion caps, or priority overrides on a per-link basis.

POST /v1/links/{id}/commission-rules

Required scope: affiliates:write Rate limit: 60 requests/minute

Path Parameters

Parameter Type Description
id integer Referral link ID

Request Body

Field Type Required Constraints Description
commission_rate float Yes 0–100 Commission percentage this rule applies
label string No max 255 Human-readable name (e.g. "Black Friday")
priority integer No min 0, default 0 Higher value = takes precedence over lower-priority rules
valid_from date/datetime No Rule activates at this time. Omit for no start restriction
valid_until date/datetime No must be ≥ valid_from Rule expires at this time. Omit for no expiry
max_conversions integer No min 1 Cap on how many conversions this rule applies to. Omit for unlimited
is_active boolean No default true Whether the rule is enabled immediately

Example Request — Timed promotional rule

curl -X POST "https://<API_DOMAIN>/v1/links/10/commission-rules" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "commission_rate": 25.0,
    "label": "Launch Week Bonus",
    "priority": 5,
    "valid_from": "2026-05-01",
    "valid_until": "2026-05-07",
    "max_conversions": 50
  }'

Example Request — Permanent always-on rule

curl -X POST "https://<API_DOMAIN>/v1/links/10/commission-rules" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "commission_rate": 15.0,
    "label": "Base link rate"
  }'

Success Response — 201 Created

{
  "success": true,
  "message": "Created",
  "data": {
    "id": 8,
    "label": "Launch Week Bonus",
    "commission_rate": 25.0,
    "priority": 5,
    "valid_from": "2026-05-01T00:00:00.000000Z",
    "valid_until": "2026-05-07T00:00:00.000000Z",
    "max_conversions": 50,
    "conversion_count": 0,
    "is_active": true,
    "status": "scheduled",
    "created_at": "2026-04-13T10:00:00.000000Z"
  }
}

Error Responses

Status Code Description
404 NOT_FOUND Referral link not found
422 VALIDATION_ERROR Invalid fields

Update Commission Rule

Modify an existing commission rule. Only include fields you want to change.

Note: conversion_count is system-managed and cannot be updated via the API.

PATCH /v1/links/{linkId}/commission-rules/{ruleId}

Required scope: affiliates:write Rate limit: 60 requests/minute

Path Parameters

Parameter Type Description
linkId integer Referral link ID
ruleId integer Commission rule ID

Request Body

All fields optional.

Field Type Constraints Description
commission_rate float 0–100 Updated commission percentage
label string|null max 255 Updated label. Send null to clear
priority integer min 0 Updated priority
valid_from date|null Updated start date. Send null to remove
valid_until date|null valid_from if both present Updated expiry. Send null to remove
max_conversions integer|null min 1 Updated cap. Send null to remove cap
is_active boolean Enable or disable the rule

Example Request — Disable a rule

curl -X PATCH "https://<API_DOMAIN>/v1/links/10/commission-rules/8" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'

Example Request — Extend validity window

curl -X PATCH "https://<API_DOMAIN>/v1/links/10/commission-rules/8" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "valid_until": "2026-05-14" }'

Success Response — 200 OK

Returns the updated Commission Rule Object.

Error Responses

Status Code Description
404 NOT_FOUND Link or rule not found for this business
422 VALIDATION_ERROR Invalid field values

Delete Commission Rule

Permanently delete a commission rule. This cannot be undone. In-progress conversions already attributed to this rule are not affected.

DELETE /v1/links/{linkId}/commission-rules/{ruleId}

Required scope: affiliates:write Rate limit: 60 requests/minute

Path Parameters

Parameter Type Description
linkId integer Referral link ID
ruleId integer Commission rule ID

Example Request

curl -X DELETE "https://<API_DOMAIN>/v1/links/10/commission-rules/8" \
  -H "Authorization: Bearer <access_token>"

Success Response — 204 No Content

Empty body.

Error Responses

Status Code Description
404 NOT_FOUND Link or rule not found for this business

Rate Limits

Endpoint Group Limit
GET read endpoints 120 requests/minute
POST, PATCH, PUT, DELETE write endpoints 60 requests/minute
POST /v1/affiliates/register 30 requests/minute

When a rate limit is exceeded, the API returns HTTP 429 Too Many Requests with the following headers:

Retry-After: 30
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0

Wait for the number of seconds indicated by Retry-After before retrying.


Error Reference

All errors follow the standard envelope:

{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description",
    "details": { }
  }
}

details is only present on VALIDATION_ERROR responses.

Error Codes

HTTP Status Code Description
400 BAD_REQUEST Generic bad request
401 UNAUTHORIZED Missing, invalid, or expired Bearer token
403 FORBIDDEN Valid token but insufficient scope, or feature disabled
403 REGISTRATION_DISABLED Self-registration is turned off for this business
404 NOT_FOUND Resource does not exist or does not belong to this business
409 AFFILIATE_EXISTS Duplicate affiliate — email already registered for this business
409 INVALID_STATUS Status transition is not valid from the current state
422 VALIDATION_ERROR Request body failed validation
429 TOO_MANY_REQUESTS Rate limit exceeded
500 SERVER_ERROR Internal server error

Validation Error Example

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": {
      "email": ["The email field is required."],
      "commission_rate": ["The commission rate must not be greater than 100."]
    }
  }
}

Quick Reference

Affiliate Endpoints

Method Path Scope Description
GET /v1/affiliates affiliates:read List affiliates
GET /v1/affiliates/{id} affiliates:read Get affiliate
POST /v1/affiliates affiliates:write Create affiliate (active)
POST /v1/affiliates/register affiliates:write Affiliate self-registration (pending)
PATCH /v1/affiliates/{id} affiliates:write Update affiliate
DELETE /v1/affiliates/{id} affiliates:write Delete affiliate
POST /v1/affiliates/{id}/approve affiliates:write Approve pending affiliate
POST /v1/affiliates/{id}/suspend affiliates:write Suspend affiliate
POST /v1/affiliates/{id}/reactivate affiliates:write Reactivate suspended affiliate
POST /v1/affiliates/{id}/terminate affiliates:write Terminate affiliate (permanent)
Method Path Scope Description
GET /v1/links affiliates:read List all links
GET /v1/affiliates/{id}/links affiliates:read List links for one affiliate
POST /v1/affiliates/{id}/links affiliates:write Create referral link
POST /v1/affiliates/{id}/links?bulk_create=true affiliates:write Bulk create referral links (async)
GET /v1/affiliates/{id}/link-batches/{batchId} affiliates:read Poll batch status
PATCH /v1/links/{id} affiliates:write Update referral link
DELETE /v1/links/{id} affiliates:write Delete referral link

Commission Endpoints

Method Path Scope Description
GET /v1/affiliates/{id}/commission affiliates:read Get affiliate commission config
PUT /v1/affiliates/{id}/commission affiliates:write Set/clear affiliate commission override
GET /v1/links/{id}/commission-rules affiliates:read List commission rules for link
POST /v1/links/{id}/commission-rules affiliates:write Create commission rule
PATCH /v1/links/{linkId}/commission-rules/{ruleId} affiliates:write Update commission rule
DELETE /v1/links/{linkId}/commission-rules/{ruleId} affiliates:write Delete commission rule

Business Settings

Configure your affiliate program settings, branding, and commission tiers via the API.

All settings endpoints require the settings:read or settings:write scope.

Settings Object

{
  "commission_rate": 10.0,
  "cookie_duration": 30,
  "auto_approve_enabled": false,
  "auto_approve_delay_days": 0,
  "auto_approve_min_amount": null,
  "auto_approve_max_amount": null,
  "allow_self_registration": false
}
Field Type Default Description
commission_rate float|null null Default commission rate (%) for all affiliates. null uses platform default. Per-affiliate overrides take priority.
cookie_duration integer 30 How long the affiliate tracking cookie lasts (days, 1–365).
auto_approve_enabled boolean false Automatically approve pending conversions matching criteria.
auto_approve_delay_days integer 0 Days to wait before auto-approving (0–365).
auto_approve_min_amount float|null null Only auto-approve conversions above this amount. null = no minimum.
auto_approve_max_amount float|null null Only auto-approve conversions below this amount. null = no maximum.
allow_self_registration boolean false Allow public affiliate signup at /affiliate/join.

Get All Settings

Retrieve all affiliate program settings for the business.

GET /v1/settings

Scope: settings:read

Response 200 OK

{
  "success": true,
  "message": "OK",
  "data": {
    "commission_rate": null,
    "cookie_duration": 30,
    "auto_approve_enabled": false,
    "auto_approve_delay_days": 0,
    "auto_approve_min_amount": null,
    "auto_approve_max_amount": null,
    "allow_self_registration": false
  }
}

Get Single Setting

Retrieve a single setting by key.

GET /v1/settings/{key}

Scope: settings:read

Path Parameters

Parameter Type Description
key string One of the setting keys listed above

Response 200 OK

{
  "success": true,
  "message": "OK",
  "data": {
    "key": "cookie_duration",
    "value": 30
  }
}

Error 404 Not Found — Unknown setting key.


Update Settings (Bulk)

Update one or more settings in a single request. Only provided fields are updated; omitted fields remain unchanged.

PUT /v1/settings

Scope: settings:write

Request Body (all fields optional)

{
  "commission_rate": 12.5,
  "cookie_duration": 45,
  "auto_approve_enabled": true,
  "auto_approve_delay_days": 7,
  "allow_self_registration": true
}

Response 200 OK — Returns the full settings object after update.


Update Single Setting

Update a single setting by key.

PUT /v1/settings/{key}

Scope: settings:write

Request Body

{
  "value": 90
}

Response 200 OK

{
  "success": true,
  "message": "Setting updated.",
  "data": {
    "key": "cookie_duration",
    "value": 90
  }
}

Error 403 Forbidden — Unknown or disallowed setting key.


Get Branding

Retrieve the business branding configuration.

GET /v1/settings/branding

Scope: settings:read

Response 200 OK

{
  "success": true,
  "message": "OK",
  "data": {
    "logo_url": "https://s3.amazonaws.com/bucket/logo.png",
    "primary_color": "#FF6B2B"
  }
}

Update Branding

Update the business logo URL and/or primary brand color. Pass remove_logo: true to clear the logo.

PUT /v1/settings/branding

Scope: settings:write

Request Body

Field Type Description
logo_url string|null External URL for the business logo (e.g., S3 URL). Max 2048 chars.
primary_color string|null Hex color code (e.g., #FF6B2B).
remove_logo boolean Set to true to remove the current logo.
{
  "logo_url": "https://cdn.example.com/logo.png",
  "primary_color": "#1A2B3C"
}

Response 200 OK — Returns the branding object after update.


Get Commission Tiers

Retrieve platform default tiers and custom business tiers.

GET /v1/settings/commission-tiers

Scope: settings:read

Commission Priority (lowest → highest): Platform base rate → Platform tiers → Business flat rate → Business tiers → Per-affiliate override.

Response 200 OK

{
  "success": true,
  "message": "OK",
  "data": {
    "using_custom": true,
    "platform_tiers": [
      { "min_amount": "0.00", "max_amount": "100.00", "rate": "5.00" }
    ],
    "business_tiers": [
      { "id": 12, "sort_order": 0, "min_amount": "0.00", "max_amount": "500.00", "rate": "8.00" }
    ]
  }
}

Create Commission Tier

Add a custom commission tier for the business. Tier ranges must not overlap.

POST /v1/settings/commission-tiers

Scope: settings:write

Request Body

Field Type Required Description
min_amount numeric Yes Minimum order amount for this tier (≥ 0).
max_amount numeric|null No Maximum order amount. null = unlimited.
rate numeric Yes Commission rate percentage (0–100).

Response 201 Created

{
  "success": true,
  "message": "Created",
  "data": {
    "id": 12,
    "sort_order": 0,
    "min_amount": "0.00",
    "max_amount": "500.00",
    "rate": "8.00"
  }
}

Error 422 Unprocessable Entity — Validation failure or overlapping range.


Update Commission Tier

Update an existing business commission tier.

PUT /v1/settings/commission-tiers/{id}

Scope: settings:write

Request Body — Same as Create Commission Tier.

Response 200 OK — Returns the updated tier.

Error 404 Not Found — Tier not found or belongs to another business.


Delete Commission Tier

Delete a business commission tier.

DELETE /v1/settings/commission-tiers/{id}

Scope: settings:write

Response 204 No Content

Error 404 Not Found — Tier not found or belongs to another business.


Reset Commission Tiers

Delete all custom business tiers, reverting to platform defaults.

POST /v1/settings/commission-tiers/reset

Scope: settings:write

Response 200 OK

{
  "success": true,
  "message": "Commission tiers reset to platform defaults."
}

Settings Endpoints Summary

Method Path Scope Description
GET /v1/settings settings:read Get all settings
PUT /v1/settings settings:write Bulk update settings
GET /v1/settings/{key} settings:read Get single setting
PUT /v1/settings/{key} settings:write Update single setting
GET /v1/settings/branding settings:read Get branding
PUT /v1/settings/branding settings:write Update branding
GET /v1/settings/commission-tiers settings:read Get commission tiers
POST /v1/settings/commission-tiers settings:write Create tier
PUT /v1/settings/commission-tiers/{id} settings:write Update tier
DELETE /v1/settings/commission-tiers/{id} settings:write Delete tier
POST /v1/settings/commission-tiers/reset settings:write Reset to defaults

—————————————Updated April 15, 2026——————————

Orders & Commissions API

Manage tracked conversions (orders), view commission statistics, approve or reject pending commissions, and export data.


Authentication

All endpoints require a Bearer access token with the appropriate scope. See authentication.md.

Scope Access
orders:read List, show, stats, export
orders:write Approve, reject, bulk approve, bulk reject

Endpoints

Method Path Scope Throttle Description
GET /v1/orders orders:read 120/min List orders (paginated)
GET /v1/orders/stats orders:read 120/min Aggregated statistics
GET /v1/orders/export orders:read 30/min CSV export
GET /v1/orders/{id} orders:read 120/min Single order detail
POST /v1/orders/{id}/approve orders:write 60/min Approve pending order
POST /v1/orders/{id}/reject orders:write 60/min Reject pending order
POST /v1/orders/bulk-approve orders:write 60/min Bulk approve
POST /v1/orders/bulk-reject orders:write 60/min Bulk reject

List Orders — GET /v1/orders

Returns a paginated list of conversions with filtering, sorting, and search.

Query Parameters

Parameter Type Required Default Constraints Description
search string No max:255 Search by order ID or affiliate name
status string No pending, approved, rejected, cancelled Filter by conversion status
affiliate_id integer No Valid affiliate ID for this business Filter by affiliate
from date No YYYY-MM-DD Start of date range (inclusive)
to date No YYYY-MM-DD, must be >= from End of date range (inclusive)
sort string No converted_at order_id, order_total, commission_amount, converted_at, status Sort field
direction string No desc asc, desc Sort direction
per_page integer No 25 1–100 Items per page
page integer No 1 min:1 Page number

Example

curl "https://<API_DOMAIN>/v1/orders?status=pending&sort=order_total&direction=desc&per_page=10" \
  -H "Authorization: Bearer <access_token>"

Success Response — HTTP 200

{
  "success": true,
  "message": "OK",
  "data": {
    "items": [
      {
        "id": 42,
        "order_id": "#135419",
        "affiliate": {
          "id": 7,
          "name": "Salman",
          "referral_code": "SALMAN2026"
        },
        "customer": {
          "name": "Geonwoo Park",
          "email": "geonwoo@example.com"
        },
        "subtotal": "70.00",
        "discount_applied": "0.00",
        "tax": "0.00",
        "order_total": "70.00",
        "commission_rate": "10.00",
        "commission_amount": "7.00",
        "currency": "USD",
        "status": "pending",
        "converted_at": "2026-04-10T14:30:00.000000Z",
        "created_at": "2026-04-10T14:30:00.000000Z"
      },
      {
        "id": 41,
        "order_id": "#135380",
        "affiliate": null,
        "customer": null,
        "subtotal": "140.00",
        "discount_applied": "0.00",
        "tax": "0.00",
        "order_total": "140.00",
        "commission_rate": "0.00",
        "commission_amount": "0.00",
        "currency": "USD",
        "status": "approved",
        "converted_at": "2026-04-08T14:30:00.000000Z",
        "created_at": "2026-04-08T14:30:00.000000Z"
      }
    ],
    "meta": {
      "current_page": 1,
      "per_page": 10,
      "total": 2,
      "last_page": 1
    }
  }
}

Response Fields

Field Type Description
items[].id integer Internal conversion record ID
items[].order_id string External order identifier
items[].affiliate object|null Attributed affiliate (null if unattributed)
items[].affiliate.id integer Affiliate ID
items[].affiliate.name string Affiliate’s display name
items[].affiliate.referral_code string Affiliate’s referral code
items[].customer object|null Customer info (null if no customer data)
items[].customer.name string|null Customer name
items[].customer.email string|null Customer email
items[].subtotal string Pre-discount amount (2 decimal places)
items[].discount_applied string Discount amount
items[].tax string Tax amount
items[].order_total string Gross total
items[].commission_rate string Commission rate (%) applied
items[].commission_amount string Computed commission amount
items[].currency string 3-letter currency code (default USD)
items[].status string pending, approved, rejected, or cancelled
items[].converted_at string|null ISO 8601 timestamp of conversion
items[].created_at string ISO 8601 timestamp of record creation
meta.current_page integer Current page number
meta.per_page integer Items per page
meta.total integer Total matching records
meta.last_page integer Last available page

Get Order Stats — GET /v1/orders/stats

Returns aggregated summary statistics for all conversions. Optionally filter by date range.

Query Parameters

Parameter Type Required Constraints Description
from date No YYYY-MM-DD Start of date range
to date No YYYY-MM-DD, must be >= from End of date range

Example

curl "https://<API_DOMAIN>/v1/orders/stats?from=2026-04-01&to=2026-04-15" \
  -H "Authorization: Bearer <access_token>"

Success Response — HTTP 200

{
  "success": true,
  "message": "OK",
  "data": {
    "total_orders": 2,
    "total_revenue": 210.00,
    "total_commission": 14.00,
    "pending_commission": 0.00,
    "approved_commission": 14.00
  }
}

Response Fields

Field Type Description
total_orders integer Total number of conversions
total_revenue number Sum of order_total across all conversions
total_commission number Sum of commission_amount across all conversions
pending_commission number Sum of commission_amount for pending conversions
approved_commission number Sum of commission_amount for approved conversions

Get Order Detail — GET /v1/orders/{id}

Returns the full detail for a single conversion, including line items and click attribution.

Path Parameters

Parameter Type Description
id integer Conversion record ID

Example

curl "https://<API_DOMAIN>/v1/orders/42" \
  -H "Authorization: Bearer <access_token>"

Success Response — HTTP 200

{
  "success": true,
  "message": "OK",
  "data": {
    "id": 42,
    "order_id": "#135380",
    "affiliate": {
      "id": 7,
      "name": "Salman",
      "referral_code": "SALMAN2026",
      "commission_rate": "10.00"
    },
    "customer": {
      "name": "Jane Smith",
      "email": "jane@example.com"
    },
    "subtotal": "140.00",
    "discount_applied": "0.00",
    "tax": "0.00",
    "order_total": "140.00",
    "commission_rate": "10.00",
    "commission_amount": "14.00",
    "currency": "USD",
    "status": "approved",
    "line_items": [
      { "name": "Mountain Bike Rental", "quantity": 1, "price": "140.00" }
    ],
    "ip_address": "192.168.1.100",
    "click": {
      "id": 123,
      "referral_link_id": 45,
      "landing_page": "https://mountainthreads.example.com/bikes",
      "referrer": "https://instagram.com",
      "created_at": "2026-04-08T10:00:00.000000Z"
    },
    "converted_at": "2026-04-08T14:30:00.000000Z",
    "created_at": "2026-04-08T14:30:00.000000Z",
    "updated_at": "2026-04-08T15:00:00.000000Z"
  }
}

Additional Detail Fields

These fields appear only in the detail response (not in the list):

Field Type Description
affiliate.commission_rate string|null Affiliate’s override commission rate (may differ from business default)
line_items array|null Product details from the order
ip_address string|null Customer’s IP address
click object|null Attribution click data (null if direct conversion)
click.id integer Click record ID
click.referral_link_id integer Associated referral link ID
click.landing_page string|null Page URL where the click landed
click.referrer string|null Referring URL
click.created_at string ISO 8601 timestamp of the click
updated_at string ISO 8601 timestamp of last update

Error — HTTP 404

{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Order not found."
  }
}

Approve Order — POST /v1/orders/{id}/approve

Transitions a pending conversion to approved status. The commission is considered owed once approved.

Path Parameters

Parameter Type Description
id integer Conversion record ID

Body Parameters

Field Type Required Constraints Description
notes string No max:1000 Approval notes (stored in audit log)

Example

curl -X POST "https://<API_DOMAIN>/v1/orders/42/approve" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "notes": "Verified payment received" }'

Success Response — HTTP 200

{
  "success": true,
  "message": "Order #135419 approved.",
  "data": {
    "id": 42,
    "order_id": "#135419",
    "status": "approved",
    "commission_amount": "7.00",
    "updated_at": "2026-04-15T12:00:00.000000Z"
  }
}

Error — HTTP 409 (Invalid Status Transition)

{
  "success": false,
  "error": {
    "code": "INVALID_STATUS",
    "message": "Only pending conversions can be approved. Current status: approved"
  }
}

Error — HTTP 404

{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Order not found."
  }
}

Reject Order — POST /v1/orders/{id}/reject

Transitions a pending conversion to rejected status. The commission is voided.

Path Parameters

Parameter Type Description
id integer Conversion record ID

Body Parameters

Field Type Required Constraints Description
reason string No max:1000 Rejection reason (stored in audit log)

Example

curl -X POST "https://<API_DOMAIN>/v1/orders/42/reject" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Fraudulent order - chargeback received" }'

Success Response — HTTP 200

{
  "success": true,
  "message": "Order #135419 rejected.",
  "data": {
    "id": 42,
    "order_id": "#135419",
    "status": "rejected",
    "commission_amount": "7.00",
    "updated_at": "2026-04-15T12:00:00.000000Z"
  }
}

Error — HTTP 409 (Invalid Status Transition)

{
  "success": false,
  "error": {
    "code": "INVALID_STATUS",
    "message": "Only pending conversions can be rejected. Current status: approved"
  }
}

Bulk Approve — POST /v1/orders/bulk-approve

Approves multiple pending conversions in a single request. Non-pending conversions are silently skipped.

Body Parameters

Field Type Required Constraints Description
ids integer[] Yes min:1, max:100, each distinct Conversion IDs to approve

Example

curl -X POST "https://<API_DOMAIN>/v1/orders/bulk-approve" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "ids": [42, 43, 44, 45, 46] }'

Success Response — HTTP 200

{
  "success": true,
  "message": "3 order(s) approved.",
  "data": {
    "approved_count": 3,
    "requested_count": 5,
    "skipped_count": 2
  }
}

Response Fields

Field Type Description
approved_count integer Number of conversions actually approved
requested_count integer Total IDs submitted in the request
skipped_count integer IDs that were skipped (non-existent, wrong business, or not pending)

Error — HTTP 422

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": {
      "ids": ["The ids field is required."]
    }
  }
}

Bulk Reject — POST /v1/orders/bulk-reject

Rejects multiple pending conversions in a single request. Non-pending conversions are silently skipped.

Body Parameters

Field Type Required Constraints Description
ids integer[] Yes min:1, max:100, each distinct Conversion IDs to reject
reason string No max:1000 Rejection reason (applied to all, stored in audit log)

Example

curl -X POST "https://<API_DOMAIN>/v1/orders/bulk-reject" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "ids": [47, 48], "reason": "Duplicate orders from same session" }'

Success Response — HTTP 200

{
  "success": true,
  "message": "2 order(s) rejected.",
  "data": {
    "rejected_count": 2,
    "requested_count": 2,
    "skipped_count": 0
  }
}

Response Fields

Field Type Description
rejected_count integer Number of conversions actually rejected
requested_count integer Total IDs submitted in the request
skipped_count integer IDs that were skipped

Export Orders — GET /v1/orders/export

Downloads all matching conversions as a CSV file. Same filters as the list endpoint but without pagination — returns all matching records up to 10,000 rows.

Query Parameters

Parameter Type Required Constraints Description
search string No max:255 Search by order ID or affiliate name
status string No pending, approved, rejected, cancelled Filter by status
affiliate_id integer No Valid affiliate ID Filter by affiliate
from date No YYYY-MM-DD Start of date range
to date No YYYY-MM-DD End of date range

Example

curl "https://<API_DOMAIN>/v1/orders/export?status=approved&from=2026-04-01" \
  -H "Authorization: Bearer <access_token>" \
  -o orders-export.csv

Success Response — HTTP 200

Content-Type: text/csv Content-Disposition: attachment; filename="orders-export-2026-04-15.csv"

Order ID,Affiliate Name,Affiliate Code,Subtotal,Discount,Tax,Order Total,Commission Rate (%),Commission Amount,Currency,Status,Customer Email,Converted At
#135380,Salman,SALMAN2026,140.00,0.00,0.00,140.00,10.00,14.00,USD,approved,jane@example.com,2026-04-08 14:30:00
#135419,,,70.00,0.00,0.00,70.00,0.00,0.00,USD,pending,geonwoo@example.com,2026-04-10 14:30:00

CSV Columns

Column Description
Order ID External order identifier
Affiliate Name Affiliate’s display name (empty if unattributed)
Affiliate Code Affiliate’s referral code (empty if unattributed)
Subtotal Pre-discount amount
Discount Discount applied
Tax Tax amount
Order Total Gross total
Commission Rate (%) Rate applied
Commission Amount Computed commission
Currency 3-letter currency code
Status Conversion status
Customer Email Customer’s email (empty if unavailable)
Converted At Conversion timestamp

Conversion Status Workflow

  ┌─────────┐
  │ pending  │ ← initial state (from SDK tracking)
  └────┬─────┘
       │
  ┌────┴────┐
  ▼         ▼
┌─────────┐ ┌──────────┐
│ approved│ │ rejected │
└─────────┘ └──────────┘

  ┌───────────┐
  │ cancelled │ ← set by system (order cancellation)
  └───────────┘
  • Only pending conversions can be approved or rejected via the API.
  • Attempting to approve/reject a non-pending conversion returns HTTP 409.
  • The cancelled status is set by the system when an order is cancelled — it is not available as an API action.

Error Reference

HTTP Status Error Code Cause
401 UNAUTHORIZED Missing or invalid Bearer token
403 FORBIDDEN Token does not have required scope (orders:read or orders:write)
404 NOT_FOUND Conversion ID does not exist or belongs to a different business
409 INVALID_STATUS Attempted to approve/reject a non-pending conversion
422 VALIDATION_ERROR Invalid query parameters or request body
429 Rate limit exceeded

Rate Limiting

Endpoint Group Limit
Read endpoints (list, stats, show) 120 requests/min
Write endpoints (approve, reject, bulk) 60 requests/min
Export 30 requests/min

Exceeding the limit returns HTTP 429 Too Many Requests.

—————————————Updated April 16, 2026——————————

Affiliate Reports API

Performance analytics and payout overview for affiliate programs. Retrieve stat cards with trend indicators, time-series commission charts, affiliate leaderboards, campaign breakdowns, payout history, and full CSV exports.


Authentication

All endpoints require a Bearer access token with the appropriate scope. See authentication.md.

Scope Access
reports:read Stats, charts, tables, payouts, export

Endpoints

Method Path Scope Throttle Description
GET /v1/reports/stats reports:read 120/min Headline stat cards with trend percentages
GET /v1/reports/commission-chart reports:read 120/min Commission over time (daily/weekly/monthly)
GET /v1/reports/top-affiliates reports:read 120/min Top affiliates ranked by earnings
GET /v1/reports/clicks-by-campaign reports:read 120/min Click counts grouped by referral link campaign
GET /v1/reports/payouts reports:read 120/min Payout history (paginated)
GET /v1/reports/export reports:read 30/min CSV export of report data

Common Query Parameters

All endpoints accept date range filters. When omitted, defaults to the last 30 days.

Parameter Type Required Default Constraints Description
from date No 30 days ago YYYY-MM-DD, must be <= to Start of date range (inclusive)
to date No today YYYY-MM-DD, must be >= from End of date range (inclusive)

Report Stats — GET /v1/reports/stats

Returns four headline metrics with percentage trends compared to the previous period of equal length.

Example

curl "https://<API_DOMAIN>/v1/reports/stats?from=2026-03-17&to=2026-04-16" \
  -H "Authorization: Bearer <access_token>"

Success Response — HTTP 200

{
  "success": true,
  "message": "Report statistics retrieved.",
  "data": {
    "total_commission_paid": 28.00,
    "pending_payouts": 14.00,
    "conversion_rate": 3.2,
    "total_referral_clicks": 2,
    "trends": {
      "commission_paid": 14.0,
      "pending_payouts": 0.0,
      "conversion_rate": 1.1,
      "referral_clicks": -8.3
    },
    "filters": {
      "from": "2026-03-17",
      "to": "2026-04-16"
    }
  }
}

Response Fields

Field Type Description
total_commission_paid number Sum of commission_amount from approved conversions in the date range
pending_payouts number Approved commission minus completed payouts (owed but not yet paid)
conversion_rate number Percentage of referral clicks that resulted in a conversion
total_referral_clicks integer Total distinct referral click sessions in the date range
trends.commission_paid number % change vs previous period (positive = increase)
trends.pending_payouts number Reserved — always 0.0 (trend requires payout history)
trends.conversion_rate number Absolute percentage-point change vs previous period
trends.referral_clicks number % change vs previous period
filters.from string Applied start date (YYYY-MM-DD)
filters.to string Applied end date (YYYY-MM-DD)

Trend Calculation

Trends compare the requested date range against the immediately preceding period of equal length. For example, if from=2026-03-17 and to=2026-04-16 (31 days), the previous period is 2026-02-14 to 2026-03-16.

  • Commission and clicks: ((current - previous) / previous) * 100, rounded to 1 decimal place. Returns 0.0 when the previous period value is zero.
  • Conversion rate: Absolute difference in percentage points (current_rate - previous_rate), rounded to 1 decimal place.

Commission Chart — GET /v1/reports/commission-chart

Returns time-series data for rendering a commission-over-time bar chart. Supports daily, weekly, or monthly granularity.

Query Parameters

Parameter Type Required Default Constraints Description
from date No 30 days ago YYYY-MM-DD Start of date range
to date No today YYYY-MM-DD End of date range
granularity string No daily daily, weekly, monthly Time bucket size

Example

curl "https://<API_DOMAIN>/v1/reports/commission-chart?from=2026-04-01&to=2026-04-16&granularity=daily" \
  -H "Authorization: Bearer <access_token>"

Success Response — HTTP 200

{
  "success": true,
  "message": "Commission chart data retrieved.",
  "data": {
    "granularity": "daily",
    "labels": ["Apr 01", "Apr 02", "Apr 03", "Apr 04", "Apr 05"],
    "data": [4.50, 0.00, 12.00, 0.00, 7.00],
    "filters": {
      "from": "2026-04-01",
      "to": "2026-04-16"
    }
  }
}

Response Fields

Field Type Description
granularity string Applied granularity (daily, weekly, or monthly)
labels string[] Human-readable time bucket labels
data number[] Sum of commission_amount for each time bucket (2 decimal places)
filters.from string Applied start date
filters.to string Applied end date

Label Format by Granularity

Granularity Format Example
daily MMM DD Apr 01
weekly MMM DD – MMM DD Apr 01 – Apr 07
monthly MMM YYYY Apr 2026

Empty time buckets are filled with 0.00 to ensure the chart has no gaps.


Top Affiliates — GET /v1/reports/top-affiliates

Returns the top-performing affiliates ranked by commission earned within the date range. Only affiliates with at least one conversion or click in the period are included.

Query Parameters

Parameter Type Required Default Constraints Description
from date No 30 days ago YYYY-MM-DD Start of date range
to date No today YYYY-MM-DD End of date range
limit integer No 10 1–50 Maximum number of affiliates to return

Example

curl "https://<API_DOMAIN>/v1/reports/top-affiliates?from=2026-03-17&to=2026-04-16&limit=5" \
  -H "Authorization: Bearer <access_token>"

Success Response — HTTP 200

{
  "success": true,
  "message": "Top affiliates retrieved.",
  "data": {
    "items": [
      {
        "affiliate_id": 1,
        "name": "Salman",
        "referral_code": "SALMAN2026",
        "earned": 140.00,
        "clicks": 2,
        "conversions": 5
      }
    ],
    "filters": {
      "from": "2026-03-17",
      "to": "2026-04-16"
    }
  }
}

Response Fields

Field Type Description
items[].affiliate_id integer Affiliate record ID
items[].name string Affiliate user’s display name
items[].referral_code string Affiliate’s referral code
items[].earned number Total commission earned in the date range (2 decimal places)
items[].clicks integer Total referral clicks attributed to this affiliate
items[].conversions integer Total conversions attributed to this affiliate
filters.from string Applied start date
filters.to string Applied end date

Clicks by Campaign — GET /v1/reports/clicks-by-campaign

Returns referral click counts grouped by the campaign tag on referral links. All active campaigns for the business are included, even those with zero clicks in the date range.

Query Parameters

Parameter Type Required Default Constraints Description
from date No 30 days ago YYYY-MM-DD Start of date range
to date No today YYYY-MM-DD End of date range
limit integer No 10 1–50 Maximum number of campaigns to return

Example

curl "https://<API_DOMAIN>/v1/reports/clicks-by-campaign?from=2026-03-17&to=2026-04-16" \
  -H "Authorization: Bearer <access_token>"

Success Response — HTTP 200

{
  "success": true,
  "message": "Clicks by campaign retrieved.",
  "data": {
    "items": [
      {
        "campaign": "mountain_rentals_brand",
        "clicks": 0
      },
      {
        "campaign": "women_skier_interest",
        "clicks": 0
      },
      {
        "campaign": "winter_sale",
        "clicks": 0
      },
      {
        "campaign": null,
        "clicks": 2
      }
    ],
    "filters": {
      "from": "2026-03-17",
      "to": "2026-04-16"
    }
  }
}

Response Fields

Field Type Description
items[].campaign string|null Campaign tag from the referral link (null = links with no campaign assigned)
items[].clicks integer Total referral clicks for links with this campaign tag
filters.from string Applied start date
filters.to string Applied end date

Results are ordered by clicks descending. The null campaign entry (representing links without a campaign tag) is always listed last.


Payouts — GET /v1/reports/payouts

Returns a paginated list of payout records with optional filtering by status and affiliate.

Query Parameters

Parameter Type Required Default Constraints Description
from date No 30 days ago YYYY-MM-DD Start of date range
to date No today YYYY-MM-DD End of date range
status string No all pending, completed, failed Filter by payout status
affiliate_id integer No all Affiliate ID Filter by affiliate
per_page integer No 15 1–100 Items per page
page integer No 1 min:1 Page number

Example

curl "https://<API_DOMAIN>/v1/reports/payouts?status=completed&per_page=10" \
  -H "Authorization: Bearer <access_token>"

Success Response — HTTP 200

{
  "success": true,
  "message": "Payouts retrieved.",
  "data": {
    "items": [
      {
        "id": 1,
        "affiliate_id": 1,
        "affiliate_name": "Salman",
        "amount": 28.00,
        "currency": "USD",
        "method": "bank_transfer",
        "status": "completed",
        "reference": "TXN-20260410-001",
        "paid_at": "2026-04-10T14:30:00.000000Z",
        "created_at": "2026-04-10T14:00:00.000000Z"
      }
    ],
    "meta": {
      "current_page": 1,
      "per_page": 15,
      "total": 1,
      "last_page": 1
    },
    "filters": {
      "from": "2026-03-17",
      "to": "2026-04-16"
    }
  }
}

Response Fields

Field Type Description
items[].id integer Payout record ID
items[].affiliate_id integer Affiliate record ID
items[].affiliate_name string Affiliate user’s display name
items[].amount number Payout amount (2 decimal places)
items[].currency string 3-letter currency code (default USD)
items[].method string|null Payment method (bank_transfer, paypal, check, other)
items[].status string pending, completed, or failed
items[].reference string|null External transaction reference/ID
items[].paid_at string|null ISO 8601 timestamp when payment was completed (null if pending/failed)
items[].created_at string ISO 8601 timestamp of payout record creation
meta.current_page integer Current page number
meta.per_page integer Items per page
meta.total integer Total matching records
meta.last_page integer Last available page

Date Range Behavior

Date range filters are applied differently based on payout status:

  • Completed payouts: filtered by paid_at (when the payment was made)
  • Pending/failed payouts: filtered by created_at (when the payout was recorded)

This ensures completed payouts appear in the period they were paid, not when they were initially created.


Export Report — GET /v1/reports/export

Downloads a CSV file containing report data. By default exports all sections; use the sections parameter to select specific sections.

Query Parameters

Parameter Type Required Default Constraints Description
from date No 30 days ago YYYY-MM-DD Start of date range
to date No today YYYY-MM-DD End of date range
sections string No all Comma-separated list Sections to include in the export

Available Sections

Section Key Description
stats Headline metrics with trend percentages
commission_chart Commission over time (daily granularity)
top_affiliates Top affiliates ranked by earnings
clicks_by_campaign Click counts grouped by campaign
payouts Payout history

Example

curl "https://<API_DOMAIN>/v1/reports/export?from=2026-04-01&to=2026-04-16&sections=stats,top_affiliates" \
  -H "Authorization: Bearer <access_token>" \
  -o affiliate-reports.csv

Success Response — HTTP 200

Content-Type: text/csv Content-Disposition: attachment; filename="affiliate-reports-2026-04-16.csv"

"--- Report Stats ---"
Metric,Value,Trend (%)
Total Commission Paid,28.00,14.0
Pending Payouts,14.00,0.0
Conversion Rate (%),3.2,1.1
Total Referral Clicks,2,-8.3

"--- Top Affiliates ---"
Name,Referral Code,Earned,Clicks,Conversions
Salman,SALMAN2026,140.00,2,5

"--- Clicks by Campaign ---"
Campaign,Clicks
mountain_rentals_brand,0
women_skier_interest,0
winter_sale,0
(no campaign),2

"--- Commission Over Time (daily) ---"
Date,Commission
Apr 01,4.50
Apr 02,0.00
Apr 03,12.00

"--- Payouts ---"
Affiliate,Amount,Currency,Method,Status,Reference,Paid At,Created At
Salman,28.00,USD,bank_transfer,completed,TXN-20260410-001,2026-04-10 14:30:00,2026-04-10 14:00:00

CSV Structure

  • Each section is separated by a header row in the format --- Section Name ---
  • Sections appear in the order: stats, top affiliates, clicks by campaign, commission chart, payouts
  • When sections is specified, only the requested sections appear
  • Unknown section keys in the sections parameter are silently ignored
  • Payout timestamps use YYYY-MM-DD HH:MM:SS format (not ISO 8601)

Payout Status Workflow

  ┌─────────┐
  │ pending  │ ← initial state (payout recorded)
  └────┬─────┘
       │
  ┌────┴────┐
  ▼         ▼
┌───────────┐ ┌────────┐
│ completed │ │ failed │
└───────────┘ └────────┘
  • **pending**: Payout has been recorded but not yet processed
  • **completed**: Payment was successfully processed (paid_at is set)
  • **failed**: Payment processing failed

Error Reference

HTTP Status Error Code Cause
401 UNAUTHORIZED Missing or invalid Bearer token
403 FORBIDDEN Token does not have reports:read scope
422 VALIDATION_ERROR Invalid query parameters (bad date format, invalid granularity, etc.)
429 Rate limit exceeded
500 REPORT_ERROR Internal error during report generation

Error Response Format

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": {
      "granularity": ["The selected granularity is invalid."]
    }
  }
}

Rate Limiting

Endpoint Group Limit
Read endpoints (stats, chart, tables, payouts) 120 requests/min
Export 30 requests/min

Exceeding the limit returns HTTP 429 Too Many Requests.

Returns aggregate statistics for all referral links in your business.

GET /v1/links/stats

Required scope: affiliates:read Rate limit: 120 requests/minute

Example Request

curl "https://<API_DOMAIN>/v1/links/stats" \
  -H "Authorization: Bearer <access_token>"

Success Response — 200 OK

{
  "success": true,
  "message": "OK",
  "data": {
    "total": 42,
    "active": 38,
    "inactive": 4,
    "total_clicks": 1523
  }
}

Response Fields

Field Type Description
total integer Total number of referral links (active + inactive)
active integer Links with is_active = true
inactive integer Links with is_active = false
total_clicks integer Sum of click_count across all links

Note: Soft-deleted links are excluded from all counts. total_clicks is derived from the denormalized click_count field on each link, which is incremented in real time by the click tracking system.


————————————————Updated April 17, 2026 ———————————————————


Get Affiliate Stats

Returns aggregate counts of affiliates grouped by status for your business.

GET /v1/affiliates/stats

Required scope: affiliates:read Rate limit: 120 requests/minute

Example Request

curl "https://<API_DOMAIN>/v1/affiliates/stats" \
  -H "Authorization: Bearer <access_token>"

Success Response — 200 OK

{
  "success": true,
  "message": "OK",
  "data": {
    "total": 120,
    "active": 85,
    "pending": 20,
    "suspended": 10,
    "terminated": 5
  }
}

Response Fields

Field Type Description
total integer Total number of affiliates across all statuses
active integer Affiliates with status = active
pending integer Affiliates with status = pending (awaiting approval)
suspended integer Affiliates with status = suspended
terminated integer Affiliates with status = terminated

Note: Soft-deleted affiliates are excluded from all counts. total equals the sum of all four status counts.



Export Affiliates

Downloads affiliates as a CSV file. Supports the same filters as List Affiliates but without pagination — returns all matching records. An additional ids parameter allows exporting a specific selection of affiliates (e.g., from checkbox selection in the UI).

GET /v1/affiliates/export

Required scope: affiliates:read Rate limit: 30 requests/minute

Query Parameters

Parameter Type Required Constraints Description
ids string No Comma-separated integers Export only these affiliate IDs (e.g., 42,55,78). When provided, status and search are ignored — exact selection takes precedence
status string No pending, active, suspended, terminated Filter by status. Ignored when ids is set
search string No max:255 Search by name, email, or referral code. Ignored when ids is set
sort string No created_at, referral_code, status, commission_rate Sort field. Default: created_at
direction string No asc, desc Sort direction. Default: desc

Filter precedence: When ids is provided, the endpoint returns exactly those affiliates (scoped to your business) regardless of any status or search parameters. Sorting still applies.

Example — Export all affiliates

curl "https://<API_DOMAIN>/v1/affiliates/export" \
  -H "Authorization: Bearer <access_token>" \
  -o affiliates.csv

Example — Export only active affiliates

curl "https://<API_DOMAIN>/v1/affiliates/export?status=active&sort=referral_code&direction=asc" \
  -H "Authorization: Bearer <access_token>" \
  -o active-affiliates.csv

Example — Export selected affiliates by ID

curl "https://<API_DOMAIN>/v1/affiliates/export?ids=42,55,78" \
  -H "Authorization: Bearer <access_token>" \
  -o selected-affiliates.csv

Success Response — HTTP 200

Content-Type: text/csv Content-Disposition: attachment; filename="affiliates-export-2026-04-17.csv"

ID,Name,Email,Phone,Referral Code,Commission Rate,Status,Joined,Approved At,Suspended At,Terminated At,Notes
42,"Jane Doe",jane@example.com,+14155552671,ABC12345,10.00,active,"2026-03-15 14:22:00","2026-04-01 09:00:00",,,"VIP partner"
55,"John Smith",john@example.com,,XK9P2QR7,,pending,"2026-04-13 11:30:00",,,,

CSV Columns

Column Description
ID Unique affiliate ID
Name Affiliate’s full name (empty if no linked user)
Email Affiliate’s email address (empty if no linked user)
Phone Phone number (empty if not set)
Referral Code Unique tracking code used in referral links
Commission Rate Affiliate-specific override rate (%). Empty if inheriting business default
Status Current status: pending, active, suspended, or terminated
Joined When the affiliate record was created (YYYY-MM-DD HH:MM:SS)
Approved At When approved (empty if never approved)
Suspended At When suspended (empty if never suspended)
Terminated At When terminated (empty if not terminated)
Notes Internal notes (may contain newlines; properly CSV-escaped)

Error Responses

Status Code Description
401 UNAUTHORIZED Missing or invalid Bearer token
403 FORBIDDEN Token does not have affiliates:read scope
422 VALIDATION_ERROR Invalid ids format (each value must be a positive integer) or invalid filter values


Get Affiliate Stats (Individual)

Returns performance statistics for a single affiliate: total earned commission, pending payouts, total orders, and referral clicks.

GET /v1/affiliates/{id}/stats

Required scope: affiliates:read Rate limit: 120 requests/minute

Path Parameters

Parameter Type Description
id integer Affiliate ID

Query Parameters

Parameter Type Default Description
date_from date Start of date range (inclusive). Format: YYYY-MM-DD. Omit for all-time
date_to date End of date range (inclusive). Format: YYYY-MM-DD. Must be ≥ date_from

Example Request — All-time stats

curl "https://<API_DOMAIN>/v1/affiliates/42/stats" \
  -H "Authorization: Bearer <access_token>"

Example Request — Date-filtered stats

curl "https://<API_DOMAIN>/v1/affiliates/42/stats?date_from=2026-04-01&date_to=2026-04-30" \
  -H "Authorization: Bearer <access_token>"

Success Response — 200 OK

{
  "success": true,
  "message": "OK",
  "data": {
    "total_earned": 175.00,
    "pending_payout": 35.00,
    "total_orders": 12,
    "referral_clicks": 483
  }
}

Response Fields

Field Type Description
total_earned float Sum of commission_amount across all conversions (all statuses)
pending_payout float Sum of commission_amount for conversions with pending status only
total_orders integer Total number of conversions (all statuses)
referral_clicks integer Total number of tracking clicks

Note: When no date range is specified, all-time totals are returned. total_earned includes commissions in all statuses (pending, approved, rejected, cancelled). pending_payout only includes commissions awaiting approval.

Error Responses

Status Code Description
404 NOT_FOUND Affiliate does not exist for this business
422 VALIDATION_ERROR Invalid date format or date_to is before date_from

Create referral links across every affiliate of a business in a single API call. Each affiliate receives the full set of provided link templates. Processing is asynchronous via a fan-out worker pattern that distributes work into per-affiliate child jobs.


Overview

This endpoint is intended for partner platforms and business operators that need to roll out a set of destination URLs (and optional per-affiliate coupons) to their entire affiliate roster — for example, when launching a new product, refreshing campaigns, or onboarding affiliates to an existing catalog.

Key characteristics

Property Value
Path POST /v1/links/bulk-all-affiliates
Authentication Bearer access token
Scope required affiliates:write
Rate limit 60 req/min (shared with affiliate-write group)
Processing model Asynchronous (queued)
Response 202 Accepted with batch_id
Affiliates per request Determined by affiliate_filter (default: all active)
Link templates per request 150
Total items hard limit 50,000 (affiliates × templates)
Status polling GET /v1/link-batches/{batchId}

For single-affiliate bulk creation, use [POST /v1/affiliates/{affiliateId}/links?bulk_create=true](/docs/api/partner-bulk-referral-links). This endpoint is a separate, additive flow and does not replace it.


Workflow

Client                    API                          Queue
  │                        │                             │
  │  POST /links/bulk-all-affiliates                     │
  │ ───────────────────────►                             │
  │                        │                             │
  │                        │  Create ReferralLinkBatch   │
  │                        │  (scope=all_affiliates)     │
  │                        │                             │
  │                        │  Dispatch fan-out job ─────►│
  │                        │                             │
  │ ◄─────────────────────  202 + batch_id              │
  │                                                      │
  │                                                      │  Fan-out:
  │                                                      │  chunkById(50)
  │                                                      │  dispatch N
  │                                                      │  child jobs
  │                                                      │
  │                                                      │  Child job per
  │                                                      │  affiliate:
  │                                                      │  - expand
  │                                                      │    templates
  │                                                      │  - resolve
  │                                                      │    coupons
  │                                                      │  - create links
  │                                                      │  - increment
  │                                                      │    counters
  │                                                      │
  │  GET /link-batches/{id} (poll)                       │
  │ ───────────────────────►                             │
  │ ◄─────────────────────  status + counts             │

Prerequisites

Requirement Details
API credentials Issued via POST /v1/auth/token
Affiliates exist At least one active affiliate under the tenant
Scope Access token must carry affiliates:write
Optional coupon templates Use coupon_code_prefix (not raw coupon_code) — see Coupon Templates

Endpoint

POST /v1/links/bulk-all-affiliates

Headers

Authorization: Bearer <access_token>
Content-Type: application/json

Request body

{
  "affiliate_filter": {
    "status": "active",
    "search": "vip"
  },
  "links": [
    {
      "destination_url": "https://example.com/products/widget",
      "campaign": "spring-2026",
      "is_active": true,
      "metadata": { "category": "widgets", "priority": 1 },
      "coupon_code_prefix": "SPRING",
      "coupon_type": "percentage",
      "coupon_value": 15.00,
      "coupon_min_order": 50.00,
      "coupon_max_uses": 100,
      "coupon_expires_at": "2026-06-30T23:59:59Z"
    },
    {
      "destination_url": "https://example.com/products/gadget",
      "campaign": "spring-2026"
    }
  ]
}

Field reference

Field Type Required Constraints Description
affiliate_filter object No Optional affiliate filter. Defaults to {status: "active"} if omitted.
affiliate_filter.status string No active Affiliate status to target. Currently only active accepted.
affiliate_filter.search string No max: 255 Case-insensitive match against affiliate referral_code, user name, or user email.
links array Yes min: 1, max: 50 Array of link templates. Each template is applied to every matching affiliate.
links[].destination_url string Yes valid URL, max: 2048 Target URL for the referral link.
links[].campaign string No max: 255 Optional campaign label.
links[].is_active boolean No default true Whether the generated links start active.
links[].metadata object No Arbitrary JSON metadata.
links[].discount_code_id integer No must exist under tenant Attach an existing discount code. Mutually exclusive with coupon_code_prefix.
links[].coupon_code_prefix string No max: 30, [A-Za-z0-9_-] Prefix used to mint a per-affiliate coupon code. See Coupon Templates.
links[].coupon_type string Required with coupon_code_prefix percentage | fixed Coupon type.
links[].coupon_value number Required with coupon_code_prefix >= 0.01 Coupon value.
links[].coupon_min_order number No >= 0 Minimum order amount for the coupon.
links[].coupon_max_uses integer No >= 1 Optional max-uses cap.
links[].coupon_expires_at datetime No must be in the future Coupon expiry timestamp.

Rejected fields

These are explicitly rejected by validation — supplying them returns 422:

Field Reason
links[].slug Slugs are globally unique. Auto-generated per affiliate to avoid collision.
links[].coupon_code Raw coupon codes are unique per business and cannot be shared across affiliates. Use coupon_code_prefix instead.

Coupon Templates

When coupon_code_prefix is supplied, a unique coupon code is materialized per affiliate at processing time:

final_code = "{coupon_code_prefix}-{AFFILIATE_REFERRAL_CODE}"

For example, prefix SPRING with three affiliates whose referral_codes are ABC123, DEF456, GHI789 yields three coupons: SPRING-ABC123, SPRING-DEF456, SPRING-GHI789. Each coupon is owned by its respective affiliate.

Coupons are created via firstOrCreate keyed on (business_id, code) — running the same prefix twice is idempotent and does not produce duplicates.


Response

Success — 202 Accepted

{
  "success": true,
  "message": "Bulk creation queued for all affiliates.",
  "data": {
    "batch_id": 1284,
    "scope": "all_affiliates",
    "status": "pending",
    "total_affiliates": 312,
    "total_items": 624,
    "status_url": "/v1/link-batches/1284"
  }
}
Field Description
batch_id Identifier used for polling.
scope Always all_affiliates for this endpoint.
status pending immediately after dispatch. Transitions to processing, then completed (or failed).
total_affiliates Snapshot of matched affiliate count at request time.
total_items total_affiliates × len(links). Used for progress reporting.
status_url Convenience path to poll for results.

Validation error — 422 Unprocessable Entity

Returned for any validation failure (rule violation, exceeded limit, no matching affiliates).

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The given data was invalid.",
    "details": {
      "links.0.coupon_code": [
        "Use coupon_code_prefix instead. coupon_code is unique per business and cannot be shared across affiliates."
      ]
    }
  }
}

Other 422 codes:

Code Meaning
NO_AFFILIATES No affiliates match the filter criteria.
LIMIT_EXCEEDED total_affiliates × len(links) exceeds the 50,000 hard limit.
VALIDATION_ERROR One or more fields failed validation. See details for per-field messages.

Server error — 500 Internal Server Error

{
  "success": false,
  "error": {
    "code": "SERVER_ERROR",
    "message": "Failed to queue bulk link creation."
  }
}

Polling for Status

Headers

Authorization: Bearer <access_token>

Scope: affiliates:read.

Response — 200 OK

While processing:

{
  "success": true,
  "data": {
    "batch_id": 1284,
    "scope": "all_affiliates",
    "status": "processing",
    "total_items": 624,
    "created_count": 412,
    "error_count": 0,
    "total_affiliates": 312,
    "processed_affiliates": 206,
    "created_at": "2026-05-15T10:42:18Z",
    "completed_at": null
  }
}

On completion:

{
  "success": true,
  "data": {
    "batch_id": 1284,
    "scope": "all_affiliates",
    "status": "completed",
    "total_items": 624,
    "created_count": 622,
    "error_count": 2,
    "total_affiliates": 312,
    "processed_affiliates": 312,
    "created_at": "2026-05-15T10:42:18Z",
    "completed_at": "2026-05-15T10:44:31Z",
    "results": [
      {
        "affiliate_id": 42,
        "created": [9011, 9012],
        "errors": []
      },
      {
        "affiliate_id": 43,
        "created": [9013],
        "errors": [
          {
            "index": 1,
            "destination_url": "https://example.com/products/gadget",
            "message": "The slug \"abc12345\" is already taken."
          }
        ]
      }
    ],
    "errors": null
  }
}

Status values

Status Meaning
pending Batch row created, fan-out job dispatched but not yet executed.
processing Fan-out completed; child jobs are running or queued.
completed All affiliates processed. created_count + error_count may be inspected per affiliate via results.
failed Fan-out step failed before children could be dispatched. errors contains the failure reason.

Polling guidelines

  • Poll no more than once every 2–5 seconds. The endpoint is rate-limited at 120 req/min within the affiliates:read group.
  • Use processed_affiliates / total_affiliates as a progress ratio.
  • A non-zero error_count does not imply batch failure — individual link errors are captured per affiliate without aborting the batch.

Not found — 404 Not Found

Returned if the batch ID does not exist or belongs to a different tenant.

{
  "success": false,
  "error": { "code": "NOT_FOUND", "message": "Batch not found." }
}

Worked Example

1. Submit batch

curl -X POST "https://api.heldsway.com/v1/links/bulk-all-affiliates" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "affiliate_filter": { "status": "active" },
    "links": [
      {
        "destination_url": "https://example.com/products/widget",
        "campaign": "spring-2026",
        "coupon_code_prefix": "SPRING",
        "coupon_type": "percentage",
        "coupon_value": 15.00
      },
      {
        "destination_url": "https://example.com/products/gadget",
        "campaign": "spring-2026"
      }
    ]
  }'

2. Response

{
  "success": true,
  "message": "Bulk creation queued for all affiliates.",
  "data": {
    "batch_id": 1284,
    "scope": "all_affiliates",
    "status": "pending",
    "total_affiliates": 312,
    "total_items": 624,
    "status_url": "/v1/link-batches/1284"
  }
}

3. Poll until complete

while true; do
  STATUS=$(curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
    "https://api.heldsway.com/v1/link-batches/1284" \
    | jq -r '.data.status')

  echo "Status: $STATUS"
  [[ "$STATUS" == "completed" || "$STATUS" == "failed" ]] && break
  sleep 3
done

4. Inspect per-affiliate results

curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://api.heldsway.com/v1/link-batches/1284" \
  | jq '.data.results[] | select(.errors | length > 0)'

Behavior Notes

Idempotency

  • Coupon resolution is idempotent: re-submitting the same coupon_code_prefix will reuse the existing per-affiliate DiscountCode rather than creating duplicates.
  • Link creation is not idempotent. Each submission produces a fresh set of referral links with newly generated slugs. Submitting the same payload twice doubles the number of links.

Tenant isolation

  • The endpoint always operates on the affiliates of the access token’s tenant.
  • Cross-tenant affiliate access is impossible: filters and queries are bound to business_id.

Partial success

  • A child job that fails to create one or more links does not abort the batch. Failures are captured in the per-affiliate errors[] array with index, destination_url, and message.
  • A child job that fails entirely (e.g. a database connection error) is recorded under that affiliate’s errors array as a single { "message": "Job failed: ..." } entry. The batch still progresses.

Concurrency

  • Child jobs run in parallel across queue workers.
  • Counter updates (created_count, error_count, processed_affiliates) use atomic SQL increments to remain consistent under parallel execution.
  • The first child to observe processed_affiliates >= total_affiliates transitions the batch to completed.

Limits

Limit Value Behavior on breach
Link templates per request 50 422 VALIDATION_ERROR
Total items (affiliates × templates) 50,000 422 LIMIT_EXCEEDED
Affiliate filter Active affiliates only Inactive/pending/suspended/terminated excluded

Comparison: Single-Affiliate vs All-Affiliates

Aspect Single affiliate (?bulk_create=true) All affiliates (this endpoint)
Path POST /v1/affiliates/{id}/links POST /v1/links/bulk-all-affiliates
Scope of effect One affiliate Many affiliates (filterable)
Allowed slug Yes (per link) No (auto-generated)
Coupon input Raw coupon_code Templated via coupon_code_prefix
Job topology Single worker job Fan-out + per-affiliate child jobs
Status endpoint GET /v1/affiliates/{id}/link-batches/{batchId} GET /v1/link-batches/{batchId}
Response code 202 Accepted 202 Accepted

The unified GET /v1/link-batches/{batchId} endpoint works for batches of both scopes.


Error Reference

HTTP Code Cause
401 UNAUTHENTICATED Missing or invalid bearer token
403 FORBIDDEN Token lacks affiliates:write scope
422 VALIDATION_ERROR One or more fields failed validation
422 NO_AFFILIATES Filter matched zero affiliates
422 LIMIT_EXCEEDED Batch size exceeds 50,000 items
429 RATE_LIMITED Exceeded 60 req/min on the write group
500 SERVER_ERROR Unexpected failure queueing the batch

Changelog

Date Change
2026-05-15 Initial release.