AACsearch
Analytics

Analytics Overview

Search analytics in AACSearch — what's measured, where the data lives, and how to use it to drive relevance, content, and conversion improvements.

AACSearch records every search, every click, every conversion, and every operational event that touches your indexes. The data is exposed in three places — the Dashboard analytics page, the search analytics oRPC procedures, and a CSV / JSON export — so you can both diagnose specific queries and close the loop by feeding insights back into relevance tuning, content gaps, and revenue analysis.

This section is the customer-facing reference for that data. The dashboard page covers UI; this section covers concepts, schemas, and the workflows that turn telemetry into improvements.

What's measured

SurfaceWhat is recordedWhere it lands
SearchEvery /api/search and /api/search/multi call with query, filters, result count, latency.SearchUsageEvent
ClickEvery click on a result, with position, productId, queryId.SearchUsageEvent
No-resultSearches that returned 0 hits (also surface in the dashboard).SearchUsageEvent + Zero Queries panel
Filter / sortfilter_used events with the filter expression.SearchUsageEvent
Widget openFirst impression of the widget.SearchUsageEvent
ConversionPost-search purchase / signup / custom event.SearchUsageEvent (type=conversion)
AI answerEvery /api/search/ai/answer call with searchTimeMs and totalTimeMs.SearchUsageEvent (type=ai_answer)
Knowledge askEvery knowledge.ask / askStream call with token spend.SearchUsageEvent (type=knowledge_ask)
Admin activityIndex created, key rotated, reindex started/finished, sync job completed, quota warning.SearchActivityEvent
Rate limit429 rejections (debug only).SearchRateLimitBucket

Tenant scope: every row has organizationId. No cross-org reads, ever (Invariant 5).

Event schema (customer view)

Public event capture goes through POST /events/track (see packages/api/modules/search/events-public.ts). The accepted shape is intentionally narrow — no email, no full IP, UA capped at 256 chars — so you can plug it into any storefront safely:

{
  type: "search_query" | "zero_results" | "result_click" | "widget_open"
      | "filter_used" | "conversion" | "visit" | "click";
  sessionId?: string;
  anonymousUserId?: string;
  query?: string;
  productId?: string;
  position?: number;
  filters?: Record<string, unknown>;
  sort?: string;
  locale?: string;
  referrer?: string;
  queryId?: string;             // joins clicks to the search that produced them
  conversionType?: string;      // "purchase", "signup", "add_to_cart", …
  metadata?: Record<string, unknown>;
}

Batch up to 50 events per request ({ events: [...] }) and use sendBeacon on page unload — both are first-class supported paths in the widget.

The exact names (search_query, result_click, …) are what surface in the analytics tables and what the SDK / widget emits. Keep them aligned; do not invent custom types in client code.

Where to read the data

Dashboard

Analytics dashboard — KPI tiles, time-series chart, Top Queries, Failed Queries, Activity feed. The UI is for everyone; period selector controls all tabs. Retention varies by plan (24 h on all; 30 d on paid).

oRPC procedures

For programmatic access, the protected procedures in packages/api/modules/search/router.ts:

ProcedureWhat it returns
search.usageSummaryPeriod totals: searches, clicks, CTR, p50/p95 latency.
search.topQueriesRanked top queries with searches, clicks, CTR, zero-results flag.
search.analyticsTime-series and aggregated counts by event type.
search.recentActivityActivity feed from SearchActivityEvent.
search.ctrAnalyticsDaily CTR trend.
search.analyticsEventsRaw event retrieval (paginated, filtered).
knowledge.usageMetricsKnowledge module token spend and query count.

Each accepts organizationId + a period (24h, 7d, 30d) and returns Prisma-shaped rows you can render anywhere.

Export

CSV and JSON export — see Export.

The closed loop

Analytics in isolation is just a dashboard. The point is to turn each metric into an action:

What you seeWhere it points
High-volume queries with low CTRRelevance tuning — synonyms, queryBy weights, curations.
Zero-result queriesNo-results loop — content gap or synonym/curation rule.
High-volume queries with no conversionConversions — funnel analysis; consider AI answers or guided merchandising.
Spike in failed queries (4xx / 5xx)Operations / errors — rate limit, scoped-token issue.
Rising no-result rateRe-ingest cadence — see Ingest and reindex.
Falling CTR despite stable volumeCuration drift or synonym noise — audit Relevance panel.
Widget-open events without subsequent searchWidget UX — see Widget overview.

For an e-commerce example, the typical loop is: zero-results → spot the missing brand → add a synonym → measure recovery in the same query's CTR over the next period. For SaaS / knowledge-base search, the loop is: low-CTR query → reword the article title → re-ingest → measure CTR rise.

Retention and privacy

  • Retention. All event rows are retained for the plan's window — typically 90 days. Aggregated counts in SearchActivityEvent are kept longer.
  • PII. The public event endpoint deliberately accepts no full IP or email. UA is capped. metadata is opaque but capped at 4 KB JSON.
  • GDPR / DPA. Personal-data scope for AACSearch is documented in Security & Compliance. The widget's analytics path is included in the standard DPA.
  • Right-to-be-forgotten. Deletion is per anonymousUserId — see the Data privacy doc for the redaction procedure.

Examples

E-commerce

Watch: top queries, conversion rate per query, no-result rate, brand-facet usage.

What to read together:

  • topQueries for the week + ctrAnalytics daily trend.
  • analyticsEvents filtered to type=conversion for revenue attribution.
  • noResults panel + content team's brand additions to close the loop.

Watch: zero-result rate, click-to-article rate, AI-answer refusal rate.

What to read together:

  • Top zero-result queries → content gaps in the help center.
  • knowledge.usageMetrics token spend + refusal-rate spikes → quality regression after a docs update.

On this page