Search
Results pages, instant search suggestions, the merchant's search settings, the anonymous search events beacon, synonyms and search rate limits.
Every store has a search index that ranks results, tolerates typos, and stays in step with the catalogue. Four endpoints cover it: a results page, suggestions as the shopper types, the merchant's search settings, and a beacon that records what shoppers click.
| Endpoint | Use |
|---|---|
GET /v1/search | The results page: products, facets and filters |
GET /v1/search/suggest | Instant suggestions as the shopper types |
GET /v1/search/config | The merchant's search settings for your search box |
POST /v1/search/events | Anonymous click and add-to-basket events from search |
Search answers are never personalised. Call these endpoints from your storefront server without a bearer token or cart token.
The results page
GET /v1/search takes a query q and the same perPage, page and include parameters as the product list. An empty q lists every published product, which is useful for brand and tag pages.
| Parameter | Matches |
|---|---|
q | The search text. Whitespace is trimmed and collapsed. |
locale | A store language code from GET /v1/search/config. Unknown or missing means the store's default. Always send it; the API does not read Accept-Language. |
filter[collection] | A collection slug |
filter[brand] | A brand slug. An unknown slug returns no products. |
filter[tag] | A tag value, exactly, ignoring case |
filter[in_stock] | 1 for only products in stock |
filter[on_sale] | 1 for only products on sale |
filter[price_min], filter[price_max] | Integer minor units of the store's currency, never decimals |
filter[attr][handle] | A filterable attribute's value |
sort | name, -name, price, -price or -created_at. Leave it out to sort by relevance. |
Malformed parameters, such as a decimal price or filter[in_stock]=maybe, return 422 VALIDATION_FAILED.
curl -G https://api.elaraone.app/api/v1/search \
-H "X-Tenant-Key: $ELARAONE_TENANT_KEY" \
-H "X-Tenant-Secret: $ELARAONE_TENANT_SECRET" \
-H "X-Client-IP: 203.0.113.7" \
--data-urlencode "q=oak lamp" \
--data-urlencode "locale=en" \
--data-urlencode "filter[in_stock]=1" \
--data-urlencode "include=variants.prices,images"Besides the usual pagination fields, meta carries:
total: the number of matching products;localeandcurrency: what the answer was searched and priced in;facets: counts per value for each facet, such ascollection_slugs,brand_slug,tags,in_stock,on_saleandattr_{handle}for each filterable attribute;facet_labels: the heading to show for each facet, in display order;facet_value_labels: display names for values that are identifiers, such as brand slugs;search_engine:meilisearchnormally, ordatabasewhen the index is briefly unavailable. The answer is still a200with products, just without facets: render what you get.
Each successful answer also carries an X-Search-Id response header. Keep it to report clicks (below).
Instant suggestions
GET /v1/search/suggest returns grouped suggestions for a partly typed query.
GET /api/v1/search/suggest?q=oak%20de&locale=en&types=products,collections,pages| Parameter | Rules |
|---|---|
q | Required, 1 to 200 characters after trimming. Shorter than the store's min_chars returns empty groups without searching. |
locale | As for GET /v1/search |
types | A comma-separated subset of products, collections, brands, tags, pages and posts. Missing means every type the merchant has switched on. |
{
"data": {
"query": "oak de",
"products": {
"total": 3,
"hits": [
{
"id": 101,
"slug": "oak-desk-lamp",
"name": "Oak desk lamp",
"name_html": "<mark>Oak</mark> <mark>de</mark>sk lamp",
"image": { "url": "https://…/lamp-small.webp", "url_medium": "https://…/lamp-medium.webp", "alt": "Oak desk lamp" },
"price": { "amount": 8900, "currency": "GBP", "formatted": "£89.00" },
"price_max": null,
"sale_price": null,
"on_sale": false,
"in_stock": true,
"default_variant_id": 1543
}
]
},
"collections": { "total": 0, "hits": [] }
},
"meta": { "search_engine": "meilisearch", "locale": "en", "currency": "GBP" }
}- A group is present only for types you asked for and the merchant has switched on. Treat a missing group as empty, and render groups in the order of
autocomplete.typesfrom the config. name_htmlandtitle_htmlcontain escaped text plus<mark>tags around the matched parts. Render them by splitting on<mark>and</mark>into text, never by injecting HTML.default_variant_idis set when the product has exactly one variant you can buy, so an "Add to basket" button can add it directly.- Cancel the previous request when a new keystroke arrives, and keep the current suggestions on screen while the next ones load.
Search settings
GET /v1/search/config?locale=en returns the merchant's choices from Search settings in the admin: whether search and suggestions are on, min_chars, debounce_ms, which suggestion types to show and how many of each, popular searches, layout, colours, the placeholder and the no-results text. Use it instead of hard-coding these values.
The response carries an ETag and Cache-Control: max-age=60, private. Cache it for 60 seconds on your server and revalidate with If-None-Match; a 304 means nothing changed. meta.version changes whenever the merchant saves, so clear your suggestion cache when it does.
The search events beacon
POST /v1/search/events records what shoppers do with search results, so the merchant can see which searches lead to clicks and sales. Send events in batches of 1 to 20:
{
"events": [
{ "type": "settled", "search_id": "01JB8Z6Q4M9W2C7XK3T5VYHNPR", "trigger": "idle" },
{ "type": "click", "search_id": "01JB8Z6Q4M9W2C7XK3T5VYHNPR", "item_type": "product", "item_id": "101", "position": 1 },
{ "type": "add_to_cart", "search_id": "01JB8Z6Q4M9W2C7XK3T5VYHNPR", "product_id": 101 }
]
}| Event | Send when | Fields |
|---|---|---|
settled | The shopper stops typing in the suggestions box (idle), leaves it (blur) or presses Enter (enter) | trigger |
click | The shopper chooses a result | item_type (product, collection, brand, tag, page or post), item_id as a string, position from 1 |
add_to_cart | The shopper adds a product straight from search | product_id |
The answer is always 202 Accepted with { "data": { "accepted": n } }. Events for an unknown or expired search id (ids last 15 minutes) are dropped quietly. Do not send settled for the results page: the engine records results-page searches itself.
No shopper identifiers. The beacon carries only the search id and what was chosen. Never send the query, customer ids or anything personal; the engine looks the query up from the search id and keeps only daily totals per query and per item. A 422 means a client bug: fix it rather than retrying. A 429 drops the batch; do not retry it.
Synonyms
Merchants teach search their own words under Search settings → Synonyms in the admin, with no code needed:
- Multi-way synonyms make words equal: searching any of "sofa", "settee" and "couch" finds the others.
- One-way synonyms widen one word only: "tableware" can also find "mug" and "tray" without "mug" finding everything that is tableware.
A store can have up to 1,000 rules, each with up to 20 terms, and can import and export them as CSV. Changes apply to suggestions and results alike.
Rate limits
| Endpoint | Budget |
|---|---|
GET /v1/search/suggest | 1,200 requests a minute per store and shopper IP |
POST /v1/search/events | 300 batches a minute per store and shopper IP |
GET /v1/search, GET /v1/search/config | The store's general API budget |
Suggestions and events have their own budgets, so typing never uses up the general one. The budgets are per shopper only when your server sends X-Client-IP with the server secret; otherwise every shopper shares your server's IP. On a 429, keep the current results and wait for Retry-After. See rate limits.
Payments
Take card payments through the store's own Stripe account and PayPal, handle test and live mode, and turn declines into clear messages.
Responses and errors
The success and error envelopes, every error code with its HTTP status and what to do, the shape of validation errors, and which errors to retry.