Search and browse

One endpoint answers every search, category page and search box; send a link or a JSON body and read what comes back.

The Query API answers every search, category page and search box at one endpoint, /v1/search, on port 7800. It needs no key, so a storefront calls it from the browser or from its own server:

Terminal
curl -s 'http://127.0.0.1:7800/v1/search?query=sofa' | jq -c '[.sections[] | select(.type == "product") | .hits[].entity]'
200 · 5.4 ms · release b25a6aaa
["product:SOFA-ASKA-2","product:SOFA-LUND-3","product:SOFA-NIKO-SCHLAF","product:SOFA-VIDAR-ECK"]

on says where a request comes from: search by default, category_page for a category page, autocomplete for the search box. Rules can tell them apart.

Send a request

GET takes the plain fields as URL parameters, so a link is enough. POST takes the whole request as JSON, and only it carries filters, dismissed, facets and context:

Terminal
curl -s http://127.0.0.1:7800/v1/search -H 'Content-Type: application/json' \
  -d '{ "query": "sofa", "filters": { "color": ["grey"] } }' | jq -c '.filters[] | {where, source}'
200 · 4.9 ms · release b25a6aaa
{"where":{"color":["grey"]},"source":"shopper"}
{"where":{"category":{"within":"wohnen/sofas"}},"source":"query"}

Every filter that narrowed the products comes back with its source: the shopper chose grey, and the query named the sofas. Facets and filters says what filters can choose.

Reference: POST /v1/search · GET /v1/search

Sections and hits

An answer holds one section per entity type, in the order of ranking.json's sections. listed names the type the page lists:

Terminal
curl -s 'http://127.0.0.1:7800/v1/search?query=sofa&per_page=2' \
  | jq -c '.listed, (.sections[] | {type, total, hits: [.hits[].entity]})'
200 · 4.6 ms · release b25a6aaa
"product"
{"type":"category","total":1,"hits":["category:wohnen/sofas"]}
{"type":"product","total":4,"hits":["product:SOFA-ASKA-2","product:SOFA-LUND-3"]}
{"type":"content","total":2,"hits":["content:guide-sofa-finden","content:guide-teppichgroesse"]}
{"type":"brand","total":0,"hits":[]}
  • The listed section pages through its results and carries the facets.
  • The sections beside it, here a category and two guides, come only when something was typed on a search or in the search box. They show up to 6 hits and do not page.
  • Each hit names its entity, the variant a tile shows, and fields: its type's result fields from types.json, in the request's locale and channel.

release and snapshot name the release and the snapshot that answered. query_id names the answer itself: keep it for the clicks and views you report. A query that names a whole page also carries a redirect (URLs and redirects).

Reference: sections · hits · ranking.json sections · result

Pages and totals

page and per_page (24 by default, up to 100) page through the listed section, and its total counts every match:

Terminal
curl -s 'http://127.0.0.1:7800/v1/search?query=sofa&per_page=2&page=2' \
  | jq -c '.sections[] | select(.type == "product") | {total, reachable, hits: [.hits[].entity]}'
200 · 3.9 ms · release b25a6aaa
{"total":4,"reachable":4,"hits":["product:SOFA-NIKO-SCHLAF","product:SOFA-VIDAR-ECK"]}

reachable says how many of them pages reach: all of them, or the first 1,000 where more match. A total marked upper_bound may be higher than the true count, where a range meets a value that differs between a tile's variants. The trace's assemble step says which range.

Reference: per_page · reachable · upper_bound

Count without hits

A filter sheet's "Show 2 results" button needs the total alone. count_only answers the listed section's total, without hits, other sections or facets:

Terminal
curl -s http://127.0.0.1:7800/v1/search -H 'Content-Type: application/json' \
  -d '{ "query": "sofa", "filters": { "color": ["grey"] }, "count_only": true }' | jq -c '.sections'
200 · 3.2 ms · release b25a6aaa
[{"type":"product","total":2,"reachable":2,"hits":[]}]

Facets named in facets still come along, as a sheet that counts its own values needs.

Reference: count_only · facets

Channel, locale and context

A request is answered in one channel and one locale, by default the release's first channel and its first locale. The home store's channel de sells in German and English:

Terminal
curl -s 'http://127.0.0.1:7800/v1/search?query=sofa&locale=en&per_page=2' \
  | jq -c '[.sections[] | select(.type == "product") | .hits[].fields.title]'
200 · 4.4 ms · release b25a6aaa
["Aska two-seater velvet sofa","Lund three-seater sofa"]

Titles, labels, URLs and prices follow them. context carries the keys channels.json declares, such as { "customer_group": "b2b" }, which rules can match on.

Reference: channel · locale · context

Errors

A request that cannot be answered as sent gets a 400, whose violations name each wrong field by its JSON pointer:

Terminal
curl -s 'http://127.0.0.1:7800/v1/search?query=sofa&sort=cheapest' | jq -c '.violations[]'
400 · 1.3 ms
{"pointer":"/sort","message":"\"cheapest\" is not a sort option; the options are relevance, price_asc, price_desc, newest and delivery_time."}

An unknown channel, filter field or context value is refused the same way. An unknown category is not an error: it lists nothing. A 503 means nothing is searchable yet or Meilisearch is unreachable.

Reference: Problems

The trace

debug=1 adds the trace: every step the search took, in order, each with what it decided:

Terminal
curl -s 'http://127.0.0.1:7800/v1/search?query=graues%20Sofa&debug=1' | jq -c '[.trace.steps[].step]'
200 · 5.6 ms · release b25a6aaa
["normalize","resolve","rules","sort","plan","execute","facets","rank","assemble"]

The query is read and resolved, rules and the sort are decided, and one call asks Meilisearch. The facets are counted, the hits ranked and the answer assembled. A search that names something and finds nothing adds relax steps (When nothing is found).

timings says how long the Query API took, measured at the server. Each request is held to the budget of its kind: 30 ms at p95 for a search or a category page, 15 ms for autocomplete. Building the trace costs a little time of its own.

Reference: The trace · Timings

Next

On this page