Search
Full-text search with support for filters, faceted search, sorting, highlighting, and hybrid vector search.
/v1/searchSearch an index.
{
"index": "products",
"q": "macbook",
"limit": 10,
"offset": 0,
"filter": "price < 2000",
"sort": ["price:asc"],
"facets": ["category"],
"attributesToHighlight": ["title"],
"attributesToRetrieve": ["id", "title", "price"]
}{
"hits": [
{
"id": 1,
"title": "MacBook Pro",
"price": 1999,
"_formatted": {
"title": "<em>MacBook</em> Pro"
}
}
],
"query": "macbook",
"processingTimeMs": 2,
"estimatedTotalHits": 42,
"facetDistribution": {
"category": { "laptops": 12, "phones": 30 }
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
index | string | Required | The index uid to search. |
q | string | "" | The search query string. Empty string returns all documents. |
limit | number | 20 | Maximum number of hits to return. Above what the engine returns at once, it is lowered and the answer says so (Paging and Totals, below). |
offset | number | 0 | Number of hits to skip (for pagination). |
page, hitsPerPage | number | 1, 20 | Page mode, on every engine: send either one instead of limit and offset. page starts at 1. The answer then has page, hitsPerPage, totalHits and totalPages. |
filter | string | "" | Filter expression, e.g. "price < 2000 AND category = laptops". |
sort | string[] | [] | Sort order, e.g. ["price:asc", "title:desc"]. |
facets | string[] | [] | Attributes to compute facet distributions for. |
attributesToHighlight | string[] | [] | Attributes to wrap matches with <em> tags. |
attributesToRetrieve | string[] | ["*"] | Attributes to include in each hit. Defaults to all. |
attributesToCrop | string[] | [] | Attributes whose values will be cropped around matches. |
cropLength | number | 10 | Number of words around a match when cropping. Visor crops to 20 words and refuses any other value with 400 feature_not_supported. |
showMatchesPosition | boolean | false | Include match position information in each hit. Visor has no match positions and refuses true with 400 feature_not_supported. |
showRankingScore | boolean | false | Include the ranking score in each hit. |
matchingStrategy | string | "last" | "last", "all", or "frequency". Meilisearch only: see Matching Every Word below. Visor matches as "last" does and refuses the other two with 400 feature_not_supported. Typesense and Elasticsearch ignore it. |
hybrid | object | null | Enable hybrid search. Example: { semanticRatio: 0.5, embedder: "default" }. |
vector | number[] | null | Vector for nearest-neighbor search (used with hybrid or standalone). |
mode | string | "instant" | Visor only: "instant" (typo-tolerant, as-you-type) or "retrieval" (BM25F, for longer queries, with no sort), on the same index. Other engines ignore it. Visor refuses hybrid and vector with 400 feature_not_supported. |
experimentKey, variant | string | none | Tag this search with an A/B test variant, for Analytics > Experiments. Send both or neither. See A/B Test Engines. |
sessionId | string | none | An anonymous visitor or session id, stored with the search for experiment results. |
A search key returns every field of each hit unless you ask for fewer. Anyone with the key can ask for all of them, so do not index private fields. See What a Search Key Can Read. To search several indexes in one call, see Multi-Search.
Each search that answers counts as one search on your plan. Searches you run from the dashboard's Search tab are not counted or billed, though they still show in Analytics.
Matching Every Word
On Meilisearch, a query of several words does not need every word to match. The default, "last", drops words from the end until enough documents match, and typo tolerance then applies to what is left. The best matches still come first, but the total and the later pages fill up with weaker ones. On a test index, "beats headphones" matched 5 documents with the default: the two Beats headphones, a Beats speaker, "Vacuum belts" and "Coffee beans". With "all" it matched only the two headphones.
{
"index": "products",
"q": "beats headphones",
"matchingStrategy": "all"
}Use "all" for product and catalog search, where a shopper means every word. "frequency" drops the most common words first.
Paging and Totals
Each engine stops somewhere. The answer tells you where, with fields that appear only when they apply.
| Field | When it appears | What it means |
|---|---|---|
limitCapped | Your limit or hitsPerPage was more than the engine returns at once | true. The answer also has the limit (or hitsPerPage) actually used: 250 on Typesense, 10,000 on Elasticsearch, 1000 on Visor, and the index's pagination.maxTotalHits on Meilisearch (1000 by default). |
maxTotalHits | The total is more than paging can reach | The hits paging stops at: the index's pagination.maxTotalHits on Meilisearch, 1000 on Visor, 10,000 on Elasticsearch. Pages past it come back empty. |
totalHitsExact | The total is a lower bound | false. Elasticsearch stops counting at 10,000, so the true total may be higher. |
page, hitsPerPage, totalHits, totalPages | You sent page or hitsPerPage | The page you got and its size. totalPages counts only pages that can be fetched, so every page it counts has hits. |
{ "index": "products", "q": "", "page": 3, "hitsPerPage": 20 }
{
"hits": [ ... ],
"page": 3,
"hitsPerPage": 20,
"totalHits": 10002,
"totalPages": 50,
"maxTotalHits": 1000
}Build page links from totalPages, not from the total. On Meilisearch you can raise the limit with the pagination.maxTotalHits setting. To read every document, use the cursor on GET /v1/documents with an admin key.
Search Errors
A search the engine cannot run answers 400 with a code, and a message that names the field and the fix. Index names in messages are your index uid, never the engine's internal name.
| Status and code | Cause |
|---|---|
| 400 invalid_search_filter | A filter on a field that is not filterable. On Elasticsearch, a filter on a field the index has no document with. |
| 400 invalid_search_facets | A facet on a field that is not filterable. On Elasticsearch, a facet on a text field: facet on <field>.keyword. |
| 400 invalid_search_sort | A sort on a field that is not sortable. On Elasticsearch, a sort on a text field (sort on <field>.keyword) or on a field the index does not have. |
| 400 invalid_search_offset | Elasticsearch: offset plus limit is past 10,000. Narrow the search, or list documents with a cursor. |
| 400 invalid_search_page | page is not a whole number of 1 or more. |
| 400 invalid_search_hits_per_page | hitsPerPage is not a whole number of 0 or more. |
| 400 index_required | The body has no index. |
| 400 invalid_json | The body is not valid JSON. |
| 404 index_not_found | No index with that uid is visible to the key. |
{
"error": "Can't facet on `category`: it isn't a filterable attribute of this index. Add it to filterableAttributes in Tune (or with PATCH /v1/indexes/{uid}/settings), then search again.",
"code": "invalid_search_facets"
}In multi-search, a query that fails gets the same error and code in its own place in results, and the other queries still answer. All codes are on the Errors page.
Filters
Filter expressions let you narrow search results. Attributes must be listed in filterableAttributes in your index settings before they can be used in filters. That is the rule on Meilisearch and Visor. Typesense and Elasticsearch filter on every field without it, but on Elasticsearch an exact match on a text field needs <field>.keyword. See Index Into More Than One Engine.
// Simple comparison
"price < 2000"
// Equality
"category = laptops"
// Combined with AND / OR
"price < 2000 AND category = laptops"
"category = laptops OR category = phones"
// IN operator
"category IN [laptops, phones, tablets]"
// Negation
"NOT category = accessories"
// Nested with parentheses
"(price < 500 OR price > 2000) AND category = laptops"Facets
Pass attribute names in the facets array to receive a distribution of values for each attribute. Useful for building filter UIs with counts.
"facetDistribution": {
"category": {
"laptops": 12,
"phones": 30,
"tablets": 8
}
}Sorting
Sort results by one or more attributes. Attributes must be listed in sortableAttributes. Use the format attribute:direction where direction is asc or desc.
"sort": ["price:asc", "title:desc"]Highlighting
Request highlighted versions of attributes to show which parts of the text matched the query. Matched text is wrapped in <em> tags.
"_formatted": {
"title": "<em>MacBook</em> Pro 16-inch"
}