# 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](/docs/reference/glossary#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:

```sh title="Terminal"
curl -s 'http://127.0.0.1:7800/v1/search?query=sofa' | jq -c '[.sections[] | select(.type == "product") | .hits[].entity]'
```

```json title="Answer: 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](/docs/category-pages), `autocomplete` for the [search box](/docs/autocomplete). 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`:

```sh title="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}'
```

```json title="Answer: 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](/docs/facets) says what `filters` can choose.

**Reference:** [`POST /v1/search`](/docs/reference/query-api#search) · [`GET /v1/search`](/docs/reference/query-api#search-by-url)

## Sections and hits

An answer holds one [section](/docs/reference/glossary#section) per entity type, in the order of `ranking.json`'s `sections`. `listed` names the type the page lists:

```sh title="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]})'
```

```json title="Answer: 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](/docs/reference/glossary#facet).
- **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](/docs/reference/glossary#release) and the [snapshot](/docs/reference/glossary#snapshot) that answered. `query_id` names the answer itself: keep it for the [clicks and views](/docs/clicks-and-views) you report. A query that names a whole page also carries a `redirect` ([URLs and redirects](/docs/urls)).

**Reference:** [`sections`](/docs/reference/query-api#search.answer.sections) · [`hits`](/docs/reference/query-api#search.answer.sections.hits) · [`ranking.json` sections](/docs/reference/release-files/ranking#sections) · [`result`](/docs/reference/release-files/types#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:

```sh title="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]}'
```

```json title="Answer: 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.

<Callout type="warn">
    A page past `reachable` answers no hits. Link no page past it, and tell the shopper that filters reach the rest.
</Callout>

**Reference:** [`per_page`](/docs/reference/query-api#search.per_page) · [`reachable`](/docs/reference/query-api#search.answer.sections.reachable) · [`upper_bound`](/docs/reference/query-api#search.answer.sections.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:

```sh title="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'
```

```json title="Answer: 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`](/docs/reference/query-api#search.count_only) · [`facets`](/docs/reference/query-api#search.facets)

## Channel, locale and context

A request is answered in one [channel](/docs/reference/glossary#channel) and one [locale](/docs/reference/glossary#locale), by default the release's first channel and its first locale. The home store's channel `de` sells in German and English:

```sh title="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]'
```

```json title="Answer: 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`](/docs/reference/query-api#search.channel) · [`locale`](/docs/reference/query-api#search.locale) · [`context`](/docs/reference/release-files/channels#context)

## Errors

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

```sh title="Terminal"
curl -s 'http://127.0.0.1:7800/v1/search?query=sofa&sort=cheapest' | jq -c '.violations[]'
```

```json title="Answer: 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](/docs/reference/problems)

## The trace

`debug=1` adds the [trace](/docs/reference/glossary#trace): every step the search took, in order, each with what it decided:

```sh title="Terminal"
curl -s 'http://127.0.0.1:7800/v1/search?query=graues%20Sofa&debug=1' | jq -c '[.trace.steps[].step]'
```

```json title="Answer: 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](/docs/no-results)).

`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](/docs/reference/trace) · [`Timings`](/docs/reference/trace#Timings)

## Next

- [How search understands a query](/docs/query-understanding): what the query named, and how the rest is matched.
- [Facets and filters](/docs/facets): the filters a page offers and how a request chooses them.
- [The client](/docs/client): the same requests from TypeScript.
