Happy House - Ecommerce Docs

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

AreaFiles InspectedWhat Was Verified
Controllersapps/api/src/modules/analytics/admin/{dashboard,reports,exports,sync}/*.controller.tsRoutes, methods, guards, permissions, rate limits
DTOsadmin/dto/*.tsPeriod/channel, filters-as-JSON, export request
Servicesshared/*.service.ts, shared/reports/*.ts, shared/sync/*.tsBehavior, freshness, registry, exports, triggers
Schemapackages/mongodb/src/schemas/analytics/*.schema.tsCollections, fields, retention
Jobspackages/jobs/src/index.tsQueue, job kinds, payloads
Existing docsconsumer-handoff.md, Fumadocs formatsFrozen contract and format baseline

2. Module Summary

FieldValue
Module nameanalytics
Module sluganalytics
Primary actorsadmin (reader), admin (operator)
API surfacesadmin only — nothing customer-facing
Base route prefixes/api/admin/analytics/
Auth modelJwtAuthGuard + RoleGuard + Analytics_* / Reports*_READ permissions
PersistenceMongoDB (facts, rollups, state, exports); reads PostgreSQL via the ETL
Runtime source of truthMongoDB; PostgreSQL is the ultimate source
Sibling docsBackend, Features and flows

3. Concepts and Terminology

TermMeaningSource FileUsed By
dataAsOfWhen the underlying data was last known current — the OLDEST contributing stream's lastSuccessAtanalytics-freshness.service.tsEvery response
stalenessSecondsSeconds between dataAsOf and nowsameEvery response
behindA contributing stream stopped on its page budget; figures valid but incompletesameEvery response
truncatedForFairnessThe comparison window was cut to the same elapsed length (7th of month → 1–7 of previous month)analytics-period.service.tsKPIs
pointInTimeReport describes NOW, not the period; meta.period is nullreport-registry.types.tsReports
dateKeyYYYY-MM-DD in Asia/Kathmandu — never UTC-bucket client-sideperiod utilEvery response
idempotencyKeyRequired on exports; unique per adminanalytics-export.service.tsExports
truncated (export)The 50k-row cap bit; also written into the fileanalytics-export-builder.service.tsExports
driftCountDocuments the last reconcile had to repair — non-zero means the incremental path missed rowsanalytics-sync-state.service.tsSync
parent_drivenorder_lines has no watermark of its own — the orders stream drives itanalytics.constants.tsSync

4. API Surface Map

SurfaceMethodPathActorAuth/GuardPermissionControllerPurpose
AdminGET/api/admin/analytics/dashboardReaderJWT + RoleAnalytics_READDashboardRollup-backed headline tiles
AdminGET/api/admin/analytics/kpisReaderJWT + RoleAnalytics_READDashboardKPIs with period comparison
AdminGET/api/admin/analytics/trendsReaderJWT + RoleAnalytics_READDashboardDay-by-day series
AdminGET/api/admin/analytics/funnelReaderJWT + RoleAnalytics_READDashboardConversion funnel (starts at cart)
AdminGET/api/admin/analytics/channelsReaderJWT + RoleAnalytics_READDashboardChannel split
AdminGET/api/admin/analytics/payment-methodsReaderJWT + RoleAnalytics_READDashboardPayment-method mix
AdminGET/api/admin/analytics/top-productsReaderJWT + RoleAnalytics_READDashboardTop products by units/revenue
AdminGET/api/admin/analytics/geographyReaderJWT + RoleAnalytics_READDashboardRevenue/orders by district
AdminGET/api/admin/analytics/reviewsReaderJWT + RoleAnalytics_READDashboardRating distribution, moderation count
AdminGET/api/admin/analytics/reportsReaderJWT + RoleAnalytics_READReportsThe caller's report descriptors
AdminGET/api/admin/analytics/reports/{reportId}ReaderJWT + RoleAnalytics_READ + report'sReportsRun one registry report
AdminPOST/api/admin/analytics/exportsReaderJWT + RoleAnalytics_READ + report'sExportsQueue an export
AdminGET/api/admin/analytics/exportsReaderJWT + RoleAnalytics_READExportsOwn exports, newest first
AdminGET/api/admin/analytics/exports/{exportId}ReaderJWT + RoleAnalytics_READExportsPoll one export
AdminGET/api/admin/analytics/exports/{exportId}/downloadReaderJWT + RoleAnalytics_READ + report's (live)ExportsDownload the file
AdminGET/api/admin/analytics/syncReaderJWT + RoleAnalytics_READSyncStream states + pending backlog
AdminPOST/api/admin/analytics/sync/refreshOperatorJWT + RoleAnalytics_UPDATESyncForce a sync now
AdminPOST/api/admin/analytics/sync/backfillOperatorJWT + RoleAnalytics_UPDATESyncResumable full rebuild
AdminPOST/api/admin/analytics/sync/reconcileOperatorJWT + RoleAnalytics_UPDATESyncRe-project a window, report drift

5. Auth, Identity, and Permissions

SurfaceGuard/DecoratorIdentity ShapePermissionGuest AllowedNotes
Dashboard/reports/exports readsJwtAuthGuard, RoleGuardreq.userAnalytics_READNoReports/exports additionally require the report's own Reports*_READ
Sync triggersJwtAuthGuard, RoleGuardreq.user.idAnalytics_UPDATENoFirst non-_READ action the module uses
  • Ten permission modules: Analytics + ReportsSales · ReportsOrders · ReportsProducts · ReportsCustomers · ReportsPayments · ReportsPromotions · ReportsInventory · ReportsReviews · ReportsPos. _CREATE/_DELETE/_RESTORE unused (catalogue cross product).
  • Rate limits per admin account (never per IP): reads ADMIN_ANALYTICS_READ 60/min; export submission ADMIN_ANALYTICS_EXPORT 5/hour; sync triggers ADMIN_HEAVY_OP 20/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

FieldTypeRequiredDefaultValidationNotes
periodstringNolast_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
fromstringcustom only@IsStringYYYY-MM-DD, required when period=custom
tostringcustom only@IsStringYYYY-MM-DD, required when period=custom
channelstringNo@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 ReportRunQueryDtoGET /reports/{reportId}

FieldTypeRequiredDefaultValidationNotes
(period DTO)Nolast_30_daysSee §6.1
filtersstring (JSON)No@IsStringOne JSON parameter, not loose keys — the global pipe runs forbidNonWhitelisted. Validated against the report's own allowlist
sortBystringNoreport's defaultSort.by@IsStringMust be in the report's sortable
sortDirectionstringNoreport's default@IsIn(["asc","desc"])
limitnumberNo50int 1–500
offsetnumberNo0int ≥0

6.4 RequestReportExportDtoPOST /exports

FieldTypeRequiredValidationNotes
reportIdstringYes@IsStringMust exist AND be permitted
formatstringYes@IsIn(["csv","xlsx"])
(period DTO)YesStored with the request
filtersstring (JSON)No@IsStringSame allowlist semantics as running
sortBy / sortDirectionstringNoas §6.3Stored with the request
idempotencyKeystringYes@IsStringUnique per admin; double-click returns the existing export

6.5 ListReportExportsDtoGET /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

EnumValueMeaningRuntime EffectSource
ANALYTICS_PERIOD_PRESETS11 presets (§6.1)Window selectorsResolved in Asia/Kathmanduperiod.util.ts
ANALYTICS_CHANNELSonline / posSales channelFilters the rollup groupanalytics.shared.ts
Export statusqueued / processing / ready / failed / expiredExport lifecycleDrives polling and downloadexport schema
Sync statusidle / running / failed / backfillingStream stateDrives GET /sync renderingsync-state schema
ANALYTICS_PAYMENT_METHODSplatform methodsPayment splitsGrouped in rollupsanalytics.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

PartRequiredDetails
QueryNonone — fixed windows (today / last 7 days / this month)

Response

200data: 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

HTTPCodeCondition
503ANALYTICS_NOT_YET_SYNCEDFirst 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

PartRequiredDetails
QueryNoperiod, 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

HTTPCodeCondition
503ANALYTICS_NOT_YET_SYNCEDNever synced
400ANALYTICS_INVALID_PERIODBad custom window
400ANALYTICS_PERIOD_TOO_LONGOver 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_createdcheckouts_startedpayment_startedpayment_succeededorders_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

PartRequiredDetails
QueryNoperiod, 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=0

Filters 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

200data: { items, total, meta: { report, dataAsOf, stalenessSeconds, behind, period (null when pointInTime), filters, sort, paging, generatedAt, durationMs } }.

Error Cases

HTTPCodeCondition
404ANALYTICS_REPORT_NOT_FOUNDNo such report, OR not permitted — deliberately indistinguishable
400ANALYTICS_INVALID_PERIOD / ANALYTICS_PERIOD_TOO_LONGBad window
400ANALYTICS_FILTER_NOT_SUPPORTEDFilter key not on this report, or bad filters JSON
400ANALYTICS_SORT_NOT_SUPPORTEDsortBy not sortable
503ANALYTICS_NOT_YET_SYNCEDNever 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

HTTPCodeCondition
404ANALYTICS_REPORT_NOT_FOUNDNo such report, or not permitted
400ANALYTICS_INVALID_PERIOD / ANALYTICS_PERIOD_TOO_LONGBad window

Edge Cases

  • Same idempotencyKey again → 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

HTTPCodeCondition
404ANALYTICS_EXPORT_NOT_FOUNDNo 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

HTTPCodeCondition
404ANALYTICS_EXPORT_NOT_FOUNDMissing, not yours, or permission revoked (same 404 as the report check)
400ANALYTICS_EXPORT_NOT_READYStill queued/building
400ANALYTICS_EXPORT_FAILEDThe build failed; error carries the reason
410ANALYTICS_EXPORT_EXPIREDThe 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

HTTPCodeCondition
409ANALYTICS_SYNC_ALREADY_RUNNINGA run is in progress — an answer, not a failure
404ANALYTICS_STREAM_NOT_FOUNDUnknown 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

HTTPCodeCondition
404ANALYTICS_STREAM_NOT_FOUNDUnknown 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

HTTPCodeCondition
404ANALYTICS_STREAM_NOT_FOUNDUnknown stream name

9. Flow Diagrams

9.1 Route Ownership

9.2 Request Sequence (report run)

9.3 Error Branch (report run)

EndpointPagination TypeDefault SizeMax SizeSort FieldsFiltersResult Cap
GET /reports/{reportId}offset50500per-report sortableper-report allowlist (JSON)500
GET /exportsoffset20500requestedAt DESC (fixed)none
GET /top-productslimit only10units/revenuechannellimit
GET /geographylimit onlyrevenue DESC (fixed)channellimit

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

IntegrationUsed?DetailsSource
Redis cacheNoThe module writes no cache domain
BullMQYesQueueName.ANALYTICS, 8 job kinds (§9 of backend doc); one @Processor; export build, sync, rollup, drain, reconcile, backfill, sweepsanalytics.processor.ts
MongoDBYesThe analytics store — 18 collectionspackages/mongodb
PostgreSQLYes (read-only)The ETL's source; the module never writes itprojectors
External APINo

13. Mandatory Deep API Documentation Pack

13.1 Route-by-Route Completeness Matrix

RouteController MethodDTOsService MethodGuardsPermissionsCacheJobsDB TouchesErrorsDocumented?
GET /dashboardsummaryAnalyticsDashboardService.summaryJWT+Role+ThrottleAnalytics_READrollups, product facts503Yes
GET /kpiskpisAnalyticsPeriodQueryDtokpisJWT+Role+ThrottleAnalytics_READrollups ×2503/400Yes
GET /trendstrendperiod DTOtrendJWT+Role+ThrottleAnalytics_READrollups503/400Yes
GET /funnelfunnelperiod DTOfunnelJWT+Role+ThrottleAnalytics_READrollups503/400Yes
GET /channelschannelsperiod DTObyChannelJWT+Role+ThrottleAnalytics_READrollups503/400Yes
GET /payment-methodspaymentMethodsperiod DTOpaymentMethodMixJWT+Role+ThrottleAnalytics_READrollups503/400Yes
GET /top-productstopProductsranked DTOtopProductsJWT+Role+ThrottleAnalytics_READorder-line facts503/400Yes
GET /geographygeographyranked DTObyDistrictJWT+Role+ThrottleAnalytics_READorder facts503/400Yes
GET /reviewsreviewsperiod DTOratingDistribution + pendingModerationCountJWT+Role+ThrottleAnalytics_READreview facts503/400Yes
GET /reportslistlistAvailableJWT+Role+ThrottleAnalytics_READregistry (in-memory)Yes
GET /reports/{reportId}runReportRunQueryDtorunJWT+Role+ThrottleAnalytics_READ + report'sfact collection404/400/503Yes
POST /exportscreateRequestReportExportDtorequestJWT+Role+ThrottleAnalytics_READ + report'sbuild_exportexport records404/400Yes
GET /exportslistListReportExportsDtolistForAdminJWT+Role+ThrottleAnalytics_READexport recordsYes
GET /exports/{exportId}findOnefindForAdminJWT+Role+ThrottleAnalytics_READexport records404Yes
GET .../downloaddownloadExportfindForAdmin + assertStillPermittedJWT+Role+ThrottleAnalytics_READ + report's (live)export records, file404/400/410Yes
GET /syncstatedescribeStreamsJWT+Role+ThrottleAnalytics_READsync-state, pendingYes
POST /sync/refreshrefreshRefreshSyncDtoforceSyncJWT+Role+ThrottleAnalytics_UPDATEsync_stream/sync_all409/404Yes
POST /sync/backfillbackfillBackfillSyncDtobackfillJWT+Role+ThrottleAnalytics_UPDATEbackfill404Yes
POST /sync/reconcilereconcileReconcileSyncDtoreconcileJWT+Role+ThrottleAnalytics_UPDATEreconcile404Yes

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

ConsumerRequired KnowledgeFailure HandlingContract Stability
Admin panel (dashboard)Independent widget endpoints; dataAsOf must be shown; behind is a subtle indicator, not an error; todayVsYesterdayPercent null on a blank yesterday503 before first sync → retry; never render zerosStable
Admin panel (KPIs)comparison.truncatedForFairness — say the comparison is same-elapsed-daysNull changePercent when previous was 0Stable
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-anchored404 = no-such OR not-permitted; 400s name the fixStable
Admin panel (exports)idempotencyKey required; poll until ready; check truncated; files expire in 48h; download re-checks permission410 → request again; 404 → re-read GET /reportsStable
Admin panel (sync)Analytics_UPDATE gates triggers; 409 ALREADY_RUNNING is an answer; backfill/reconcile never delete; poll GET /sync for progress404 stream name → message lists valid onesStable
QA12 error codes; 503 vs zero semantics; truncation; idempotency; per-report filtersReproduce via exact codesStable
StorefrontNothing changes — no mobile surface, no route touchedStable

13.5 API Tradeoffs and Rationale

DecisionChosen BehaviorAlternatives ConsideredWhy This TradeoffRiskMitigation
Filters as one JSON paramfilters={...}Loose query keysforbidNonWhitelisted + per-report allowlistsUglier URLsDocumented
404 for both no-such and not-permittedOne shape403No id enumerationOps can't see what existsGET /reports shows what they may run
503 before first syncRefuse200-with-zerosZero revenue ≠ no tradingFirst-load errorRetry shortly
dataAsOf on every responseVisible stalenessDebug-only fieldHonest age
Exports asyncQueued jobsSynchronous streamMinutes-long aggregationsPolling UXstatuses + downloadUrl
5/hour export capTight budgetRead budgetEach spawns aggregation + file
Download re-checks permissionLive roleRequest-time onlyNo snapshot access
Analytics_UPDATE on triggersElevated gateSame _READBackfill walks all history

13.6 API Change Impact

ChangeAffected ConsumersBackend ImpactData ImpactMigration Needed?Compatibility Plan
New report added to registryAdmin picker (auto-discovers)One descriptorRollup/fact readNoAdditive; GET /reports drives the UI
New period presetAdmin date pickersperiod.util.tsNoneNoAdditive
Filter field renamedAdmin report formsDescriptor + pipelineNoneNoGET /reports carries the new key
Report permission changedAdmin rolesDescriptorNoneNoPicker filters automatically
New analytics endpointAdminController + serviceReadNoAdditive

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

On this page

Reports & Analytics - API Reference1. Documentation Evidence2. Module Summary3. Concepts and Terminology4. API Surface Map5. Auth, Identity, and Permissions6. DTO and Model Reference6.1 AnalyticsPeriodQueryDto — every analytics/report/export request6.2 AnalyticsRankedQueryDto — rankings6.3 ReportRunQueryDtoGET /reports/{reportId}6.4 RequestReportExportDtoPOST /exports6.5 ListReportExportsDtoGET /exports6.6 AnalyticsSyncDto — the three trigger routes6.7 The meta envelope — on every data response7. Enum Reference8. Endpoint Reference8.1 GET /api/admin/analytics/dashboardPurposeAuth and PermissionsRequestResponseSide EffectsError Cases8.2 GET /api/admin/analytics/kpisPurposeAuth and PermissionsRequestResponseSide EffectsError Cases8.3 GET /api/admin/analytics/trendsPurposeAuth and PermissionsSide Effects8.4 GET /api/admin/analytics/funnelPurposeAuth and PermissionsSide Effects8.5 GET /api/admin/analytics/channelsPurposeAuth and Permissions8.6 GET /api/admin/analytics/payment-methodsPurposeAuth and Permissions8.7 GET /api/admin/analytics/top-productsPurposeAuth and PermissionsRequest8.8 GET /api/admin/analytics/geographyPurposeAuth and Permissions8.9 GET /api/admin/analytics/reviewsPurposeAuth and Permissions8.10 GET /api/admin/analytics/reportsPurposeAuth and PermissionsResponseSide Effects8.11 GET /api/admin/analytics/reports/{reportId}PurposeAuth and PermissionsResponseError Cases8.12 POST /api/admin/analytics/exportsPurposeAuth and PermissionsRequestResponseSide EffectsError CasesEdge Cases8.13 GET /api/admin/analytics/exportsPurposeAuth and Permissions8.14 GET /api/admin/analytics/exports/{exportId}PurposeAuth and PermissionsResponseError Cases8.15 GET /api/admin/analytics/exports/{exportId}/downloadPurposeAuth and PermissionsError Cases8.16 GET /api/admin/analytics/syncPurposeAuth and PermissionsResponse8.17 POST /api/admin/analytics/sync/refreshPurposeAuth and PermissionsResponseError Cases8.18 POST /api/admin/analytics/sync/backfillPurposeAuth and PermissionsError Cases8.19 POST /api/admin/analytics/sync/reconcilePurposeAuth and PermissionsError Cases9. Flow Diagrams9.1 Route Ownership9.2 Request Sequence (report run)9.3 Error Branch (report run)10. Pagination, Sorting, Filtering, and Search11. Caching, Jobs, and External Integrations13. Mandatory Deep API Documentation Pack13.1 Route-by-Route Completeness Matrix13.2 Request/Response Exhaustiveness13.3 API Diagram Pack13.4 Consumer Integration Notes13.5 API Tradeoffs and Rationale13.6 API Change Impact14. Zero-Omission API Checklist15. Integration ChecklistSee Also