# Get search suggestions

URL: /docs/api/storefront/search/get-search-suggestions

Grouped suggestions for the typed query: products, collections, brands, tags, CMS pages and
posts (only the groups asked for in `types` that the store has enabled, each with its
merchant limit). `*_html` fields are escaped text plus `<mark>`/`</mark>` only. Below the
store's `min_chars` the answer is 200 with empty groups; with search switched off it is 200
with no groups and `meta.search_engine: "disabled"`. When Meilisearch is unavailable the
answer is still 200: products from the database, the other groups empty, `meta.search_engine:
"database"`, `Cache-Control: no-store, private` (never cached). A searched answer carries
`X-Search-Total` (products total) and a fresh `X-Search-Id` (ULID) for the search beacons,
even when served from the response cache (kept 300 s, dropped on every index write).

## GET /v1/search/suggest

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

Grouped suggestions for the typed query: products, collections, brands, tags, CMS pages and
posts (only the groups asked for in `types` that the store has enabled, each with its
merchant limit). `*_html` fields are escaped text plus `<mark>`/`</mark>` only. Below the
store's `min_chars` the answer is 200 with empty groups; with search switched off it is 200
with no groups and `meta.search_engine: "disabled"`. When Meilisearch is unavailable the
answer is still 200: products from the database, the other groups empty, `meta.search_engine:
"database"`, `Cache-Control: no-store, private` (never cached). A searched answer carries
`X-Search-Total` (products total) and a fresh `X-Search-Id` (ULID) for the search beacons,
even when served from the response cache (kept 300 s, dropped on every index write).

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `q` | query | string | yes | The typed text, 1–200 characters after trimming. Shorter than the store's `min_chars`: 200 with empty groups, nothing searched. |
| `locale` | query | string (nullable) |  | A store language code as listed in `locales[].code` (e.g. `en`, `fr`). Missing or unknown: the store default; `meta.locale` says which was used. |
| `types` | query | string (nullable) |  | Comma-separated subset of `products,collections,brands,tags,pages`. Missing: every group the store has enabled. Unknown or disabled values are ignored. |
| `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**: Success.
- **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
- **429**: `RATE_LIMITED`: per store and shopper IP (`X-Client-IP` with a valid `X-Tenant-Secret`). Keep the results you have and send nothing more until `Retry-After` has passed.