Analytics
Overview, sales, redemption, and fraud insights for the merchant dashboard.
Analytics endpoints power the dashboard's Statistics, Sales, and
Redemptions insight views. All four require an authenticated merchant
session with the portal.analytics permission, checked inline (the same
pattern as Merchant orders, not via
requireBffMerchantAuth), so error responses use the bare
{ "ok": false, "error": "..." } shape.
Shared query parameter
All four endpoints accept the same time-range parameter:
| Param | Type | Default | Notes |
|---|---|---|---|
days | number | 14 | Must be one of 7, 14, 30, 90. Any other value (including missing or invalid) falls back to 14. |
Each endpoint compares the selected period against the immediately
preceding period of equal length (for example, with days=14, "current"
is the last 14 days and "previous" is the 14 days before that), and returns
a changePct or changePp (percentage-point) figure per KPI.
Sales and Redemptions insights are mutually exclusive per merchant
A merchant's settlementMode (SETTLEMENT_ON_SALE or
SETTLEMENT_ON_REDEMPTION) determines which of the Sales or Redemptions
insight endpoints is available, reflecting whether the merchant settles on
sale or on redemption. A merchant with no settlementMode set sees both.
Calling the disabled one returns a 403, not empty data. The Overview and
Fraud endpoints are not gated by settlementMode.
Overview
GET /api/bff/analytics/overviewSession-and-order traffic overview: unique sessions, orders placed, redemption attempts, visit-to-order conversion, a daily session/order series, embed mode and entry point splits, and a fraud pass/review/block breakdown.
Response
{
"ok": true,
"data": {
"periodDays": 14,
"kpis": {
"uniqueSessions": { "value": 0, "changePct": 0 },
"ordersPlaced": { "value": 0, "changePct": 0 },
"redemptionAttempts": { "value": 0, "changePct": 0 },
"visitToOrderPct": { "valuePct": null, "changePp": 0 }
},
"series": [
{ "dateKey": "2026-06-13", "dateLabel": "13 Jun", "sessions": 0, "orders": 0 }
],
"embedSplit": [{ "name": "...", "count": 0 }],
"entrySplit": [{ "name": "...", "count": 0 }],
"fraudBreakdown": [
{ "name": "Passed", "value": 0, "color": "#4b9349" },
{ "name": "Review", "value": 0, "color": "#f59e0b" },
{ "name": "Blocked", "value": 0, "color": "#ef4444" }
]
}
}series has one entry per day in the selected range, oldest first, even for
days with no activity. embedSplit/entrySplit are sorted descending by
count and capped at 8 entries. visitToOrderPct.valuePct is null when
there were no sessions to divide by.
Errors
| Status | Cause |
|---|---|
| 401 | No session. |
| 404 | Session user has no associated merchant. |
| 403 | Session user's role lacks the portal.analytics permission. |
| 500 | Unexpected internal error. |
Sales
GET /api/bff/analytics/salesOrder volume and revenue: orders placed, completed orders, gross revenue, average completed order value, a daily series, status and payment-status breakdowns, and the 10 most recent orders.
Response
{
"ok": true,
"data": {
"periodDays": 14,
"currency": "EUR",
"kpis": {
"ordersPlaced": { "value": 0, "changePct": 0 },
"completed": { "value": 0, "changePct": 0 },
"grossRevenueMinor": { "value": 0, "changePct": 0 },
"avgCompletedMinor": { "value": null, "changePct": 0 }
},
"series": [
{ "dateKey": "2026-06-13", "dateLabel": "13 Jun", "total": 0, "completed": 0, "revenueMinor": 0 }
],
"statusBreakdown": [{ "name": "completed", "count": 0, "valueMinor": 0 }],
"paymentBreakdown": [{ "name": "payment capture succeeded", "count": 0, "valueMinor": 0 }],
"recent": [
{
"id": "...",
"occurredAt": "2026-06-26T00:00:00.000Z",
"orderRef": "Order …abc123",
"status": "COMPLETED",
"paymentLabel": "payment capture succeeded",
"amountMinor": 2500,
"currency": "EUR"
}
]
}
}currency falls back to the merchant's defaultCurrency, then USD, if an
order has no currency of its own. statusBreakdown/paymentBreakdown
labels are the raw enum value lowercased with underscores replaced by
spaces; both are capped at 10 entries, sorted descending by count.
grossRevenueMinor and avgCompletedMinor only count COMPLETED orders.
Errors
| Status | Cause |
|---|---|
| 401 | No session. |
| 404 | Session user has no associated merchant. |
| 403 | Sales insights are disabled for this merchant's settlement mode, or the session user's role lacks the portal.analytics permission. |
| 500 | Unexpected internal error. |
Redemptions
GET /api/bff/analytics/redemptionsRedemption activity: attempts, successes, failures, total and average redeemed amount, a daily series, top retail stores and redemption sources by value, and the 10 most recent redemptions.
Response
{
"ok": true,
"data": {
"periodDays": 14,
"currency": "EUR",
"kpis": {
"totalAttempts": { "value": 0, "changePct": 0 },
"succeeded": { "value": 0, "changePct": 0 },
"failed": { "value": 0, "changePct": 0 },
"totalRedeemedMinor": { "value": 0, "changePct": 0 },
"avgRedeemMinor": { "value": null, "changePct": 0 }
},
"series": [
{ "dateKey": "2026-06-13", "dateLabel": "13 Jun", "total": 0, "succeeded": 0, "amountSucceededMinor": 0 }
],
"topStores": [{ "name": "Unspecified location", "count": 0, "valueMinor": 0 }],
"sourceSplit": [{ "name": "POS", "count": 0, "valueMinor": 0 }],
"recent": [
{
"id": "...",
"occurredAt": "2026-06-26T00:00:00.000Z",
"status": "SUCCEEDED",
"sourceLabel": "POS",
"storeLabel": "Unspecified location",
"amountMinor": 1500,
"currency": "EUR",
"cardRef": "Card …1234"
}
]
}
}topStores is capped at 8, ordered by redeemed value descending, only
counting successful redemptions; a redemption with no store name is
labelled "Unspecified location". sourceSplit is capped at 8, ordered by
value descending; an empty source is labelled "Other". cardRef masks
the card identifier to its last 4 characters (preferring the card network's
own code, falling back to the QR code), or "Card …****" if too short to
mask meaningfully.
Errors
| Status | Cause |
|---|---|
| 401 | No session. |
| 404 | Session user has no associated merchant. |
| 403 | Redemption insights are disabled for this merchant's settlement mode, or the session user's role lacks the portal.analytics permission. |
| 500 | Unexpected internal error. |
Fraud
GET /api/bff/analytics/fraudFraud screening summary: blocked count, under-review count, pass rate, a pass/review/blocked percentage breakdown, and the 8 most recent fraud screening events with plain-language reasons.
Not gated by settlement mode
Unlike Sales and Redemptions, this endpoint is available regardless of the
merchant's settlementMode.
Response
{
"ok": true,
"data": {
"periodDays": 14,
"kpis": {
"blocked": { "value": 0, "changePct": 0 },
"underReview": { "value": 0, "changePct": 0 },
"passRate": { "valuePct": null, "changePp": 0 }
},
"breakdown": [
{ "name": "Passed", "value": 0, "color": "#4b9349" },
{ "name": "Review", "value": 0, "color": "#f59e0b" },
{ "name": "Blocked", "value": 0, "color": "#ef4444" }
],
"recentEvents": [
{
"id": "...",
"occurredAt": "2026-06-26T00:00:00.000Z",
"orderRef": "Order …abc123",
"action": "Passed",
"reason": "Cleared by automated screening",
"amountLabel": "EUR 25.00"
}
]
}
}passRate.valuePct only counts orders that reached a screening outcome
(excludes orders with fraudStatus: NOT_CHECKED) and is null if none did.
reason is intentionally plain-language and never echoes the raw fraud
provider's decision token. recentEvents is capped at 8, most recent first.
Errors
| Status | Cause |
|---|---|
| 401 | No session. |
| 404 | Session user has no associated merchant. |
| 403 | Session user's role lacks the portal.analytics permission. |
| 500 | Unexpected internal error. |