# Search products

URL: /docs/api/storefront/search/search-products

Full product cards for a results page. For the same `q` and `locale` with no filters,
`meta.total` equals the suggest endpoint's `data.products.total`. An empty `q` with a filter
is a browse (e.g. every product with a tag). `meta.facets` counts the store's facets
(Meilisearch only; empty on the database fallback), `meta.facet_labels` names them and
`meta.facet_value_labels` names identifier values (`brand_slug` => brand names). Prices
in filters are integer minor units of `meta.currency`. Every 200 carries `X-Search-Total`
(equal to `meta.total`) and a fresh `X-Search-Id` header (a ULID) for click beacons, cached
body or not.

## GET /v1/search

```http
GET https://api.elaraone.app/api/v1/search
```

Full product cards for a results page. For the same `q` and `locale` with no filters,
`meta.total` equals the suggest endpoint's `data.products.total`. An empty `q` with a filter
is a browse (e.g. every product with a tag). `meta.facets` counts the store's facets
(Meilisearch only; empty on the database fallback), `meta.facet_labels` names them and
`meta.facet_value_labels` names identifier values (`brand_slug` => brand names). Prices
in filters are integer minor units of `meta.currency`. Every 200 carries `X-Search-Total`
(equal to `meta.total`) and a fresh `X-Search-Id` header (a ULID) for click beacons, cached
body or not.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `q` | query | string (nullable) |  | Free-text search term. Empty or missing lists every published product. |
| `locale` | query | string (nullable) |  | A store language code (`GET search/config` `locales[].code`, e.g. `en`, `fr`). Unknown or missing: the store default. `meta.locale` reports the locale actually used. |
| `live` | query | string (nullable) |  | `1` on the results page's update-as-you-type keystrokes: answered as usual, never recorded as a search. Omit it on the request the shopper settles on. |
| `filter[collection]` | query | string (nullable) |  | Collection slug. |
| `filter[brand]` | query | string (nullable) |  | Brand slug. An unknown slug matches no product. |
| `filter[tag]` | query | string (nullable) |  | Tag value, matched exactly (letter case ignored). |
| `filter[in_stock]` | query | string (nullable) |  | `1`: only products in stock. `0` or missing: no stock constraint. |
| `filter[on_sale]` | query | string (nullable) |  | `1`: only products on sale — a sale price for guests or a compare-at price above the price (the `on_sale` facet counts the same). `0` or missing: no constraint. |
| `filter[price_min]` | query | integer (nullable) |  | Integer minor units of the store currency (never a decimal amount). Compared with the regular price, or the sale price when the store's search settings say so. |
| `filter[price_max]` | query | integer (nullable) |  |  |
| `filter[attr][]` | query | array (nullable) |  |  |
| `X-Client-IP` | header | string |  | Optional. IP address of the shopper (one IPv4/IPv6, no port, no list). Honoured only together with a valid X-Tenant-Secret; ignored otherwise. Used for rate limits and recorded with form submissions. |
| `X-Tenant-Secret` | header | string |  | Optional here. Server secret of the store: server-to-server only, never from a browser. Enables X-Client-IP. |

### Responses

- **200**: A page of results, with `links` and `meta`.
- **402**: Payment required. Branch on `error.code`: - `TENANT_PAYMENT_REQUIRED`: this store is on payment hold. Show a "store temporarily unavailable" page; do not retry the request.
- **422**: Validation error