Coupon analytics metrics and definitions

How the three coupon analytics endpoints define KPIs, redeemer segments, retention, products, and usage limits — and how those definitions differ from the coupons-used-on-orders report.

Affiliate Command loads the Coupon analytics modal from three IAS v2 endpoints:

EndpointPurposeDate range
GET .../analyticsCoupon header, KPIs, usage limitQuery dateRange (default last_7_days)
GET .../analytics/redeemersNew / existing first use / returning + retentionLifetime — no date query params
GET .../analytics/productsPaginated Products tabSame as KPI endpoint

The UI does not recompute these numbers — it renders the API payload. Changing the modal date range refetches /analytics and /analytics/products only; /analytics/redeemers is loaded once per coupon (lifetime).

Authentication and access

  • Auth: Affiliate JWT (affiliateToken).
  • Roles: AFFILIATE_SUPER_ADMIN and AFFILIATE_ADMIN only on all four coupon report routes (the three per-coupon analytics routes and GET .../used-on-orders). AFFILIATE_AGENT is not allowed. Agents are not in the affiliate permission matrix, so they are not gated by requireModuleAccess either — the route role list is what blocks them.
  • Module: couponAnalytics with view on the three per-coupon analytics routes; full on GET .../used-on-orders.
  • 404: Coupon id not found, or coupon belongs to another affiliate.
  • 401 / 403: Missing/invalid token or insufficient role/module access.
  • 422 (INVALID_FIELD): Invalid dateRange preset; dateRange=custom without startDate/endDate; bad YYYY-MM-DD; startDate after endDate.

Date range (KPIs and products only)

Query parameter dateRange defaults to last_7_days when omitted.

Affiliate Command typically opens the modal with last_thirty_days and sends an explicit dateRange on fetch.

PresetNotes
last_7_daysRolling UTC calendar window (today plus the previous 6 days)
last_thirty_daysRolling UTC calendar window (today plus the previous 29 days). Resolved only by coupon analytics (resolveCouponAnalyticsPresetDateRange). This does not change the global last_thirty_days window used by other reports (about 31 days, ending one hour ago).
last_90_daysRolling UTC calendar window (today plus the previous 89 days). Coupon-analytics preset; also available on the shared date-range helper.
last_180_daysRolling UTC calendar window (today plus the previous 179 days). Coupon-analytics preset; also available on the shared date-range helper.
customRequires startDate and endDate as YYYY-MM-DD. End date inclusive through 23:59:59.999 UTC.

Range application: KPIs and products filter on redeem redeemedAt (appliedAt, else redeem createdAt). They do not filter on order.createdAt.

Resolved bounds are echoed on data.range (startDate, endDate, preset) for KPI and products responses.

Redeem rows (before order qualify)

All three endpoints start from affiliate-scoped coupon redeems that are:

  • status: APPLIED
  • deleted: false
  • Linked to a non-empty ecommerceOrder and patient (KPIs / products / redeemers pipelines)

Only redeems whose linked order qualifies (below) contribute to KPIs, products, redeemer segments, and retention.

Qualifying orders

A redemption counts toward analytics only when the linked ecommerce order qualifies. The same rules are implemented in buildCouponAnalyticsOrderQualifyExpr, buildCouponAnalyticsOrderQualifyStages, and buildCouponAnalyticsOrderLookupQualifyStages (KPIs, products, redeemers, retention, and coupons-used-on-orders).

Order shapeQualify rule
totalAmount > 0Order status processing, paid, or completed; payment intent id present; PI status succeeded, success, or settledSuccessfully.
totalAmount ≤ 0 (100% off / free checkout)Same statuses processing, paid, or completed (no PI required).
Partially refunded (status refunded)totalAmount - totalRefunded > 0 qualifies even when status is refunded (net revenue remains).
Fully refundedDoes not qualify (refunded with net ≤ 0, or other non-qualifying statuses such as canceled, pending, on_hold, etc.).

Orders in processing, paid, or completed with partial refunds before status moves to refunded still qualify on the paid branch; after qualify, revenue uses net totals (see below).

Revenue and refunds: After qualification, order totalAmount on the pipeline is max(totalAmount - totalRefunded, 0). KPI revenueCollected and product line allocation use that net order total.

Within the selected date range, one qualifying redemption per order (earliest redeemedAt per order) feeds KPI uses, revenue, and discount totals.

Compared with GET /v2/ecommerceReports/coupons/used-on-orders

Both join APPLIED redeems to orders with the same core qualify pipeline. Contract differences:

TopicPer-coupon analyticsCoupons used on orders
Redeem date filterredeemedAt for KPIs and productsredeemedAt on redeems; summary also uses order.createdAt
Primary metricsrevenueCollected, discountCost, usesPer-row revenueImpact (discount only)
ScopeSingle coupon; three endpointsAll coupons; CSV export
Redeemers / retentionSeparate lifetime /analytics/redeemersN/A
Usage limitLifetime usageLimit.used on /analytics (all APPLIED redeems, not qualify-gated)N/A

KPI endpoint (GET .../analytics)

data.kpis

FieldDefinitionUI label (recommended)
usesDistinct qualifying orders in range (deduped by order, earliest redeem)Redemptions
revenueCollectedSum of net order totals on those orders (post-discount, after refunds at order level)Revenue collected
discountCostSum of redeem.discountAmount on those ordersDiscount cost (subtext: given as discount)
spendingPerNewUserdiscountCost ÷ distinct patients with no prior affiliate order (any status) among qualifying redeems in range; 0 when denominator is 0Average discount cost per new customer — not customer spend

Two different “new vs returning” ideas (do not conflate):

  • New to the affiliate — drives spendingPerNewUser and the users.new vs users.existingFirstUse split. Implemented as any earlier affiliate ecommerce order for the patient (any order status), not “first qualifying order only.” Non-qualifying or unpaid checkouts still count as prior orders for this rule.
  • Returning on this coupon — users.returning only: patient has more than one qualifying deduped order on this coupon (lifetime). Independent of the affiliate new/existing split.

data.usageLimit

Lifetime only (not limited to the selected date range).

FieldDefinition
usedNon-deleted APPLIED redeems on this coupon (checkout enforcement count). Not limited to qualifying orders — can exceed KPI uses when redeems exist without a qualifying linked order.
cap / remaining / percentConsumedFrom coupon usageLimit; unlimited: true when cap is null or 0.

The analytics response does not include users, retention, or qualifiedUsed.

Redeemers endpoint (GET .../analytics/redeemers)

data.users (lifetime)

Each patient with at least one qualifying redeem on this coupon appears once. Segment assignment is per patient (lifetime qualifying orders, deduped by order):

SegmentAPI fieldDefinition
Newusers.newExactly one qualifying deduped order on this coupon, and no prior affiliate order (any status) at that first qualifying redeem.
Existing customers, first useusers.existingFirstUseExactly one qualifying deduped order on this coupon, with at least one prior affiliate order (any status) at that first qualifying redeem.
Returningusers.returningMore than one qualifying deduped order (lifetime) on this coupon for the patient.

Each bucket includes:

  • count — distinct patients in the segment.
  • pct — count ÷ totalDistinctRedeemers × 100.
  • redemptions — qualifying deduped orders (lifetime) for patients in that segment.
  • redemptionPct — redemptions ÷ totalRedemptions × 100 (use for relative performance bars on the Redeemers tab).

Also: totalDistinctRedeemers, totalRedemptions.

data.retention (new customers only)

Retention cohort = patients in the new segment at their first qualifying coupon redemption (lifetime). Returning and existing first use patients are excluded so blended retention is not inflated.

For each milestone day30, day60, day90, day180:

FieldMeaning
eligibleCohort members where firstRedeemedAt + N days ≤ now.
retainedEligible members with either (1) any later qualifying affiliate order (any coupon) with createdAt on or after the milestone, or (2) an affiliate subscription active at the milestone (active / on_hold, excluding pending / abandoned; start/end/cancel dates respected).
pctretained ÷ eligible × 100, or null when eligible is 0 (show — in UI).

Label retention in the UI as new customer retention.

Products endpoint (GET .../analytics/products)

Same qualify rules and redeemedAt date filter as KPIs. Default limit 5 (max 50). Paginate to list all products in range.

FieldDefinition
ordersDistinct qualifying orders containing the product (not unit count).
revenueNet line revenue: coupon discount allocated by line share of order product subtotal × net order total; null if any line lacks persisted pricePerUnitOverride.
sharePctrevenue ÷ highest product revenue in range × 100 (top product = 100% bar). 0 when the highest product revenue is 0. Not order share.

Sort (server): revenue descending, then orders descending.

Affiliate Command UI (integration notes)

AreaBehavior
Default tabProducts when the modal opens
Default rangelast_thirty_days on open (Command); API default if omitted is last_7_days
KPIsMap fields per table above; spendingPerNewUser copy must not imply customer spend
ProductsBars use sharePct; handle revenue: null
RedeemersThree segment rows including Existing customers, first use; bars use redemptionPct; retention pct: null → em dash
Usage limitHeader subtitle from usageLimit.used / cap (lifetime)

Response envelope

{
  "success": true,
  "data": { }
}

OpenAPI: CouponAnalyticsSuccessEnvelope, CouponAnalyticsRedeemersSuccessEnvelope, CouponAnalyticsProductsSuccessEnvelope.

Implementation reference in telegra-rest: src/ias/v2/ui/ecommerceReports/coupons/ (routes) and src/services/ecommerce/ecommerceReport/internal.js (qualify pipelines and aggregations).


Did this page help you?