Analytics Module — Admin surface
Admin-facing HTTP surface for precomputed platform-wide dashboard snapshots (GMV, orders, commission & payouts, vendor leaderboard, customers, inventory KPIs for today / 7d / 30d / all-time) and a live SSE platform paid-order feed.
Admin-facing HTTP surface for precomputed platform-wide dashboard snapshots (GMV, orders, commission & payouts, vendor leaderboard, customers, inventory KPIs) and a live SSE paid-order feed for the whole platform. The dashboard is computed every 10 minutes by a background worker and served directly from a snapshot table — it is never computed on request. The SSE stream is a long-lived connection that emits a baseline count on connect and increments as new paid orders arrive anywhere on the platform.
This is the platform-wide counterpart to the per-vendor Analytics — Vendor surface. Where the vendor surface scopes every metric to one vendor, the admin surface aggregates across all vendors and adds admin-only metrics (vendor leaderboard, platform commission, payouts, customers).
Source:
api-modules/admin-analytics/src/controllers/admin-analytics.controller.ts,api-modules/admin-analytics/src/controllers/admin-live-orders.controller.ts.
Conventions
Authentication & authorization
Both endpoints require a Better-Auth admin bearer session and the analytics:view permission.
Authorization: Bearer <session-token>Guarded by BetterAuthGuard + PermissionsGuard with @RequirePermissions({ analytics: ["view"] }). The analytics resource is granted to the built-in superAdmin and admin roles; narrower dynamic roles must be granted analytics:view explicitly. A session without the permission is rejected with 403 Forbidden (FORBIDDEN).
There is no tenant scoping — every metric spans the entire platform.
Response envelope (dashboard)
{
"data": { /* dashboard payload */ },
"message": "Success",
"statusCode": 200
}SSE stream (live/orders)
The SSE endpoint is not wrapped by the standard response envelope. It is a raw text/event-stream connection; each frame is a data: line containing a JSON object.
Error envelope
statusCode | errorCode examples |
|---|---|
| 400 | BAD_REQUEST, VALIDATION_ERROR |
| 401 | UNAUTHORIZED |
| 403 | FORBIDDEN (missing analytics:view) |
| 500 | INTERNAL_SERVER_ERROR |
Money + dates
All monetary fields are integer subunits (paise / cents / eurocents — single currency per tenant). Date/time fields are ISO 8601 strings (computedAt, at, bucket).
Endpoints
GET /admin/analytics/dashboard
Returns the precomputed platform analytics snapshot for the selected time window.
The snapshot is computed every 10 minutes by a background BullMQ worker (admin-analytics-refresh job). This read never recalculates on request — it always returns the latest stored snapshot. To force a fresh recompute without waiting for the next 10-minute tick, use POST /admin/analytics/refresh (below), then re-read this endpoint. Before the worker has run for the first time, every metric is zeroed and computedAt is null.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
window | "today" | "7d" | "30d" | "all_time" | "30d" | The analytics time window to serve |
Response fields (data object)
| Field | Type | Description |
|---|---|---|
window | string | The window requested |
computedAt | string | null | ISO datetime of the last snapshot computation, or null if never computed |
grossSales | int (subunits) | Platform GMV — total order grand-total in the window (includes shipping + tax) |
ordersCount | int | Total orders placed in the window |
unitsSold | int | Total line-item units sold |
aov | int (subunits) | Average order value (grossSales / ordersCount, 0 when ordersCount = 0) |
refundsAmount | int (subunits) | Total value of refunds processed |
deliveredOrders | int | Sub-orders in delivered status |
cancelledOrders | int | Sub-orders in cancelled status |
avgFulfillSeconds | int | Average seconds from order placed to fulfilled (across vendors) |
avgDeliverSeconds | int | Average seconds from fulfilled to delivered (across vendors) |
commissionEarned | int (subunits) | Platform commission earned (ledger sale entries) |
vendorNetEarnings | int (subunits) | Vendor net earnings accrued (ledger sale + refund) |
payoutsPaid | int (subunits) | Total paid out to vendors (payouts marked paid in the window) |
vendorsTotal | int | Total registered vendors |
vendorsActive | int | Vendors with at least one sub-order in the window |
customersTotal | int | Total registered (non-admin, non-anonymous) customers |
newCustomers | int | Customers created within the window |
returningCustomers | int | Distinct in-window buyers who also have an order before the window start |
productsTotal | int | Total products platform-wide (excludes soft-deleted) |
productsActive | int | Active products platform-wide |
productsDraft | int | Draft products platform-wide |
productsArchived | int | Archived products platform-wide |
skusLowStock | int | SKUs below their low-stock threshold |
skusOutOfStock | int | SKUs with zero available inventory |
inventoryUnitsOnHand | int | Total on-hand units across all SKUs |
pendingActionCount | int | Sub-orders awaiting vendor action (pending status) |
salesTrend | TrendBucket[] | Revenue + order count over time (see below) |
statusBreakdown | StatusBreakdown | Sub-order counts by status (see below) |
topVendorsByRevenue | TopVendor[] (max 5) | Top 5 vendors ranked by revenue |
topVendorsByOrders | TopVendor[] (max 5) | Top 5 vendors ranked by order count |
topProductsByRevenue | TopProduct[] (max 5) | Top 5 SKUs ranked by revenue |
topProductsByUnits | TopProduct[] (max 5) | Top 5 SKUs ranked by units sold |
lowStockList | LowStockItem[] (max 20) | Up to 20 SKUs closest to or past their low-stock threshold |
deltas | Deltas | null | Period-over-period changes; null for all_time (no previous period) |
TrendBucket
Bucket granularity depends on the window:
today→ hourly buckets7d,30d→ daily bucketsall_time→ monthly buckets
{
bucket: string; // ISO datetime (start of the bucket)
revenue: number; // integer subunits
orders: number;
}StatusBreakdown
{
pending: number;
fulfilled: number;
delivered: number;
cancelled: number;
returned: number;
}TopVendor
{
vendorId: string;
name: string;
revenue: number; // integer subunits
orders: number;
}TopProduct
{
variantId: string;
productId: string | null;
name: string;
sku: string;
units: number;
revenue: number; // integer subunits
}LowStockItem
{
variantId: string;
sku: string;
name: string;
onHand: number;
threshold: number | null;
}Deltas
Holds period-over-period comparison for four headline KPIs. Each delta has an absolute value (in the same unit as the metric) and a pct (percentage change, or null if the previous period had a zero baseline so percentage is undefined). The deltas field itself is null for the all_time window because there is no defined previous period.
{
grossSales: { absolute: number; pct: number | null };
ordersCount: { absolute: number; pct: number | null };
unitsSold: { absolute: number; pct: number | null };
commissionEarned: { absolute: number; pct: number | null };
}Example response
// GET /admin/analytics/dashboard?window=7d
{
"data": {
"window": "7d",
"computedAt": "2026-06-08T09:40:00.000Z",
"grossSales": 84200000,
"ordersCount": 612,
"unitsSold": 1043,
"aov": 137581,
"refundsAmount": 1250000,
"deliveredOrders": 470,
"cancelledOrders": 33,
"avgFulfillSeconds": 16200,
"avgDeliverSeconds": 169200,
"commissionEarned": 12630000,
"vendorNetEarnings": 71570000,
"payoutsPaid": 58000000,
"vendorsTotal": 38,
"vendorsActive": 24,
"customersTotal": 5821,
"newCustomers": 142,
"returningCustomers": 318,
"productsTotal": 1987,
"productsActive": 1840,
"productsDraft": 96,
"productsArchived": 51,
"skusLowStock": 73,
"skusOutOfStock": 22,
"inventoryUnitsOnHand": 41280,
"pendingActionCount": 109,
"salesTrend": [
{ "bucket": "2026-06-02T00:00:00.000Z", "revenue": 11200000, "orders": 82 },
{ "bucket": "2026-06-03T00:00:00.000Z", "revenue": 12450000, "orders": 90 },
{ "bucket": "2026-06-04T00:00:00.000Z", "revenue": 13900000, "orders": 101 },
{ "bucket": "2026-06-05T00:00:00.000Z", "revenue": 12800000, "orders": 95 },
{ "bucket": "2026-06-06T00:00:00.000Z", "revenue": 13100000, "orders": 96 },
{ "bucket": "2026-06-07T00:00:00.000Z", "revenue": 11550000, "orders": 86 },
{ "bucket": "2026-06-08T00:00:00.000Z", "revenue": 9200000, "orders": 62 }
],
"statusBreakdown": {
"pending": 109,
"fulfilled": 0,
"delivered": 470,
"cancelled": 33,
"returned": 0
},
"topVendorsByRevenue": [
{ "vendorId": "ven_01", "name": "GlowLabs", "revenue": 18400000, "orders": 132 },
{ "vendorId": "ven_02", "name": "DermaCo", "revenue": 14100000, "orders": 98 },
{ "vendorId": "ven_03", "name": "PureSkin", "revenue": 11900000, "orders": 87 },
{ "vendorId": "ven_04", "name": "AuraBeauty", "revenue": 9700000, "orders": 71 },
{ "vendorId": "ven_05", "name": "NudeEssentials", "revenue": 8200000, "orders": 64 }
],
"topVendorsByOrders": [
{ "vendorId": "ven_01", "name": "GlowLabs", "revenue": 18400000, "orders": 132 },
{ "vendorId": "ven_02", "name": "DermaCo", "revenue": 14100000, "orders": 98 },
{ "vendorId": "ven_03", "name": "PureSkin", "revenue": 11900000, "orders": 87 },
{ "vendorId": "ven_04", "name": "AuraBeauty", "revenue": 9700000, "orders": 71 },
{ "vendorId": "ven_05", "name": "NudeEssentials", "revenue": 8200000, "orders": 64 }
],
"topProductsByRevenue": [
{ "variantId": "var_01", "productId": "prod_01", "name": "Vitamin C Serum 30ml", "sku": "VCS-30", "units": 220, "revenue": 6600000 }
],
"topProductsByUnits": [
{ "variantId": "var_03", "productId": "prod_01", "name": "Vitamin C Serum 15ml", "sku": "VCS-15", "units": 410, "revenue": 2870000 }
],
"lowStockList": [
{ "variantId": "var_07", "sku": "MZ-CLN-100", "name": "Micellar Cleanser 100ml", "onHand": 3, "threshold": 10 },
{ "variantId": "var_08", "sku": "RET-EYE", "name": "Retinol Eye Serum", "onHand": 0, "threshold": 5 }
],
"deltas": {
"grossSales": { "absolute": 5400000, "pct": 6.85 },
"ordersCount": { "absolute": 41, "pct": 7.18 },
"unitsSold": { "absolute": 63, "pct": 6.43 },
"commissionEarned": { "absolute": 810000, "pct": 6.85 }
}
},
"message": "Success",
"statusCode": 200
}POST /admin/analytics/refresh
Forces an immediate recompute of every snapshot window instead of waiting for the scheduled 10-minute tick.
Requires @RequirePermissions({ analytics: ["refresh"] }) — granted to the built-in superAdmin and admin roles alongside analytics:view.
The recompute runs in the background on the worker, not in this request. The handler enqueues a one-off admin-analytics-refresh job (fixed jobId admin-analytics-refresh-manual, so rapid repeat calls dedup to a single in-flight run) onto the same admin-analytics BullMQ queue the cron uses, then returns immediately. Poll GET /admin/analytics/dashboard afterwards — once the worker finishes, every window's computedAt advances.
No request body.
Response 200 — { "data": { "enqueued": true }, "message": "Success", "statusCode": 200 }.
| Status | Code | When |
|---|---|---|
| 403 | FORBIDDEN | Session lacks analytics:refresh |
GET /admin/analytics/live/orders (Server-Sent Events)
Opens a long-lived SSE connection (text/event-stream) that streams real-time paid-order activity across the whole platform.
This endpoint is not wrapped in the standard response envelope — the response body is a raw event stream.
Behaviour
| Event | When emitted | Payload |
|---|---|---|
| Immediately on (re)connect | baseline snapshot of today's platform-wide paid-order count | snapshot frame |
| On each newly-paid order anywhere on the platform | incremented live count | order.paid frame |
| Every 30 seconds | keep-alive | heartbeat frame |
Self-healing counter: on every connect (including EventSource auto-reconnects after a dropped connection), the controller re-baselines the count from a fresh DB query (COUNT(order WHERE payment_status='paid' AND paid_at >= start-of-day)). A missed event corrects itself within one reconnect cycle — the count never permanently drifts.
Frame shapes
All frames arrive as data: <JSON>\n\n lines.
snapshot — emitted once immediately on connect:
{
"type": "snapshot",
"paidOrdersToday": 184,
"at": "2026-06-08T09:41:05.123Z"
}order.paid — emitted for each newly-paid order on the platform:
{
"type": "order.paid",
"paidOrdersToday": 185,
"orderNumber": "ORD-2026-00892",
"grandTotal": 249900,
"at": "2026-06-08T09:53:28.447Z"
}grandTotal is the order's total in integer subunits.
heartbeat — emitted every 30 seconds:
{
"type": "heartbeat",
"at": "2026-06-08T10:00:05.001Z"
}Example: curl
curl -N \
-H "Authorization: Bearer <session-token>" \
-H "Accept: text/event-stream" \
https://api.example.com/admin/analytics/live/ordersAffiliate Module — Admin
HTTP surface for platform staff managing the affiliate plugin: review applications, manage affiliates (incl. suspend/resume + per-affiliate commission overrides), configure…
Audit Module — Admin
Unified audit log capturing every domain event (attributed to the triggering actor via request-context propagation) plus eventless HTTP mutations, enriched (geo + device) and stored in ClickHouse via a dedicated event bus and a separately-deployable worker. Filterable, paginated admin read API.