Supercommerce API Docs
Admin API

Search Analytics Module — Admin

Read-only reporting over customer search activity: window-scoped KPIs, top searched terms, zero-result terms (catalog/synonym gaps), a volume time series, and a raw event drill-down.

Read-only reporting over customer search activity: window-scoped KPIs, top searched terms, zero-result terms (catalog/synonym gaps), a volume time series, and a raw event drill-down.

Source: api-modules/search-analytics/src/controllers/admin-search-analytics.controller.ts.

Optional plugin. Capture works by subscribing to the fire-and-forget search.performed domain event emitted by @sc/search; removing SearchAnalyticsModule.forRoot() from app.module.ts disables tracking and these endpoints entirely (storefront search is unaffected).


Capture semantics

A search_event row is recorded when a customer hits GET /store/product-search with a non-empty q on page 1. Specifically:

  • Filter-only browsing (no q) is not recorded — that's PLP traffic, not search intent.
  • Paging through results (page > 1) is not recorded — one search submission = one event.
  • The /suggestions autocomplete endpoint is never recorded.
  • Internal ProductSearchService consumers (dynamic product listings, admin previews) are never recorded.
  • The write is asynchronous (event listener) — it adds no latency to the search response, and an insert failure is logged and dropped, never surfaced to the customer.

Each event stores: the raw + normalized (lowercased, whitespace-collapsed) query, the filters that accompanied it (jsonb, only the keys sent), the sort, the result total, a zero-result flag, and — when a better-auth session cookie was present — the (possibly anonymous) userId + userIsAnonymous. Events are retained indefinitely.

Conventions

Authentication

All endpoints require a Better-Auth admin session and a role granting the matching permission.

EndpointPermission
GET /admin/search-analytics/*searchAnalytics: view

Windows

window is one of today (hourly trend buckets), 7d, 30d (daily buckets), all_time (monthly buckets). Defaults to 30d. Bounds are resolved against the store timezone (STORE_TZ_OFFSET_MINUTES).

Response envelope

Successful responses are wrapped by ResponseInterceptor:

{
  "data": <payload>,
  "message": "Success",
  "statusCode": 200,
  "metadata": { /* pagination, where applicable */ }
}

Endpoints

GET /admin/search-analytics/summary

Window-scoped KPIs.

Query: window.

// data
{
  "window": "30d",
  "totalSearches": 27123,
  "zeroResultSearches": 812,
  "uniqueTerms": 4031,
  "uniqueSearchers": 1922 // distinct identified users; sessionless searches excluded
}

GET /admin/search-analytics/top-searches

Most-searched terms, grouped on the normalized query, ordered by search count. Paginated.

Query: window, limit (1–100, default 20), offset (default 0).

// data: TopSearchRow[]
[
  {
    "query": "vitamin c serum",
    "searches": 412,
    "avgResults": 38,
    "lastSearchedAt": "2026-06-10T08:11:02.000Z"
  }
]
// metadata: { total, limit, offset, hasMore } — total = distinct terms in the window

GET /admin/search-analytics/zero-results

Same shape as top-searches, restricted to terms whose searches returned nothing. The actionable merchandising list (missing products, Typesense synonym gaps).

GET /admin/search-analytics/trend

Search volume time series for charting. Bucket granularity follows the window (see Windows). Not paginated — fixed-cardinality series.

Query: window.

// data: SearchTrendBucket[]
[
  { "bucket": "2026-06-09T00:00:00.000Z", "searches": 903, "zeroResults": 27 }
]

GET /admin/search-analytics/events

Raw event drill-down, newest first. Paginated (limit default 100/max 500, offset).

Query: limit, offset, q (substring match on the normalized query), zeroOnly (true/false), from, to (ISO datetimes).

// data: SearchEventRow[]
[
  {
    "id": "5f0c…",
    "query": "Vitamin C",
    "normalizedQuery": "vitamin c",
    "filters": { "brands": ["some-brand"] }, // null when none
    "sortBy": null,
    "totalResults": 38,
    "zeroResult": false,
    "userId": "usr_…",        // null when no session cookie was present
    "userIsAnonymous": false, // true = better-auth anonymous (guest) session
    "createdAt": "2026-06-10T08:11:02.000Z"
  }
]

On this page