AACsearch
AI Search

Suggestions & Autocomplete

How AACSearch surfaces query suggestions, popular queries, and did-you-mean as the user types.

Suggestions are short query candidates surfaced as the user types — autocomplete in the search box, "did you mean…" on no-results, and trending queries on the empty state. They are a UX layer on top of the same indexes, not a separate engine.

What "suggestions" means here

Three distinct sources, each with its own endpoint:

SourceWhat it returnsUse case
Title prefixIndex documents whose searchable fields start with the prefix."wireless head…" → product titles starting with that prefix.
Popular queriesPast queries from SearchUsageEvent ordered by frequency.Empty-state suggestions, "people are searching for…".
Spell-checkCorrected spelling derived from the index's vocabulary."iphine" → suggest "iphone" when the original query has few hits.

The widget combines all three into a single dropdown; an API integrator can pick any subset.

Status

CapabilityStatus
Title-prefix autocomplete via multi-search✅ Available
Popular queries (Top Queries analytics panel + API)✅ Available
Spell-check / "did you mean" (/v1/projects/{projectId}/spell-check)✅ Available
Personalised suggestions (user history)🟡 Beta (opt-in)
Cross-index federated suggestions⏳ Roadmap

Title-prefix autocomplete

The cleanest pattern is two searches in one round-trip via multi-search: a primary search for the result list and a tiny suggestions search for the dropdown.

const [main, suggestions] = await client.multiSearch([
  {
    indexSlug: "products",
    q: "wireless head",
    queryBy: "title,brand,description",
    perPage: 20,
    facetBy: "brand,categories",
  },
  {
    indexSlug: "products",
    q: "wireless head",
    queryBy: "title",
    perPage: 5,
    includeFields: "id,title",
    highlightFields: "title",
  },
]);

Tips:

  • Keep the suggestions search cheap: queryBy only title, perPage ≤ 5, includeFields only the columns you render.
  • Use highlightFields: "title" so you can render the matched prefix in bold.
  • Debounce keystrokes at 150–300 ms. Without debounce, every keystroke is one round-trip — fast but expensive.

The full multi-search response shape is in Multi-search and querying.

Two places to read them from:

Analytics API

const popular = await orpc.search.topQueries.call({
  organizationId,
  period: "30d",
  limit: 10,
});

Returns { query, searches, clicks, ctr, zeroResults }[] from SearchUsageEvent. Use this for an admin panel or a "trending" widget.

Empty-state suggestions

Render the top 5–10 popular queries when the search box is focused but empty. Cache the result client-side for 5–15 minutes — the underlying data only changes as new events come in.

Did-you-mean / spell-check

POST /v1/projects/{projectId}/spell-check

Input: a query string. Output: a list of corrected candidates ranked by confidence, taken from the searchable terms in the org's indexes. Use it when the primary search returns < 3 hits:

const main = await search({ q: "iphine" });

if (main.found < 3) {
  const corrections = await spellCheck({ q: "iphine" });
  // → ["iphone", "iphone 15", …]
}

Important: the spell-check does not retry the search for the user — it returns candidates. The UI decides whether to auto-run the top candidate or show a "Did you mean: iphone?" link.

Authentication

All three sources reuse the same auth and quota model as keyword search:

  • Bearer search key (ss_search_*) — origin-restricted, rate-limited.
  • Scoped search tokens (HMAC) — narrow the visible documents; their scopedFilter is AND-combined into both the primary and suggestions search (Invariant 4).
  • Per-org rate limits from SearchRateLimitBucket.

A scoped token that pins indexSlug: "products" will reject a suggestions call against any other index.

Analytics grouping

Suggestion calls and the eventual click event are tied together through the query_id on the multi-search response. Pass query_id along with the click event so analytics groups the suggestion impression and the resulting click in the same funnel:

trackClick({
  query_id: main.queryId,
  document_id: clickedHit.id,
});

See Analytics overview for the event schema.

Caching and debouncing

KnobRecommended value
Keystroke debounce150–300 ms
Min characters to query2 (1 is noisy; 3+ misses short brand names)
Client-side suggestion cacheLRU 20–50 entries, scoped to the current indexSlug
Popular-queries TTL5–15 minutes (refresh in the background, not on render)
Server cache TTLDefault Hono response cache headers; do not enable a longer cache without rate-limit-aware invalidation

The bundled widget (packages/widget/src/index.ts) ships with debounce = 200 ms and a 30-entry LRU; align custom integrations with the same numbers.

When to skip suggestions

  • Voice / scanner input — the user is committed to a query string.
  • Mobile no-typing flows where the user opens the search modal pre-filled with a deep link.
  • Internal admin search where exact IDs are the norm.

In each case, render the result list directly and skip the suggestions panel.

On this page