Affiliates API
Manage affiliate registrations, profiles, lifecycle states (approve, suspend, reactivate, terminate), statistics, and exports.
Authentication & Scopes
All endpoints require a Bearer access token obtained via POST /v1/auth/token.
| Scope | Access |
|---|---|
affiliates:read |
List, view profile, view aggregate & individual stats, and export affiliates |
affiliates:write |
Create, update, delete, approve, suspend, reactivate, and terminate affiliates |
List Affiliates
Retrieve a paginated list of all affiliates for your business.
GET /v1/affiliates
Required scope: affiliates:read Rate limit: 120 requests/minute
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 |
Page number |
per_page |
integer | 25 |
Results per page. Max 100 |
status |
string | — | Filter by status: pending | active | suspended | terminated |
commission_type |
string | — | Filter by commission override: default (inherits business default) | custom (override set) |
search |
string | — | Search across name, email, and referral code |
sort |
string | created_at |
Sort field: created_at | referral_code | status | commission_rate |
direction |
string | desc |
Sort direction: asc | desc |
Example Request
curl "https://api.heldsway.com/api/v1/affiliates?status=active&per_page=10&sort=created_at&direction=desc" \
-H "Authorization: Bearer <access_token>"
Success Response — 200 OK
{
"success": true,
"message": "OK",
"data": {
"items": [
{
"id": 42,
"referral_code": "ABC12345",
"status": "active",
"commission_rate": "10.00",
"notes": null,
"approved_at": "2026-04-01T09:00:00.000000Z",
"suspended_at": null,
"terminated_at": null,
"created_at": "2026-03-15T14:22:00.000000Z",
"affiliate_user": {
"id": 7,
"name": "Jane Doe",
"email": "jane@example.com",
"phone": null
}
}
],
"meta": {
"current_page": 1,
"per_page": 10,
"total": 87,
"last_page": 9
}
}
}
Get Affiliate
Retrieve a single affiliate by ID.
GET /v1/affiliates/{id}
Required scope: affiliates:read Rate limit: 120 requests/minute
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Affiliate ID |
Example Request
curl "https://api.heldsway.com/api/v1/affiliates/42" \
-H "Authorization: Bearer <access_token>"
Success Response — 200 OK
{
"success": true,
"message": "OK",
"data": {
"id": 42,
"referral_code": "ABC12345",
"status": "active",
"commission_rate": "10.00",
"notes": "VIP partner",
"approved_at": "2026-04-01T09:00:00.000000Z",
"suspended_at": null,
"terminated_at": null,
"created_at": "2026-03-15T14:22:00.000000Z",
"affiliate_user": {
"id": 7,
"name": "Jane Doe",
"email": "jane@example.com",
"phone": "+14155552671"
},
"referral_links_count": 3
}
}
Error Responses
| Status | Code | Description |
|---|---|---|
404 |
NOT_FOUND |
Affiliate does not exist for this business |
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.heldsway.com/api/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.
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.heldsway.com/api/v1/affiliates/42/stats" \
-H "Authorization: Bearer <access_token>"
Example Request — Date-filtered stats
curl "https://api.heldsway.com/api/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 |
— | Invalid date format or date_to is before date_from. Form Request failure: Laravel’s standard validation body, no code (see Error Reference → Validation Errors (422)) |
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 |
commission_type |
string | No | default, custom |
Filter by commission type (default = business default, custom = override 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.heldsway.com/api/v1/affiliates/export" \
-H "Authorization: Bearer <access_token>" \
-o affiliates.csv
Example — Export only active affiliates
curl "https://api.heldsway.com/api/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.heldsway.com/api/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 |
— | Invalid ids format (each value must be a positive integer) or invalid filter values. Form Request failure: Laravel’s standard validation body, no code (see Error Reference → Validation Errors (422)) |
Create Affiliate
Create a new affiliate account directly. The affiliate is immediately set to active status — no approval step required. Use this when you are adding affiliates on their behalf (e.g., migrating from another system, programmatic onboarding).
If the email already exists as an affiliate user account (across any business), they will be linked to your business without changing their password. If no account exists, one is created with the provided credentials.
POST /v1/affiliates
Required scope: affiliates:write Rate limit: 60 requests/minute
Request Body
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
name |
string | Yes | max 255 | Affiliate’s full name |
email |
string | Yes | valid email, max 255 | Affiliate’s email address |
password |
string | Yes | min 8, max 255 | Account password |
phone |
string | No | max 30 | Phone number |
commission_type |
string | No | percentage | fixed |
Commission calculation mode. Defaults to percentage |
commission_rate |
float | No | 0–100 (percentage) or 0–999999.99 (fixed) | Override commission rate. Omit to use business default |
notes |
string | No | max 1000 | Internal notes |
referral_code |
string | No | alphanumeric, max 32, unique | Custom referral code. Auto-generated if omitted |
destination_url |
string | No | valid URL, max 2048 | Default landing page URL for this affiliate’s store link |
Example Request
curl -X POST "https://api.heldsway.com/api/v1/affiliates" \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Doe",
"email": "jane@example.com",
"password": "SecurePass123!",
"phone": "+14155552671",
"commission_rate": 12.5,
"notes": "Referred by our sales team",
"referral_code": "JANE2026"
}'
Success Response — 201 Created
{
"success": true,
"message": "Created",
"data": {
"id": 42,
"referral_code": "JANE2026",
"status": "active",
"commission_rate": "12.50",
"notes": "Referred by our sales team",
"approved_at": "2026-04-13T10:00:00.000000Z",
"suspended_at": null,
"terminated_at": null,
"created_at": "2026-04-13T10:00:00.000000Z",
"affiliate_user": {
"id": 7,
"name": "Jane Doe",
"email": "jane@example.com",
"phone": "+14155552671"
},
"referral_link": {
"id": 10,
"slug": "JANE2026",
"destination_url": "https://example.com/promo",
"tracking_url": "https://yourbusiness.heldsway.com/r/JANE2026",
"qr_code_url": "https://yourbusiness.heldsway.com/r/JANE2026/qr",
"qr_code_svg_url": "https://yourbusiness.heldsway.com/r/JANE2026/qr?format=svg"
}
}
}
Error Responses
| Status | Code | Description |
|---|---|---|
409 |
AFFILIATE_EXISTS |
This email is already an affiliate of your business |
422 |
— | Missing or invalid fields. Form Request failure: Laravel’s standard validation body, no code (see Error Reference → Validation Errors (422)) |
Affiliate Self-Registration
Allow an individual to register themselves as an affiliate for your business. This endpoint is intended for use in your own frontend — your backend calls our API with your access token, passing the user’s details.
Prerequisite: The business setting
allow_self_registrationmust be enabled. If it is off, this endpoint returns403. This setting is configured in your business dashboard under Affiliate Program → Settings.
Affiliates created via this endpoint start with **pending** status and must be approved by you before they can earn commissions.
POST /v1/affiliates/register
Required scope: affiliates:write Rate limit: 30 requests/minute
Request Body
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
name |
string | Yes | max 255 | Applicant’s full name |
email |
string | Yes | valid email, max 255 | Applicant’s email |
password |
string | Yes | min 8, max 255 | Account password they will use to log in |
phone |
string | No | max 30 | Phone number |
Example Request
curl -X POST "https://api.heldsway.com/api/v1/affiliates/register" \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "John Smith",
"email": "john@example.com",
"password": "MyPassword123!"
}'
Success Response — 201 Created
{
"success": true,
"message": "Registration submitted. Pending approval.",
"data": {
"id": 55,
"referral_code": "XK9P2QR7",
"status": "pending",
"commission_rate": null,
"notes": null,
"approved_at": null,
"suspended_at": null,
"terminated_at": null,
"created_at": "2026-04-13T11:30:00.000000Z",
"affiliate_user": {
"id": 12,
"name": "John Smith",
"email": "john@example.com",
"phone": null
}
}
}
Error Responses
| Status | Code | Description |
|---|---|---|
403 |
REGISTRATION_DISABLED |
allow_self_registration is not enabled for this business |
409 |
AFFILIATE_EXISTS |
This email is already an affiliate of your business |
422 |
— | Missing or invalid fields. Form Request failure: Laravel’s standard validation body, no code (see Error Reference → Validation Errors (422)) |
Update Affiliate
Update editable fields on an existing affiliate. Only include fields you want to change — all fields are optional.
PATCH /v1/affiliates/{id}
Required scope: affiliates:write Rate limit: 60 requests/minute
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Affiliate ID |
Request Body
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
commission_type |
string | No | percentage | fixed |
Commission calculation mode |
commission_rate |
float|null | No | 0–100 (percentage) or 0–999999.99 (fixed) | Override rate. Send null to revert to business default |
notes |
string|null | No | max 1000 | Internal notes |
destination_url |
string|null | No | valid URL, max 2048 | Update affiliate’s default storefront destination URL |
Example Request
curl -X PATCH "https://api.heldsway.com/api/v1/affiliates/42" \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{
"commission_rate": 15.0,
"notes": "Upgraded to premium tier"
}'
Success Response — 200 OK
Returns the updated Affiliate Object.
Error Responses
| Status | Code | Description |
|---|---|---|
404 |
NOT_FOUND |
Affiliate does not exist for this business |
422 |
— | Invalid field values. Form Request failure: Laravel’s standard validation body, no code (see Error Reference → Validation Errors (422)) |
Delete Affiliate
Soft-delete an affiliate from your business. This removes the affiliate record but does not delete their user account — they may still be an affiliate for other businesses. Historical conversion and click data is preserved.
DELETE /v1/affiliates/{id}
Required scope: affiliates:write Rate limit: 60 requests/minute
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Affiliate ID |
Example Request
curl -X DELETE "https://api.heldsway.com/api/v1/affiliates/42" \
-H "Authorization: Bearer <access_token>"
Success Response — 204 No Content
Empty body.
Error Responses
| Status | Code | Description |
|---|---|---|
404 |
NOT_FOUND |
Affiliate does not exist for this business |
Approve Affiliate
Approve a pending affiliate, moving them to active status and allowing them to start earning commissions.
POST /v1/affiliates/{id}/approve
Required scope: affiliates:write Rate limit: 60 requests/minute
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Affiliate ID |
Example Request
curl -X POST "https://api.heldsway.com/api/v1/affiliates/55/approve" \
-H "Authorization: Bearer <access_token>"
Success Response — 200 OK
{
"success": true,
"message": "Affiliate approved.",
"data": {
"id": 55,
"status": "active",
"approved_at": "2026-04-13T12:00:00.000000Z",
...
}
}
Error Responses
| Status | Code | Description |
|---|---|---|
404 |
NOT_FOUND |
Affiliate not found |
409 |
INVALID_STATUS |
Affiliate is not in pending status |
Suspend Affiliate
Suspend an active or pending affiliate. Suspended affiliates cannot earn new commissions. Existing approved commissions are not affected.
POST /v1/affiliates/{id}/suspend
Required scope: affiliates:write Rate limit: 60 requests/minute
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Affiliate ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | No | Internal reason for suspension (max 500 chars). Appended to notes. |
Example Request
curl -X POST "https://api.heldsway.com/api/v1/affiliates/42/suspend" \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{ "reason": "Suspected fraudulent activity under review." }'
Success Response — 200 OK
{
"success": true,
"message": "Affiliate suspended.",
"data": {
"id": 42,
"status": "suspended",
"suspended_at": "2026-04-13T12:30:00.000000Z",
...
}
}
Error Responses
| Status | Code | Description |
|---|---|---|
404 |
NOT_FOUND |
Affiliate not found |
409 |
INVALID_STATUS |
Affiliate is already suspended or terminated |
Reactivate Affiliate
Reactivate a suspended affiliate, restoring them to active status.
POST /v1/affiliates/{id}/reactivate
Required scope: affiliates:write Rate limit: 60 requests/minute
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Affiliate ID |
Example Request
curl -X POST "https://api.heldsway.com/api/v1/affiliates/42/reactivate" \
-H "Authorization: Bearer <access_token>"
Success Response — 200 OK
{
"success": true,
"message": "Affiliate reactivated.",
"data": {
"id": 42,
"status": "active",
"suspended_at": null,
...
}
}
Error Responses
| Status | Code | Description |
|---|---|---|
404 |
NOT_FOUND |
Affiliate not found |
409 |
INVALID_STATUS |
Affiliate is not suspended |
Terminate Affiliate
Permanently terminate an affiliate. This is irreversible — terminated affiliates cannot be reactivated. Use suspend if you may want to reinstate them later.
POST /v1/affiliates/{id}/terminate
Required scope: affiliates:write Rate limit: 60 requests/minute
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Affiliate ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | No | Reason for termination (max 500 chars). Appended to notes. |
Example Request
curl -X POST "https://api.heldsway.com/api/v1/affiliates/42/terminate" \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{ "reason": "Violated affiliate terms of service." }'
Success Response — 200 OK
{
"success": true,
"message": "Affiliate terminated.",
"data": {
"id": 42,
"status": "terminated",
"terminated_at": "2026-04-13T13:00:00.000000Z",
...
}
}
Error Responses
| Status | Code | Description |
|---|---|---|
404 |
NOT_FOUND |
Affiliate not found |
409 |
INVALID_STATUS |
Affiliate is already terminated |