AACSearch / guide
Search rules and synonyms
Merchandise a collection from your server — exclusion filters, boosts, pins, redirects, diversity, zero-hit fallbacks, schedules — and teach it your vocabulary with synonym sets, stop words and stemming dictionaries.
Search rules and synonyms change what a search returns without changing the request the storefront sends. They are configuration, so every operation on this page needs a full key (see keys and scopes); a widget using a search or browser key picks the result up on its next search.
Examples use the TypeScript SDK client from the quick start
and curl with -H "X-NOVA-API-KEY: $NOVA_API_KEY".
One rule set per collection
A collection has one rule set:
| Operation | Endpoint |
|---|---|
getSearchRule | GET /collections/{collectionName}/search_rules |
upsertSearchRule | PUT /collections/{collectionName}/search_rules |
deleteSearchRule | DELETE /collections/{collectionName}/search_rules |
listSearchRules | GET /search_rules — every collection's rules |
upsertSearchRule replaces the whole set: a field you leave out is reset to
its default. Read the current set first, change it, and send it back. The change
applies from the next search request.
const current = await client.searchRules.getSearchRule({ collectionName: "products" });
const { collection, updated_at, ...rules } = current;
await client.searchRules.upsertSearchRule({
collectionName: "products",
body: { ...rules, exclusion_filter_by: "active:=true" },
});Exclusion filter
exclusion_filter_by is a filter_by expression ANDed onto every search of
the collection — for example active:=true && stock_status:!=discontinued. It is
applied on the server, so a widget or a copied key cannot skip it.
Boosts
boosts rank documents matching a filter above the rest:
{
"boosts": [
{ "name": "In stock", "filter_by": "in_stock:true", "weight": 2 },
{ "name": "Clearance last", "filter_by": "clearance:true", "weight": -1 }
]
}- A higher
weightranks first; a negative weight buries. The default is1. - A document gets the score of the highest-weight boost it matches — weights
do not add up. For a combined effect, put the conditions in one boost
(
in_stock:true && has_image:true) with a higher weight. - By default boosts apply only to relevance-ordered searches, so an explicit
"price ascending" stays strictly by price. Set
boosts_override_sort: trueto put the boosts in front of the caller'ssort_bytoo. prioritybreaks ties when the collection's rule and active campaigns both contribute boosts.
Pins and sponsored placements
A boost only influences the sort score. A pin guarantees a slot: pins
splice doc_id into a 0-based position after the engine answers, whatever the
ties, boosts or caller sort.
{
"pins": [
{ "query": "iphone", "match": "prefix", "doc_id": "iphone-17", "position": 0 },
{ "filter_contains": "category:=shoes", "doc_id": "sneaker-42", "position": 2 }
]
}A pin with query applies when the search text matches (exact, prefix or
contains). A pin with filter_contains applies when the caller's filter_by
contains that string verbatim, which is how you pin on a category page with no
search text. A pin with neither applies to every search. When several pins claim
one slot, the higher priority wins.
sponsored entries (id, position, query/match, filter_by, label)
are placed the same way, and the returned hit carries a _sponsored marker so
the UI can label it "Sponsored". A sponsored placement only places a document the
engine already returned for the query.
Redirects, suggestions and banners
redirects— whenqmatchesquery, the search result carriesredirect: { url, query }. Hits are still returned, so a client that doesn't navigate keeps working; a storefront should go tourl.suggestions— refinements offered whenqmatches, for example "phone" → "phone Apple", "phone Samsung".banners— a free-formcontentblock (image, heading, link) surfaced alongside the results whenquerymatchesqorfilter_containsmatches the caller'sfilter_by. Each banner has a stableid.
Diversity
diversity rules reshape the final hit order:
| Setting | Effect |
|---|---|
field + max | at most max hits sharing a value, e.g. 3 per brand |
field + min (+ from) | every present value gets at least min slots within the top from hits |
near_duplicate | drops hits whose text over fields is nearly identical to an earlier hit (threshold, default 0.9) |
mmr | re-orders the page by Maximal Marginal Relevance |
They run in this order: near-duplicate suppression, max caps, MMR, min
floors.
When a search finds nothing
zero_hits_fallback decides what an empty search returns:
| Value | Returns |
|---|---|
none (default) | the empty result |
broaden_query | the same search rerun without the caller's filter_by |
recommendation_model | the documents matching fallback_model_id's filter — set fallback_model_id too |
A fallback response carries the x-nova-fallback header with the value used.
Scheduling and targeting
starts_at/ends_at(date-time) — outside that window the rule set behaves as if it were not configured.device(desktop,mobile,tablet),language(a BCP-47 tag) andcountry(ISO 3166-1 alpha-2) restrict the rules to requests whosex_device,x_languageorx_countryquery parameter matches exactly.
See what a rule did
Add debug=true to a search, or send X-NOVA-DEBUG: 1. The response gains
_trace — its rule entry lists what applied (exclusion filter, compiled boost
clause, pins matched, redirect, banners, suggestions, diversity drops) — and
every hit an _explain object with the boosts it matched. Every search response
carries an X-Nova-Trace-Id header identifying it in the query log.
curl "https://api.aacsearch.com/v1/collections/products/documents/search?q=iphone&query_by=name&debug=true" \
-H "X-NOVA-API-KEY: $NOVA_API_KEY"Synonym sets
Synonyms live in named sets that collections and searches refer to:
| Operation | Endpoint |
|---|---|
upsertSynonymSet | PUT /synonym_sets/{synonymSetName} — create or replace the set |
upsertSynonymSetItem | PUT /synonym_sets/{synonymSetName}/items/{itemId} — one item |
retrieveSynonymSets, retrieveSynonymSet, retrieveSynonymSetItems, retrieveSynonymSetItem | read |
deleteSynonymSet, deleteSynonymSetItem | delete |
exportSynonymSet, importSynonymSet | GET …/export, POST …/import |
An item has an id and synonyms. Without root the words are
interchangeable; with root the mapping is one-way: a search for any word in
synonyms also finds root. locale picks the tokenizer, and
symbols_to_index keeps special characters that are otherwise dropped.
await client.synonyms.upsertSynonymSet({
synonymSetName: "catalog",
body: {
items: [
{ id: "drill", synonyms: ["drill", "driver", "screwdriver"] },
{ id: "tv", root: "television", synonyms: ["tv", "telly"] },
],
},
});A set takes effect where it is referenced:
-
On the collection —
updateCollectionwithsynonym_sets, so every search of the collection uses them:await client.collections.updateCollection({ collectionName: "products", body: { synonym_sets: ["catalog"] }, }); -
Per search — the
synonym_setssearch parameter (comma-separated names).
Per search, enable_synonyms=false turns synonyms off, synonym_prefix=true
matches synonyms on word prefixes, and synonym_num_typos allows them on
typo-corrected words.
Suggested synonyms
generateSynonymSuggestions
(POST /collections/{collectionName}/synonym_suggestions/generate) proposes
synonyms; listSynonymSuggestions lists them and acceptSynonymSuggestion /
rejectSynonymSuggestion decide. deleteSynonymSuggestion
(DELETE /synonym_suggestions/{suggestionId}) removes a suggestion without
accepting or rejecting it — for one that's stale or was raised in error. One of
its inputs is zero-hit queries from your
events: report found on search events so it
has them. Suggestions are paid in credits (see
keys and credits).
Stopword sets
A stopword set is a named list of words dropped from queries before matching —
useful for filler words your algorithmic tokenizer cannot know. A set has a
stopwords array and a locale that picks the tokenizer:
| Operation | Endpoint |
|---|---|
upsertStopwordsSet | PUT /stopwords/{setId} — create or replace |
retrieveStopwordsSets | GET /stopwords |
retrieveStopwordsSet | GET /stopwords/{setId} |
deleteStopwordsSet | DELETE /stopwords/{setId} |
await client.stopwords.upsertStopwordsSet({
setId: "noise-words",
body: { stopwords: ["купить", "недорого", "online"], locale: "ru" },
});A set applies where the search names it: pass the stopwords search parameter
with the set id on any search. The stopwords plan feature is on every tier
(see plans).
Stemming dictionaries
The stemmer reduces word forms to a root so "jackets" matches "jacket".
Algorithmic stemming is wrong for irregular words; a stemming dictionary fixes
exactly those cases with explicit word → root mappings.
| Operation | Endpoint |
|---|---|
listStemmingDictionaries | GET /stemming/dictionaries |
getStemmingDictionary | GET /stemming/dictionaries/{dictionaryId} |
importStemmingDictionary | POST /stemming/dictionaries/import — a JSONL file of word mappings |
await client.stemming.importStemmingDictionary({
id: "irregular-plurals",
body: '{"word": "people", "root": "person"}\n{"word": "feet", "root": "foot"}\n',
});Curation sets
A curation set holds per-query rules that include documents at positions,
exclude documents, replace the query or override sort_by. Search rules'
pins cover most per-query placement; reach for a curation set when a rule
needs several items with distinct positions, or should fire only on an exact
filter_by match.
| Operation | Endpoint |
|---|---|
upsertCurationSet | PUT /curation_sets/{curationSetName} — create or replace the set's description |
retrieveCurationSets | GET /curation_sets |
retrieveCurationSet | GET /curation_sets/{curationSetName} |
deleteCurationSet | DELETE /curation_sets/{curationSetName} |
upsertCurationSetItem | PUT /curation_sets/{curationSetName}/items/{itemId} — one rule |
retrieveCurationSetItems | GET /curation_sets/{curationSetName}/items |
retrieveCurationSetItem | GET /curation_sets/{curationSetName}/items/{itemId} |
deleteCurationSetItem | DELETE /curation_sets/{curationSetName}/items/{itemId} |
An item's rule decides when it applies — query with match: "exact" or
"contains", or filter_by matched verbatim against the caller's filter_by.
includes (id, position) and excludes (id) are per-query pins and
exclusions; filter_by, sort_by and replace_query override those parts of
the search; remove_matched_tokens strips the rule's own query words before
searching; metadata is returned verbatim on a match, for a banner or message
the storefront renders when the rule fires.
await client.curationSets.upsertCurationSet({
curationSetName: "black-friday",
body: { description: "Black Friday merchandising", items: [] },
});
await client.curationSets.upsertCurationSetItem({
curationSetName: "black-friday",
itemId: "drill-push",
body: {
rule: { query: "drill", match: "contains" },
includes: [{ id: "cordless-drill-42", position: 0 }],
metadata: { banner: "Black Friday: extra 10% off drills" },
},
});The console's curation page edits the same resource.
Presets, stopwords and stemming
Save reusable search parameters as a preset, exclude noise words with a stopwords set, and teach the tokenizer irregular word roots with a stemming dictionary.
Events and recommendations
Send behavioural events from the storefront, configure recommendation models on a collection, and fetch similar, bought-together, trending and personal recommendations.