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>
Referral Links
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 Commission Rules for Link
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_countis 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) |
Referral Link Endpoints
| 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
cancelledstatus 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. Returns0.0when 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§ions=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
sectionsis specified, only the requested sections appear - Unknown section keys in the
sectionsparameter are silently ignored - Payout timestamps use
YYYY-MM-DD HH:MM:SSformat (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_atis 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.
Get Link Stats
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_clicksis derived from the denormalizedclick_countfield 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.
totalequals 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
idsis provided, the endpoint returns exactly those affiliates (scoped to your business) regardless of anystatusorsearchparameters. 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) |
| 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_earnedincludes commissions in all statuses (pending, approved, rejected, cancelled).pending_payoutonly 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 |
Bulk Referral Link Creation — All Affiliates
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 | 1 – 50 |
| 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
GET /v1/link-batches/{batchId}
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:readgroup. - Use
processed_affiliates / total_affiliatesas a progress ratio. - A non-zero
error_countdoes 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_prefixwill reuse the existing per-affiliateDiscountCoderather 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 withindex,destination_url, andmessage. - A child job that fails entirely (e.g. a database connection error) is recorded under that affiliate’s
errorsarray 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_affiliatestransitions the batch tocompleted.
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. |