Reports & Analytics API
Generate aggregate performance statistics, commission time-series charts, top-earner leaderboards, campaign distribution, payout summaries, and detailed coupon redemption reports.
Authentication & Scopes
All endpoints require a Bearer access token with the reports:read scope. See Authentication.
Authorization: Bearer <access_token>
Endpoints Summary
Core Performance Reports
| Method | Path | Scope | Throttle | Description |
|---|---|---|---|---|
GET |
/v1/reports/stats |
reports:read |
120/min | Summary stat cards with period-over-period trend percentages |
GET |
/v1/reports/commission-chart |
reports:read |
120/min | Daily/weekly/monthly commission time-series chart |
GET |
/v1/reports/top-affiliates |
reports:read |
120/min | Top-performing affiliates ranked by earnings |
GET |
/v1/reports/clicks-by-campaign |
reports:read |
120/min | Traffic distribution grouped by campaign tag |
GET |
/v1/reports/payouts |
reports:read |
120/min | Payout history report |
GET |
/v1/reports/export |
reports:read |
30/min | Export performance metrics as CSV |
Coupon Performance Reports
| Method | Path | Scope | Throttle | Description |
|---|---|---|---|---|
GET |
/v1/reports/coupons/stats |
reports:read |
120/min | Coupon summary metrics with period trends |
GET |
/v1/reports/coupons/redemptions-chart |
reports:read |
120/min | Daily coupon redemptions chart data |
GET |
/v1/reports/coupons/top |
reports:read |
120/min | Top coupons ranked by revenue |
GET |
/v1/reports/coupons/performance |
reports:read |
120/min | Paginated coupon performance breakdown |
GET |
/v1/reports/coupons/{id}/conversions |
reports:read |
120/min | Conversions generated by a specific coupon |
GET |
/v1/reports/coupons/links |
reports:read |
120/min | Referral links attached to coupons |
GET |
/v1/reports/coupons/export |
reports:read |
30/min | Export coupon report as CSV |
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.
All endpoints require a Bearer access token with the appropriate scope. See Prerequisites & Authentication.
| 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/coupons/stats |
reports:read |
120/min | Coupon headline stats with trends |
GET |
/v1/reports/coupons/redemptions-chart |
reports:read |
120/min | Daily coupon redemptions |
GET |
/v1/reports/coupons/top |
reports:read |
120/min | Top coupons by revenue |
GET |
/v1/reports/coupons/performance |
reports:read |
120/min | Coupon performance table (paginated) |
GET |
/v1/reports/coupons/{id}/conversions |
reports:read |
120/min | Latest conversions for one coupon |
GET |
/v1/reports/coupons/links |
reports:read |
120/min | Referral links with a coupon attached |
GET |
/v1/reports/coupons/export |
reports:read |
30/min | CSV export of the coupon performance table |
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) |
location_id |
string | No | — | max:64 | Filter metrics by store location ID |
source_domain |
string | No | — | max:255 | Filter metrics by origin storefront domain |
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.heldsway.com/api/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",
"location_id": null,
"source_domain": null
}
}
}
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 | Attributed conversions (every status except cancelled; rejected and $0 orders count) ÷ sessions × 100, rounded to 1 decimal, where sessions is the same session count as total_referral_clicks below. Can read above 100% if a session converts more than once in range |
total_referral_clicks |
integer | Distinct visiting sessions in the date range (MetricsService::sessions()), not a raw click count despite the name. Counted from tracking_pageviews distinct sessions first, falling back to a distinct-session count from tracking_clicks, then to a raw tracking_clicks row count only when neither table has session-tagged rows in 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.heldsway.com/api/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.heldsway.com/api/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",
"location_id": null,
"source_domain": null
}
}
}
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 | Conversions attributed to this affiliate in the range, every status including cancelled, $0 orders included |
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.heldsway.com/api/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",
"location_id": null,
"source_domain": null
}
}
}
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.heldsway.com/api/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.heldsway.com/api/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 |
402 |
SUBSCRIPTION_REQUIRED |
The business has no active subscription (see Error Reference → Subscription Required (402)) |
403 |
FORBIDDEN |
Token does not have reports:read scope |
403 |
FEATURE_NOT_AVAILABLE |
GET /v1/reports/export and GET /v1/reports/coupons/export only — the plan does not include CSV export |
422 |
— | Invalid query parameters (bad date format, invalid granularity, etc.). Report endpoints validate with Form Requests, so the body is Laravel’s standard validation response with no code |
429 |
RATE_LIMIT_EXCEEDED |
Rate limit exceeded |
500 |
REPORT_ERROR |
Internal error during report generation |
Error Response Format
A validation failure returns Laravel’s standard validation body:
{
"message": "The selected granularity is invalid.",
"errors": {
"granularity": ["The selected granularity is invalid."]
}
}
Every other error uses the standard envelope (see Error Reference in the Affiliate Management API section).
Rate Limiting
| Endpoint Group | Limit |
|---|---|
| Read endpoints (stats, chart, tables, payouts, coupon reports) | 120 requests/min |
Export (/v1/reports/export, /v1/reports/coupons/export) |
30 requests/min |
Exceeding the limit returns HTTP 429 Too Many Requests with error code RATE_LIMIT_EXCEEDED.
Coupon Reports
Coupon (discount code) performance for the business: headline stats, a daily redemptions chart, top coupons by revenue, a paginated performance table, per-coupon conversion drill-down, coupon-linked referral links, and a CSV export. Source: Modules\AffiliateSupport\Http\Controllers\Api\CouponReportApiController, backed by ReportingService.
Every coupon report endpoint accepts the date range below; the other parameters are listed per endpoint.
| Parameter | Type | Required | Default | Constraints | Description |
|---|---|---|---|---|---|
from |
date | No | 30 days ago | YYYY-MM-DD, must be <= to |
Start of the range (inclusive, from 00:00:00) |
to |
date | No | today | YYYY-MM-DD, must be >= from |
End of the range (inclusive, to 23:59:59) |
A redemption is a conversion of this business that has a discount code, is not cancelled, and has converted_at inside the range ($0 orders count). Gross sales add each conversion’s subtotal, or its order_total when subtotal is 0 or empty.
Coupon Stats
Headline coupon metrics with trends against the previous period of equal length (source: CouponReportApiController::stats()).
GET /v1/reports/coupons/stats
Required scope: reports:read Rate limit: 120 requests/minute
Query Parameters
from, to only (rules from CouponReportStatsRequest::rules()).
Example Request
curl "https://api.heldsway.com/api/v1/reports/coupons/stats?from=2026-03-17&to=2026-04-16" \
-H "Authorization: Bearer <access_token>"
Success Response — 200 OK
{
"success": true,
"message": "Coupon report statistics retrieved.",
"data": {
"total_redemptions": 12,
"gross_sales": 1840.5,
"net_revenue": 1650.25,
"total_discount": 190.25,
"total_commission": 184.05,
"avg_order_value": 153.38,
"conversion_rate": 40,
"trends": {
"total_redemptions": 20,
"gross_sales": 12.4,
"net_revenue": 10.1,
"total_discount": 35.2,
"total_commission": 12.4,
"avg_order_value": -6.3,
"conversion_rate": 100
},
"filters": { "from": "2026-03-17", "to": "2026-04-16" }
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
total_redemptions |
integer | Number of redemptions in the range |
gross_sales |
number | Gross sales of those redemptions |
total_discount |
number | Sum of discount_applied |
net_revenue |
number | gross_sales − total_discount |
total_commission |
number | Sum of commission_amount |
avg_order_value |
number | gross_sales ÷ total_redemptions (0 when there are none) |
conversion_rate |
number | Redemptions as a percentage of all the business’s non-cancelled conversions in the range, coupon or not |
trends.* |
number | Percentage change of each metric against the previous period, rounded to 1 decimal. When the previous value is 0: 100 if the current value is above 0, else 0 |
filters.from, filters.to |
string | Applied range |
Error Responses
| Status | Code | Description |
|---|---|---|
422 |
— | Bad date format or from after to. Form Request failure: Laravel’s standard validation body, no code |
500 |
REPORT_ERROR |
Failed to fetch coupon report statistics |
Coupon Redemptions Chart
Daily count of redemptions across the range (source: CouponReportApiController::redemptionsChart()).
GET /v1/reports/coupons/redemptions-chart
Required scope: reports:read Rate limit: 120 requests/minute
Query Parameters
from, to only (rules from CouponReportStatsRequest::rules()).
Example Request
curl "https://api.heldsway.com/api/v1/reports/coupons/redemptions-chart?from=2026-04-01&to=2026-04-03" \
-H "Authorization: Bearer <access_token>"
Success Response — 200 OK
{
"success": true,
"message": "Coupon redemptions chart data retrieved.",
"data": {
"labels": ["Apr 01", "Apr 02", "Apr 03"],
"data": [2, 0, 5],
"filters": { "from": "2026-04-01", "to": "2026-04-03" }
}
}
labels has one M d entry for every day in the range; days without redemptions have 0 in data.
Error Responses
| Status | Code | Description |
|---|---|---|
422 |
— | Bad date format or from after to. Form Request failure: Laravel’s standard validation body, no code |
500 |
REPORT_ERROR |
Failed to fetch coupon redemptions chart data |
Top Coupons
Coupons ranked by revenue in the range (source: CouponReportApiController::top()).
GET /v1/reports/coupons/top
Required scope: reports:read Rate limit: 120 requests/minute
Query Parameters
Rules from CouponReportTopRequest::rules(), plus from / to.
| Parameter | Type | Required | Default | Constraints | Description |
|---|---|---|---|---|---|
limit |
integer | No | 10 | 1–50 | Number of coupons to return |
Example Request
curl "https://api.heldsway.com/api/v1/reports/coupons/top?limit=3" \
-H "Authorization: Bearer <access_token>"
Success Response — 200 OK
{
"success": true,
"message": "Top coupons by revenue retrieved.",
"data": {
"labels": ["SUMMER20", "JANE15", "WELCOME10"],
"data": [980.5, 540, 320.75],
"filters": { "from": "2026-03-17", "to": "2026-04-16" }
}
}
labels[i] is a coupon code and data[i] the revenue of its redemptions — per order, subtotal (or order_total when subtotal is 0 or empty) minus discount_applied, never below 0 per order — ordered by revenue descending.
Error Responses
| Status | Code | Description |
|---|---|---|
422 |
— | Bad date or limit outside 1–50. Form Request failure: Laravel’s standard validation body, no code |
500 |
REPORT_ERROR |
Failed to fetch top coupons |
Coupon Performance
Paginated table of the business’s discount codes (soft-deleted codes excluded) with their redemption totals in the range (source: CouponReportApiController::performance()). Only redemptions attributed to an affiliate are counted here; codes with no redemptions are listed with zeros.
GET /v1/reports/coupons/performance
Required scope: reports:read Rate limit: 120 requests/minute
Query Parameters
Rules from CouponReportPerformanceRequest::rules(), plus from / to.
| Parameter | Type | Required | Default | Constraints | Description |
|---|---|---|---|---|---|
status |
string | No | — | active or expired |
active: active and not past expires_at. expired: inactive, or past expires_at |
affiliate_ids[] |
integer[] | No | — | integers | Only codes bound to these affiliates |
search |
string | No | — | max 100 | Case-insensitive substring match on the code |
sort_by |
string | No | gross_sales |
redemptions, gross_sales, discount_given, commission, first_used, last_used, dc.code |
Sort column (dc.code sorts by code) |
sort_dir |
string | No | desc |
asc or desc |
Sort direction |
per_page |
integer | No | 25 | 1–100 | Page size |
page |
integer | No | 1 | ≥ 1 | Page number |
Example Request
curl "https://api.heldsway.com/api/v1/reports/coupons/performance?status=active&sort_by=redemptions&per_page=10" \
-H "Authorization: Bearer <access_token>"
Success Response — 200 OK
{
"success": true,
"message": "Coupon performance table retrieved.",
"data": {
"items": [
{
"id": 57,
"code": "JANE15",
"affiliate_name": "Jane Smith",
"affiliate_code": "JANE2026",
"type": "percentage",
"value": "15.00",
"redemptions": 4,
"gross_sales": 540,
"discount_given": 81,
"net_revenue": 459,
"commission": 68.85,
"aov": 135,
"first_used": "2026-03-20 14:03:11",
"last_used": "2026-04-12 09:41:02",
"is_active": true,
"expires_at": null
}
],
"meta": { "current_page": 1, "per_page": 10, "total": 1, "last_page": 1 },
"filters": { "from": "2026-03-17", "to": "2026-04-16" }
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
items[].id, code, type, value, is_active, expires_at |
mixed | The discount code’s own columns (value is the stored decimal string; expires_at a database timestamp string or null) |
items[].affiliate_name, affiliate_code |
string|null | Name and referral code of the affiliate the code is bound to |
items[].redemptions |
integer | Redemptions in the range |
items[].gross_sales, discount_given, net_revenue, commission |
number | Totals in the range; net_revenue = gross_sales − discount_given |
items[].aov |
number | gross_sales ÷ redemptions (0 when there are none) |
items[].first_used, last_used |
string|null | First and last converted_at in the range, as database timestamp strings |
meta |
object | Pagination: current_page, per_page, total, last_page |
Error Responses
| Status | Code | Description |
|---|---|---|
422 |
— | Invalid filter, sort or pagination value. Form Request failure: Laravel’s standard validation body, no code |
500 |
REPORT_ERROR |
Failed to fetch coupon performance table |
Coupon Conversions (Drill-down)
The latest 100 conversions that used one coupon (source: CouponReportApiController::drillDown()). Only conversions of this business that are attributed to an affiliate, not cancelled, and fall inside the range are returned, newest first ($0 orders included). An unknown coupon ID returns an empty list, not 404.
GET /v1/reports/coupons/{id}/conversions
Required scope: reports:read Rate limit: 120 requests/minute
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
integer | Discount code ID (items[].id from Coupon Performance) |
Query Parameters
from, to only (rules from CouponReportStatsRequest::rules()).
Example Request
curl "https://api.heldsway.com/api/v1/reports/coupons/57/conversions" \
-H "Authorization: Bearer <access_token>"
Success Response — 200 OK
{
"success": true,
"message": "Coupon drill-down data retrieved.",
"data": {
"conversions": [
{
"order_id": "ORD-10492",
"subtotal": 150,
"discount_applied": 22.5,
"commission_amount": 19.13,
"status": "approved",
"converted_at": "2026-04-12T09:41:02.000000Z",
"affiliate_code": "JANE2026"
}
],
"filters": { "from": "2026-03-17", "to": "2026-04-16" }
}
}
subtotal falls back to order_total when the conversion has no subtotal. status is a conversion status value (see module-8 §9.3 enums).
Error Responses
| Status | Code | Description |
|---|---|---|
422 |
— | Bad date format or from after to. Form Request failure: Laravel’s standard validation body, no code |
500 |
REPORT_ERROR |
Failed to fetch coupon drill-down data |
Coupon-Linked Referral Links
Referral links that have a discount code attached, ranked by clicks in the range (source: CouponReportApiController::links() → ReportingService::couponLinksPerformance()).
GET /v1/reports/coupons/links
Required scope: reports:read Rate limit: 120 requests/minute
Query Parameters
Rules from CouponReportLinksRequest::rules(), plus from / to.
| Parameter | Type | Required | Default | Constraints | Description |
|---|---|---|---|---|---|
discount_code_id |
integer | No | — | ≥ 1 | Only links carrying this discount code |
affiliate_id |
integer | No | — | ≥ 1 | Only links of this affiliate |
limit |
integer | No | 20 | 1–100 | Number of links to return |
Example Request
curl "https://api.heldsway.com/api/v1/reports/coupons/links?discount_code_id=57" \
-H "Authorization: Bearer <access_token>"
Success Response — 200 OK
{
"success": true,
"message": "Coupon-linked referral links retrieved.",
"data": {
"items": [
{
"slug": "jane-promo",
"destination_url": "https://example.com/promo",
"campaign": "Spring 2026",
"product_id": null,
"product_name": null,
"affiliate_code": "JANE2026",
"discount_code": { "id": 57, "code": "JANE15", "type": "percentage", "value": "15.00" },
"clicks": 40,
"conversions": 4,
"conversion_rate": 10,
"revenue": 540,
"commission": 68.85
}
],
"filters": { "from": "2026-03-17", "to": "2026-04-16" }
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
items[].clicks |
integer | Distinct clicks on the link that landed in the range |
items[].conversions |
integer | Distinct conversions from those clicks that are attributed and not cancelled |
items[].conversion_rate |
number | conversions ÷ clicks × 100, rounded to 1 decimal |
items[].revenue |
number | Revenue of those conversions: per order, subtotal (or order_total when subtotal is 0 or empty) minus discount_applied, never below 0 per order |
items[].commission |
number | The stored commission_amount when positive; otherwise an estimate from the link’s best active commission rule, then the affiliate’s rate, then the business default rate. Suppressed commissions count as 0 |
A query failure is logged and returns 200 with an empty items array.
Error Responses
| Status | Code | Description |
|---|---|---|
422 |
— | Invalid date, ID or limit. Form Request failure: Laravel’s standard validation body, no code |
Export Coupon Report
Download the coupon performance table as CSV (source: CouponReportApiController::export()). It applies the same filters as Coupon Performance but ignores sort_by, sort_dir, per_page and page: rows are ordered by gross sales descending, capped at 10,000.
GET /v1/reports/coupons/export
Required scope: reports:read Rate limit: 30 requests/minute
Also requires the real-time-dashboard-csv-export plan feature.
Query Parameters
Same as Coupon Performance (CouponReportPerformanceRequest); only from, to, status, affiliate_ids[] and search affect the output.
Example Request
curl "https://api.heldsway.com/api/v1/reports/coupons/export?status=active" \
-H "Authorization: Bearer <access_token>" \
-o coupon-reports.csv
Success Response — 200 OK
Content-Type: text/csv Content-Disposition: attachment; filename="coupon-reports-2026-04-16.csv"
Code,Affiliate,Type,Value,Redemptions,"Gross Sales","Discount Given","Net Revenue",Commission,AOV,"First Used","Last Used",Status
JANE15,"Jane Smith",percentage,15.00,4,540,81,459,68.85,135,2026-03-20T14:03:11Z,2026-04-12T09:41:02Z,Active
First Used and Last Used are the first and last converted_at in the range, in UTC, as ISO 8601 with a Z suffix (empty when the code has no redemptions). Status is Active or Inactive from the code’s is_active flag only; an active code past its expiry still exports as Active.
Error Responses
| Status | Code | Description |
|---|---|---|
403 |
FEATURE_NOT_AVAILABLE |
The plan does not include real-time-dashboard-csv-export |
422 |
— | Invalid filter value. Form Request failure: Laravel’s standard validation body, no code |
The export has no error handler of its own: a failure while building the rows returns Laravel’s generic 500 JSON body.