Reports & Analytics API Reference
Complete API contracts for Reports & Analytics, including routes, auth, DTOs, responses, errors, examples, and integration notes.
Reports & Analytics - API Reference
Audience: Frontend engineers (admin panel), backend engineers, QA, and API consumers.
Scope: The 19 admin-only routes under /api/admin/analytics/ — dashboards, KPIs, trends, the report registry, exports, and the operator sync surface.
1. Documentation Evidence
| Area | Files Inspected | What Was Verified |
|---|---|---|
| Controllers | apps/api/src/modules/analytics/admin/{dashboard,reports,exports,sync}/*.controller.ts | Routes, methods, guards, permissions, rate limits |
| DTOs | admin/dto/*.ts | Period/channel, filters-as-JSON, export request |
| Services | shared/*.service.ts, shared/reports/*.ts, shared/sync/*.ts | Behavior, freshness, registry, exports, triggers |
| Schema | packages/mongodb/src/schemas/analytics/*.schema.ts | Collections, fields, retention |
| Jobs | packages/jobs/src/index.ts | Queue, job kinds, payloads |
| Existing docs | consumer-handoff.md, Fumadocs formats | Frozen contract and format baseline |
2. Module Summary
| Field | Value |
|---|---|
| Module name | analytics |
| Module slug | analytics |
| Primary actors | admin (reader), admin (operator) |
| API surfaces | admin only — nothing customer-facing |
| Base route prefixes | /api/admin/analytics/ |
| Auth model | JwtAuthGuard + RoleGuard + Analytics_* / Reports*_READ permissions |
| Persistence | MongoDB (facts, rollups, state, exports); reads PostgreSQL via the ETL |
| Runtime source of truth | MongoDB; PostgreSQL is the ultimate source |
| Sibling docs | Backend, Features and flows |
3. Concepts and Terminology
| Term | Meaning | Source File | Used By |
|---|---|---|---|
dataAsOf | When the underlying data was last known current — the OLDEST contributing stream's lastSuccessAt | analytics-freshness.service.ts | Every response |
stalenessSeconds | Seconds between dataAsOf and now | same | Every response |
behind | A contributing stream stopped on its page budget; figures valid but incomplete | same | Every response |
truncatedForFairness | The comparison window was cut to the same elapsed length (7th of month → 1–7 of previous month) | analytics-period.service.ts | KPIs |
pointInTime | Report describes NOW, not the period; meta.period is null | report-registry.types.ts | Reports |
dateKey | YYYY-MM-DD in Asia/Kathmandu — never UTC-bucket client-side | period util | Every response |
idempotencyKey | Required on exports; unique per admin | analytics-export.service.ts | Exports |
truncated (export) | The 50k-row cap bit; also written into the file | analytics-export-builder.service.ts | Exports |
driftCount | Documents the last reconcile had to repair — non-zero means the incremental path missed rows | analytics-sync-state.service.ts | Sync |
parent_driven | order_lines has no watermark of its own — the orders stream drives it | analytics.constants.ts | Sync |
4. API Surface Map
| Surface | Method | Path | Actor | Auth/Guard | Permission | Controller | Purpose |
|---|---|---|---|---|---|---|---|
| Admin | GET | /api/admin/analytics/dashboard | Reader | JWT + Role | Analytics_READ | Dashboard | Rollup-backed headline tiles |
| Admin | GET | /api/admin/analytics/kpis | Reader | JWT + Role | Analytics_READ | Dashboard | KPIs with period comparison |
| Admin | GET | /api/admin/analytics/trends | Reader | JWT + Role | Analytics_READ | Dashboard | Day-by-day series |
| Admin | GET | /api/admin/analytics/funnel | Reader | JWT + Role | Analytics_READ | Dashboard | Conversion funnel (starts at cart) |
| Admin | GET | /api/admin/analytics/channels | Reader | JWT + Role | Analytics_READ | Dashboard | Channel split |
| Admin | GET | /api/admin/analytics/payment-methods | Reader | JWT + Role | Analytics_READ | Dashboard | Payment-method mix |
| Admin | GET | /api/admin/analytics/top-products | Reader | JWT + Role | Analytics_READ | Dashboard | Top products by units/revenue |
| Admin | GET | /api/admin/analytics/geography | Reader | JWT + Role | Analytics_READ | Dashboard | Revenue/orders by district |
| Admin | GET | /api/admin/analytics/reviews | Reader | JWT + Role | Analytics_READ | Dashboard | Rating distribution, moderation count |
| Admin | GET | /api/admin/analytics/reports | Reader | JWT + Role | Analytics_READ | Reports | The caller's report descriptors |
| Admin | GET | /api/admin/analytics/reports/{reportId} | Reader | JWT + Role | Analytics_READ + report's | Reports | Run one registry report |
| Admin | POST | /api/admin/analytics/exports | Reader | JWT + Role | Analytics_READ + report's | Exports | Queue an export |
| Admin | GET | /api/admin/analytics/exports | Reader | JWT + Role | Analytics_READ | Exports | Own exports, newest first |
| Admin | GET | /api/admin/analytics/exports/{exportId} | Reader | JWT + Role | Analytics_READ | Exports | Poll one export |
| Admin | GET | /api/admin/analytics/exports/{exportId}/download | Reader | JWT + Role | Analytics_READ + report's (live) | Exports | Download the file |
| Admin | GET | /api/admin/analytics/sync | Reader | JWT + Role | Analytics_READ | Sync | Stream states + pending backlog |
| Admin | POST | /api/admin/analytics/sync/refresh | Operator | JWT + Role | Analytics_UPDATE | Sync | Force a sync now |
| Admin | POST | /api/admin/analytics/sync/backfill | Operator | JWT + Role | Analytics_UPDATE | Sync | Resumable full rebuild |
| Admin | POST | /api/admin/analytics/sync/reconcile | Operator | JWT + Role | Analytics_UPDATE | Sync | Re-project a window, report drift |
5. Auth, Identity, and Permissions
| Surface | Guard/Decorator | Identity Shape | Permission | Guest Allowed | Notes |
|---|---|---|---|---|---|
| Dashboard/reports/exports reads | JwtAuthGuard, RoleGuard | req.user | Analytics_READ | No | Reports/exports additionally require the report's own Reports*_READ |
| Sync triggers | JwtAuthGuard, RoleGuard | req.user.id | Analytics_UPDATE | No | First non-_READ action the module uses |
- Ten permission modules:
Analytics+ReportsSales·ReportsOrders·ReportsProducts·ReportsCustomers·ReportsPayments·ReportsPromotions·ReportsInventory·ReportsReviews·ReportsPos._CREATE/_DELETE/_RESTOREunused (catalogue cross product). - Rate limits per admin account (never per IP): reads
ADMIN_ANALYTICS_READ60/min; export submissionADMIN_ANALYTICS_EXPORT5/hour; sync triggersADMIN_HEAVY_OP20/min. - No id enumeration: an unpermitted report is the same 404 as a nonexistent one; another admin's export is the same 404 as a missing one.
6. DTO and Model Reference
6.1 AnalyticsPeriodQueryDto — every analytics/report/export request
| Field | Type | Required | Default | Validation | Notes |
|---|---|---|---|---|---|
period | string | No | last_30_days | @IsIn(ANALYTICS_PERIOD_PRESETS) | today · yesterday · last_7_days · last_30_days · this_month · previous_month · this_quarter · previous_quarter · this_year · previous_year · custom |
from | string | custom only | — | @IsString | YYYY-MM-DD, required when period=custom |
to | string | custom only | — | @IsString | YYYY-MM-DD, required when period=custom |
channel | string | No | — | @IsIn(ANALYTICS_CHANNELS) | online/pos; omit for the platform total |
6.2 AnalyticsRankedQueryDto — rankings
Extends the period DTO with limit (int ≥1, default 10) and, for top-products, sortBy (units/revenue).
6.3 ReportRunQueryDto — GET /reports/{reportId}
| Field | Type | Required | Default | Validation | Notes |
|---|---|---|---|---|---|
| (period DTO) | — | No | last_30_days | — | See §6.1 |
filters | string (JSON) | No | — | @IsString | One JSON parameter, not loose keys — the global pipe runs forbidNonWhitelisted. Validated against the report's own allowlist |
sortBy | string | No | report's defaultSort.by | @IsString | Must be in the report's sortable |
sortDirection | string | No | report's default | @IsIn(["asc","desc"]) | — |
limit | number | No | 50 | int 1–500 | — |
offset | number | No | 0 | int ≥0 | — |
6.4 RequestReportExportDto — POST /exports
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
reportId | string | Yes | @IsString | Must exist AND be permitted |
format | string | Yes | @IsIn(["csv","xlsx"]) | — |
| (period DTO) | — | Yes | — | Stored with the request |
filters | string (JSON) | No | @IsString | Same allowlist semantics as running |
sortBy / sortDirection | string | No | as §6.3 | Stored with the request |
idempotencyKey | string | Yes | @IsString | Unique per admin; double-click returns the existing export |
6.5 ListReportExportsDto — GET /exports
page (int ≥1, default 1), size (int 1–500, default 20).
6.6 AnalyticsSyncDto — the three trigger routes
RefreshSyncDto: { stream? } (string, optional — omit for all). BackfillSyncDto:
{ stream } (required). ReconcileSyncDto: { stream, windowDays? } (window default 30).
6.7 The meta envelope — on every data response
{
"dataAsOf": "2026-08-07T10:42:10.000Z",
"stalenessSeconds": 96,
"behind": false,
"period": { "preset": "this_month", "from": "2026-08-01", "to": "2026-08-07",
"dayCount": 7, "timeZone": "Asia/Kathmandu" },
"filters": { "channel": "pos" },
"generatedAt": "2026-08-07T10:43:46.000Z",
"durationMs": 41
}KPIs add comparison: { ...previous window..., truncatedForFairness: true }. The dashboard
summary replaces period with windows: { today, weekToDate, monthToDate }. Reports add
report, sort, paging, and period: null for point-in-time reports.
7. Enum Reference
| Enum | Value | Meaning | Runtime Effect | Source |
|---|---|---|---|---|
ANALYTICS_PERIOD_PRESETS | 11 presets (§6.1) | Window selectors | Resolved in Asia/Kathmandu | period.util.ts |
ANALYTICS_CHANNELS | online / pos | Sales channel | Filters the rollup group | analytics.shared.ts |
| Export status | queued / processing / ready / failed / expired | Export lifecycle | Drives polling and download | export schema |
| Sync status | idle / running / failed / backfilling | Stream state | Drives GET /sync rendering | sync-state schema |
ANALYTICS_PAYMENT_METHODS | platform methods | Payment splits | Grouped in rollups | analytics.shared.ts |
8. Endpoint Reference
8.1 GET /api/admin/analytics/dashboard
Purpose
The headline tiles: revenue/orders/customers/payments today, week-to-date and month-to-date, stock counts, pending moderation, average order value. Rollup-backed — three bounded reads over at most ~60 documents.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ· Rate limit: 60/min per admin
Request
| Part | Required | Details |
|---|---|---|
| Query | No | none — fixed windows (today / last 7 days / this month) |
Response
200 — data: revenue { today, weekToDate, monthToDate, todayVsYesterdayPercent? },
orders, customers, payments, inventory { lowStockCount, outOfStockCount },
reviews { awaitingModeration }, averageOrderValue. meta.windows names the three windows.
todayVsYesterdayPercent is null when yesterday had no revenue.
Side Effects
None — reads daily rollups + product facts (stock counts).
Error Cases
| HTTP | Code | Condition |
|---|---|---|
| 503 | ANALYTICS_NOT_YET_SYNCED | First sync not complete |
8.2 GET /api/admin/analytics/kpis
Purpose
KPIs for an arbitrary period with a like-for-like comparison: netRevenue, grossRevenue,
orders, completedOrders, cancelledOrders, unitsSold, newCustomers,
averageOrderValue, failedPayments, checkoutConversionPercent (null when nothing
started).
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ· Rate limit: 60/min
Request
| Part | Required | Details |
|---|---|---|
| Query | No | period, from/to, channel (§6.1) |
Response
200 — each metric is { value, previous, changePercent } (changePercent null when previous
was 0). meta.comparison.truncatedForFairness says the previous window was cut to the same
elapsed length.
Side Effects
None.
Error Cases
| HTTP | Code | Condition |
|---|---|---|
| 503 | ANALYTICS_NOT_YET_SYNCED | Never synced |
| 400 | ANALYTICS_INVALID_PERIOD | Bad custom window |
| 400 | ANALYTICS_PERIOD_TOO_LONG | Over 3 years |
8.3 GET /api/admin/analytics/trends
Purpose
Day-by-day series for charting: date, netRevenue, grossRevenue, orders,
completedOrders, unitsSold, newCustomers — one row per dateKey.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ· Rate limit: 60/min
Side Effects
None. Freshness from the orders + customers streams.
8.4 GET /api/admin/analytics/funnel
Purpose
The conversion funnel: carts_created → checkouts_started → payment_started →
payment_succeeded → orders_completed, with pairwise percentages, abandonedCheckouts,
and unavailableStages naming product_viewed as unavailable (no view counter exists in
PostgreSQL).
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ· Rate limit: 60/min
Side Effects
None. Cart counts are platform-wide and attributed to online — a cart has no channel
until a checkout session is created from it.
8.5 GET /api/admin/analytics/channels
Purpose
Revenue and orders split by channel, so online and POS stay comparable without double
counting — a counter sale is one order with channel: "pos", not a second record.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ· Rate limit: 60/min
8.6 GET /api/admin/analytics/payment-methods
Purpose
Payment-method distribution over the period ({ method, count }[]), from the rollup's map
field.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ· Rate limit: 60/min
8.7 GET /api/admin/analytics/top-products
Purpose
Top products by units or revenue over the period. Reads order-line facts directly (not
day-shaped), bounded by limit. Excludes cancelled orders.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ· Rate limit: 60/min
Request
| Part | Required | Details |
|---|---|---|
| Query | No | period, channel, limit (default 10), sortBy (units/revenue) |
8.8 GET /api/admin/analytics/geography
Purpose
Revenue, orders and customer count by district. Grouped by district id, never name — a province rename must not split history.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ· Rate limit: 60/min
8.9 GET /api/admin/analytics/reviews
Purpose
Rating distribution (1–5, zero-filled) over the period and the pending-moderation count.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ· Rate limit: 60/min
8.10 GET /api/admin/analytics/reports
Purpose
The report registry, filtered to what the caller may run — descriptors with id, title,
description, category, permission, pointInTime, columns, filters, sortable,
defaultSort. Build the report picker from this response; never hard-code the list.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ· Rate limit: 60/min
Response
200 — { items: [descriptor...] }, only reports whose Reports*_READ the caller holds.
Side Effects
None.
8.11 GET /api/admin/analytics/reports/{reportId}
Purpose
Run one registry report. Example:
GET /api/admin/analytics/reports/product_sales
?period=last_30_days
&filters={"brandId":4,"channel":"pos"}
&sortBy=revenue&sortDirection=desc
&limit=50&offset=0Filters travel as one URL-encoded JSON object — the global pipe runs
forbidNonWhitelisted, and the filter set differs per report. An unsupported key is refused
by name, never silently ignored.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ+ the report's own · Rate limit: 60/min
Response
200 — data: { items, total, meta: { report, dataAsOf, stalenessSeconds, behind, period (null when pointInTime), filters, sort, paging, generatedAt, durationMs } }.
Error Cases
| HTTP | Code | Condition |
|---|---|---|
| 404 | ANALYTICS_REPORT_NOT_FOUND | No such report, OR not permitted — deliberately indistinguishable |
| 400 | ANALYTICS_INVALID_PERIOD / ANALYTICS_PERIOD_TOO_LONG | Bad window |
| 400 | ANALYTICS_FILTER_NOT_SUPPORTED | Filter key not on this report, or bad filters JSON |
| 400 | ANALYTICS_SORT_NOT_SUPPORTED | sortBy not sortable |
| 503 | ANALYTICS_NOT_YET_SYNCED | Never synced |
8.12 POST /api/admin/analytics/exports
Purpose
Queue a report export — asynchronous by design; a year of orders can aggregate for minutes.
Returns immediately; poll GET /exports/{exportId} until ready, then follow downloadUrl.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ+ the report's own · Rate limit: 5/hour per admin
Request
{
"reportId": "sales_summary_by_day",
"format": "xlsx",
"period": "previous_month",
"filters": "{\"channel\":\"pos\"}",
"idempotencyKey": "sales-2026-07-a1b2c3"
}Response
200 — { exportId, reportType, format, status: "queued", truncated: false, expiresAt, downloadUrl: null }.
Side Effects
Export record (Mongo) written FIRST, then analytics.build_export queued (job id = export
id). Permission checked before anything is written.
Error Cases
| HTTP | Code | Condition |
|---|---|---|
| 404 | ANALYTICS_REPORT_NOT_FOUND | No such report, or not permitted |
| 400 | ANALYTICS_INVALID_PERIOD / ANALYTICS_PERIOD_TOO_LONG | Bad window |
Edge Cases
- Same
idempotencyKeyagain → the existing export is returned, no second aggregation. - Rate limit 5/hour — tighter than reads: each submission spawns an aggregation and writes a file.
8.13 GET /api/admin/analytics/exports
Purpose
The requesting admin's own exports, newest first, paginated.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ· Rate limit: 60/min
8.14 GET /api/admin/analytics/exports/{exportId}
Purpose
Poll one export. Scoped to its requester — another admin's export is the same 404 as a missing one.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ· Rate limit: 60/min
Response
200 — { exportId, reportType, format, status, requestedAt, startedAt?, completedAt?, rowCount?, truncated, fileSizeBytes?, expiresAt, error?, downloadUrl (only when ready) }.
downloadUrl is the API route, never a storage key.
Error Cases
| HTTP | Code | Condition |
|---|---|---|
| 404 | ANALYTICS_EXPORT_NOT_FOUND | No such export, or not yours |
8.15 GET /api/admin/analytics/exports/{exportId}/download
Purpose
Download the finished file. Re-checks the report's permission against the caller's LIVE role — queueing an export before a role change must not preserve access.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ+ the report's own (live) · Rate limit: 60/min
Error Cases
| HTTP | Code | Condition |
|---|---|---|
| 404 | ANALYTICS_EXPORT_NOT_FOUND | Missing, not yours, or permission revoked (same 404 as the report check) |
| 400 | ANALYTICS_EXPORT_NOT_READY | Still queued/building |
| 400 | ANALYTICS_EXPORT_FAILED | The build failed; error carries the reason |
| 410 | ANALYTICS_EXPORT_EXPIRED | The file was swept after 48 hours |
8.16 GET /api/admin/analytics/sync
Purpose
The operator freshness surface: every stream's state plus the pending-work backlog, with a
rolled-up healthy. Three fields carry the diagnosis: consecutiveFailures (5 in a row =
stopped pipeline), behind (page budget), driftCount (reconcile repairs).
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_READ· Rate limit: 60/min
Response
200 — { healthy, streams: [{ stream, status, parentDriven, watermarkAt, lastSuccessAt, lastDurationMs, lastDocumentsWritten, consecutiveFailures, behind, lastReconciledAt, driftCount, backfillCursor?, backfillCompletedAt?, lastError?, lockedUntil? }], pendingWork: { count, oldestAt } }. status: "parent_driven" on order_lines is normal.
8.17 POST /api/admin/analytics/sync/refresh
Purpose
Force the sync now — body { "stream": "orders" } or {} to sync everything in dependency
order. Returns as soon as the job is queued; poll GET /sync and watch lastSuccessAt
move.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_UPDATE· Rate limit: 20/min
Response
200 — { queued: true, stream: "orders" | null, correlationId }.
Error Cases
| HTTP | Code | Condition |
|---|---|---|
| 409 | ANALYTICS_SYNC_ALREADY_RUNNING | A run is in progress — an answer, not a failure |
| 404 | ANALYTICS_STREAM_NOT_FOUND | Unknown stream name (message lists valid ones) |
8.18 POST /api/admin/analytics/sync/backfill
Purpose
Rebuild a stream from the beginning. Resumable — each job walks a slice and records where it reached, so pressing it twice continues rather than restarts. Nothing is deleted — projections are idempotent upserts, so this rewrites documents in place. It is the repair for drift, not a destructive operation, but it is expensive — not something to run routinely.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_UPDATE· Rate limit: 20/min
Error Cases
| HTTP | Code | Condition |
|---|---|---|
| 404 | ANALYTICS_STREAM_NOT_FOUND | Unknown stream name |
8.19 POST /api/admin/analytics/sync/reconcile
Purpose
Re-project a historical window (default 30 days) ignoring the watermark, and report how much
had to change via driftCount on GET /sync. Diagnostic: a healthy stream reports zero
drift. Deliberately does not advance the watermark — a reconcile that made progress could
mask an incremental path that is stuck.
Auth and Permissions
- Auth:
JwtAuthGuard+RoleGuard· Permission:Analytics_UPDATE· Rate limit: 20/min
Error Cases
| HTTP | Code | Condition |
|---|---|---|
| 404 | ANALYTICS_STREAM_NOT_FOUND | Unknown stream name |
9. Flow Diagrams
9.1 Route Ownership
9.2 Request Sequence (report run)
9.3 Error Branch (report run)
10. Pagination, Sorting, Filtering, and Search
| Endpoint | Pagination Type | Default Size | Max Size | Sort Fields | Filters | Result Cap |
|---|---|---|---|---|---|---|
GET /reports/{reportId} | offset | 50 | 500 | per-report sortable | per-report allowlist (JSON) | 500 |
GET /exports | offset | 20 | 500 | requestedAt DESC (fixed) | none | — |
GET /top-products | limit only | 10 | — | units/revenue | channel | limit |
GET /geography | limit only | — | — | revenue DESC (fixed) | channel | limit |
Search filters (on reports that support them): prefix-anchored, case-insensitive,
escaped — filters={"customerName":"ram"} matches "Ram Bahadur", not "Sita Ram". Substring
search is not supported: the index cannot serve it, and an unanchored scan is exactly the
expensive-query vector the rate limits bound.
11. Caching, Jobs, and External Integrations
| Integration | Used? | Details | Source |
|---|---|---|---|
| Redis cache | No | The module writes no cache domain | — |
| BullMQ | Yes | QueueName.ANALYTICS, 8 job kinds (§9 of backend doc); one @Processor; export build, sync, rollup, drain, reconcile, backfill, sweeps | analytics.processor.ts |
| MongoDB | Yes | The analytics store — 18 collections | packages/mongodb |
| PostgreSQL | Yes (read-only) | The ETL's source; the module never writes it | projectors |
| External API | No | — | — |
13. Mandatory Deep API Documentation Pack
13.1 Route-by-Route Completeness Matrix
| Route | Controller Method | DTOs | Service Method | Guards | Permissions | Cache | Jobs | DB Touches | Errors | Documented? |
|---|---|---|---|---|---|---|---|---|---|---|
GET /dashboard | summary | — | AnalyticsDashboardService.summary | JWT+Role+Throttle | Analytics_READ | — | — | rollups, product facts | 503 | Yes |
GET /kpis | kpis | AnalyticsPeriodQueryDto | kpis | JWT+Role+Throttle | Analytics_READ | — | — | rollups ×2 | 503/400 | Yes |
GET /trends | trend | period DTO | trend | JWT+Role+Throttle | Analytics_READ | — | — | rollups | 503/400 | Yes |
GET /funnel | funnel | period DTO | funnel | JWT+Role+Throttle | Analytics_READ | — | — | rollups | 503/400 | Yes |
GET /channels | channels | period DTO | byChannel | JWT+Role+Throttle | Analytics_READ | — | — | rollups | 503/400 | Yes |
GET /payment-methods | paymentMethods | period DTO | paymentMethodMix | JWT+Role+Throttle | Analytics_READ | — | — | rollups | 503/400 | Yes |
GET /top-products | topProducts | ranked DTO | topProducts | JWT+Role+Throttle | Analytics_READ | — | — | order-line facts | 503/400 | Yes |
GET /geography | geography | ranked DTO | byDistrict | JWT+Role+Throttle | Analytics_READ | — | — | order facts | 503/400 | Yes |
GET /reviews | reviews | period DTO | ratingDistribution + pendingModerationCount | JWT+Role+Throttle | Analytics_READ | — | — | review facts | 503/400 | Yes |
GET /reports | list | — | listAvailable | JWT+Role+Throttle | Analytics_READ | — | — | registry (in-memory) | — | Yes |
GET /reports/{reportId} | run | ReportRunQueryDto | run | JWT+Role+Throttle | Analytics_READ + report's | — | — | fact collection | 404/400/503 | Yes |
POST /exports | create | RequestReportExportDto | request | JWT+Role+Throttle | Analytics_READ + report's | — | build_export | export records | 404/400 | Yes |
GET /exports | list | ListReportExportsDto | listForAdmin | JWT+Role+Throttle | Analytics_READ | — | — | export records | — | Yes |
GET /exports/{exportId} | findOne | — | findForAdmin | JWT+Role+Throttle | Analytics_READ | — | — | export records | 404 | Yes |
GET .../download | downloadExport | — | findForAdmin + assertStillPermitted | JWT+Role+Throttle | Analytics_READ + report's (live) | — | — | export records, file | 404/400/410 | Yes |
GET /sync | state | — | describeStreams | JWT+Role+Throttle | Analytics_READ | — | — | sync-state, pending | — | Yes |
POST /sync/refresh | refresh | RefreshSyncDto | forceSync | JWT+Role+Throttle | Analytics_UPDATE | — | sync_stream/sync_all | — | 409/404 | Yes |
POST /sync/backfill | backfill | BackfillSyncDto | backfill | JWT+Role+Throttle | Analytics_UPDATE | — | backfill | — | 404 | Yes |
POST /sync/reconcile | reconcile | ReconcileSyncDto | reconcile | JWT+Role+Throttle | Analytics_UPDATE | — | reconcile | — | 404 | Yes |
13.2 Request/Response Exhaustiveness
Covered in §6 and §8: the period DTO and every preset, the filters-as-JSON parameter with
its forbidNonWhitelisted rationale, the export request with idempotency, the full meta
envelope (§6.7), the report envelope with pointInTime behavior, per-endpoint error tables
with the exact codes, and the 503-before-first-sync rule.
13.3 API Diagram Pack
Route ownership (§9.1), report-run sequence (§9.2), report-run error tree (§9.3), plus the export state machine and sync flow diagrams in the feature and backend docs.
13.4 Consumer Integration Notes
| Consumer | Required Knowledge | Failure Handling | Contract Stability |
|---|---|---|---|
| Admin panel (dashboard) | Independent widget endpoints; dataAsOf must be shown; behind is a subtle indicator, not an error; todayVsYesterdayPercent null on a blank yesterday | 503 before first sync → retry; never render zeros | Stable |
| Admin panel (KPIs) | comparison.truncatedForFairness — say the comparison is same-elapsed-days | Null changePercent when previous was 0 | Stable |
| Admin panel (reports) | Build the picker from GET /reports; filters as one JSON object; unsupported keys refused by name; pointInTime reports have period: null; search is prefix-anchored | 404 = no-such OR not-permitted; 400s name the fix | Stable |
| Admin panel (exports) | idempotencyKey required; poll until ready; check truncated; files expire in 48h; download re-checks permission | 410 → request again; 404 → re-read GET /reports | Stable |
| Admin panel (sync) | Analytics_UPDATE gates triggers; 409 ALREADY_RUNNING is an answer; backfill/reconcile never delete; poll GET /sync for progress | 404 stream name → message lists valid ones | Stable |
| QA | 12 error codes; 503 vs zero semantics; truncation; idempotency; per-report filters | Reproduce via exact codes | Stable |
| Storefront | Nothing changes — no mobile surface, no route touched | — | Stable |
13.5 API Tradeoffs and Rationale
| Decision | Chosen Behavior | Alternatives Considered | Why This Tradeoff | Risk | Mitigation |
|---|---|---|---|---|---|
| Filters as one JSON param | filters={...} | Loose query keys | forbidNonWhitelisted + per-report allowlists | Uglier URLs | Documented |
| 404 for both no-such and not-permitted | One shape | 403 | No id enumeration | Ops can't see what exists | GET /reports shows what they may run |
| 503 before first sync | Refuse | 200-with-zeros | Zero revenue ≠ no trading | First-load error | Retry shortly |
dataAsOf on every response | Visible staleness | Debug-only field | Honest age | — | — |
| Exports async | Queued jobs | Synchronous stream | Minutes-long aggregations | Polling UX | statuses + downloadUrl |
| 5/hour export cap | Tight budget | Read budget | Each spawns aggregation + file | — | — |
| Download re-checks permission | Live role | Request-time only | No snapshot access | — | — |
Analytics_UPDATE on triggers | Elevated gate | Same _READ | Backfill walks all history | — | — |
13.6 API Change Impact
| Change | Affected Consumers | Backend Impact | Data Impact | Migration Needed? | Compatibility Plan |
|---|---|---|---|---|---|
| New report added to registry | Admin picker (auto-discovers) | One descriptor | Rollup/fact read | No | Additive; GET /reports drives the UI |
| New period preset | Admin date pickers | period.util.ts | None | No | Additive |
| Filter field renamed | Admin report forms | Descriptor + pipeline | None | No | GET /reports carries the new key |
| Report permission changed | Admin roles | Descriptor | None | No | Picker filters automatically |
| New analytics endpoint | Admin | Controller + service | Read | No | Additive |
14. Zero-Omission API Checklist
- Every controller route is documented (§4, §8, §13.1).
- Every parent route prefix and runtime URL is documented (§2, §4).
- Every DTO field, nested field, enum, default, transform, and validator is documented (§6, §7).
- Every response field, nullable field, generated field, and omitted raw entity field is documented (§6.7, §8).
- Every auth, guard, permission, role, and identity branch is documented (§5).
- Every success, validation, auth, permission, not-found, conflict, rate-limit, and server-error branch is documented (§8).
- Every database read/write, queue job, and external call is documented (§8 side effects, §11).
- Every route has examples for minimal request, success response, and representative failures (§8).
- Every endpoint family has route, sequence, and error diagrams (§9).
- Every tradeoff and compatibility risk is documented (§13.5, §13.6).
- The API doc links to backend and features/flows (See Also).
15. Integration Checklist
- Every route from controllers is documented.
- Every DTO field is documented.
- Every enum value is documented.
- Every response envelope is documented.
- Every error code is documented (12/12).
- Every auth guard and permission is documented.
- Every cache key, queue job, and external call is documented — no cache exists by design.
- Every diagram matches the current code.
- The API doc links to backend and features/flows.
See Also
- Backend doc:
/docs/developer/analytics/backend - Features and flows doc:
/docs/developer/analytics/feature - TDD: not yet published
Reports & Analytics Module Overview
The admin analytics surface — dashboards and KPIs served from pre-aggregated MongoDB rollups, a 19-report registry, async exports, and the PostgreSQL-to-MongoDB ETL that feeds it all.
Reports & Analytics Backend Documentation
Backend architecture, data model, ETL pipeline, watermark mechanics, rollups, queues, and operational behavior for Reports & Analytics.