> ## Documentation Index
> Fetch the complete documentation index at: https://docs.omneo.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Running a search

> Search parameters, filter expressions, pagination, and facets for querying an Omneo Discovery index.

Send a search to the [Discovery](/concepts/platform-surfaces/omneo-discovery) host with the index name in the path and the query in a JSON body.

```bash theme={null}
POST https://discovery.[tenant].getomneo.com/indexes/[index]/search
```

| Header          | Value                   |
| --------------- | ----------------------- |
| `Authorization` | `Bearer [search token]` |
| `Content-Type`  | `application/json`      |

The search token must cover the index you are querying. See [Minting search tokens](/dev-guides/discovery/search-tokens).

```json theme={null}
{
  "q": "jordan",
  "filter": "preferred_location_id = 12",
  "sort": ["last_name:asc"],
  "limit": 10
}
```

## Search parameters

| Parameter                              | Type            | Description                                                                                                  |
| -------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------ |
| `q`                                    | string          | The search terms. Matches across every searchable attribute on the index.                                    |
| `filter`                               | string or array | A filter expression over the index's filterable attributes. See [Filter expressions](#filter-expressions).   |
| `sort`                                 | array           | Sort order over the index's sortable attributes, for example `["last_name:asc"]`.                            |
| `limit`                                | number          | Maximum hits to return. Defaults to `20`.                                                                    |
| `offset`                               | number          | Number of hits to skip. Use with `limit` for offset pagination.                                              |
| `page`                                 | number          | Page number, 1-based. Switches the response to page-based pagination.                                        |
| `hitsPerPage`                          | number          | Hits per page when using `page`. Defaults to `20`.                                                           |
| `facets`                               | array           | Attributes to return counts for, for example `["brand", "status"]`.                                          |
| `attributesToRetrieve`                 | array           | Restricts which attributes appear in each hit. Defaults to all.                                              |
| `attributesToSearchOn`                 | array           | Restricts which searchable attributes `q` is matched against. Defaults to all.                               |
| `attributesToHighlight`                | array           | Wraps the matched terms in the named attributes and returns them under `_formatted`.                         |
| `highlightPreTag` / `highlightPostTag` | string          | The tags wrapped around highlighted matches. Default to `<em>` and `</em>`.                                  |
| `attributesToCrop`                     | array           | Returns a shortened window of the named attributes around the match, under `_formatted`.                     |
| `cropLength`                           | number          | Number of words kept by `attributesToCrop`. Defaults to `10`.                                                |
| `matchingStrategy`                     | string          | `last` (default) drops query words from the end until results are found. `all` requires every word to match. |
| `showMatchesPosition`                  | boolean         | Adds `_matchesPosition` to each hit, giving the character offsets of each match.                             |

An empty `q` is valid. Sending only a `filter`, or neither, returns documents in ranking order, which is what you want for browsing views and filter-driven pickers.

## Filter expressions

A filter names a filterable attribute, an operator, and a value. Only attributes marked filterable on the index can be used. See [Discovery indexes](/dev-guides/discovery/indexes) for how to find them.

```text theme={null}
status = 'active'
```

| Operator          | Meaning                                             | Example                       |
| ----------------- | --------------------------------------------------- | ----------------------------- |
| `=`               | Equal to.                                           | `status = 'active'`           |
| `!=`              | Not equal to.                                       | `status != 'archived'`        |
| `>` `>=` `<` `<=` | Numeric and date comparison.                        | `item_count >= 3`             |
| `TO`              | Inclusive range.                                    | `item_count 1 TO 10`          |
| `IN`              | Matches any value in the list.                      | `brand IN ['Acme', 'Globex']` |
| `EXISTS`          | The attribute is present on the document.           | `external_id EXISTS`          |
| `IS NULL`         | The attribute is present and null.                  | `external_id IS NULL`         |
| `IS EMPTY`        | The attribute is an empty string, array, or object. | `tags IS EMPTY`               |
| `NOT`             | Negates the expression that follows.                | `NOT brand IN ['Acme']`       |

Combine expressions with `AND` and `OR`, and group them with parentheses:

```text theme={null}
status = 'active' AND brand = 'Acme'
brand IN ['Acme', 'Globex'] AND (status = 'active' OR status = 'draft')
NOT tags IS EMPTY AND item_count > 0
```

Quote string values with single or double quotes. Quoting is required when the value contains a space, a quote, or a reserved word. Numbers and booleans are not quoted.

`filter` also accepts an array. Elements of the outer array are combined with `AND`, and elements of a nested array are combined with `OR`. These two are equivalent:

```json theme={null}
{ "filter": "status = 'active' AND (brand = 'Acme' OR brand = 'Globex')" }
```

```json theme={null}
{ "filter": ["status = 'active'", ["brand = 'Acme'", "brand = 'Globex'"]] }
```

<Note>
  Discovery rejects a filter on an attribute that is not filterable with a `400`. This is the opposite of the [Omneo API list endpoints](/dev-guides/core-setup/filtering-and-sorting), which silently ignore an unrecognised filter attribute. A typo in a Discovery filter fails loudly.
</Note>

## Sorting

`sort` takes an array of `attribute:direction` strings, applied in order. The attribute must be sortable on the index.

```json theme={null}
{ "q": "", "sort": ["last_name:asc", "first_name:asc"], "limit": 50 }
```

Without `sort`, hits come back in relevance order for the given `q`.

## Pagination

Two modes are available, and the response shape differs between them.

| Mode   | Parameters            | Response fields                                  |
| ------ | --------------------- | ------------------------------------------------ |
| Offset | `limit`, `offset`     | `estimatedTotalHits`, `limit`, `offset`          |
| Page   | `page`, `hitsPerPage` | `totalHits`, `totalPages`, `hitsPerPage`, `page` |

Use offset pagination for infinite scroll and typeahead, where an approximate total is fine and later pages are rarely reached. Use page pagination when you need to render exact page numbers or a total count, which costs a little more to compute.

```json theme={null}
{ "q": "jordan", "page": 2, "hitsPerPage": 20 }
```

## Facets

Request `facets` to get the distinct values of an attribute and how many documents carry each one. This is how you populate filter menus, and how you discover what values an attribute can hold.

```json theme={null}
{ "q": "", "facets": ["brand", "status"], "limit": 0 }
```

The response adds `facetDistribution`:

```json theme={null}
{
  "hits": [],
  "facetDistribution": {
    "brand": { "Acme": 128, "Globex": 47 },
    "status": { "active": 160, "draft": 15 }
  },
  "estimatedTotalHits": 175
}
```

Facet counts respect the current `q` and `filter`, so they narrow as the user refines the search. Only filterable attributes can be requested as facets.

## Response shape

```json theme={null}
{
  "hits": [
    {
      "id": "43219",
      "first_name": "Jordan",
      "last_name": "Reyes",
      "email": "jordan.reyes@example.com",
      "preferred_location_id": "12"
    }
  ],
  "query": "jordan",
  "processingTimeMs": 1,
  "limit": 10,
  "offset": 0,
  "estimatedTotalHits": 42
}
```

`hits` holds the matching documents, each shaped by that index's attributes. `processingTimeMs` is the search time on the Discovery host and excludes network time. `estimatedTotalHits` is an approximation that improves as more of the result set is scanned, so treat it as a "roughly this many" figure rather than an exact count. Use page pagination when you need an exact `totalHits`.

## Searching several indexes at once

Post to `/multi-search` to run queries against different indexes in a single round trip, for example a combined profile and product search behind one search box.

```bash theme={null}
curl -X POST "https://discovery.[tenant].getomneo.com/multi-search" \
  -H "Authorization: Bearer ${SEARCH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "queries": [
      { "indexUid": "profiles", "q": "jordan", "limit": 5 },
      { "indexUid": "products", "q": "jordan", "limit": 5 }
    ]
  }'
```

The response carries a `results` array, one entry per query, each in the shape above with its `indexUid` included. The search token must cover every index named in `queries`.

## curl example

```bash theme={null}
curl -X POST "https://discovery.[tenant].getomneo.com/indexes/products/search" \
  -H "Authorization: Bearer ${SEARCH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "q": "running shoe",
    "filter": "status = '\''active'\'' AND brand IN ['\''Acme'\'', '\''Globex'\'']",
    "sort": ["title:asc"],
    "limit": 10
  }'
```

## JavaScript example

```js theme={null}
const DISCOVERY_HOST = 'https://discovery.[tenant].getomneo.com';

export async function search(token, index, query, options = {}) {
  const response = await fetch(`${DISCOVERY_HOST}/indexes/${index}/search`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ q: query, ...options })
  });

  if (!response.ok) {
    throw new Error(`Search failed: ${response.status}`);
  }

  return response.json();
}

// Usage
const token = await getSearchToken(); // or fetched from your own backend endpoint
const results = await search(token, 'profiles', 'jordan', {
  filter: "preferred_location_id = 12",
  sort: ['last_name:asc'],
  limit: 10
});
console.log(results.hits);
```

In browser code, request the token from your own backend rather than minting it client-side, and re-request it when a search returns `401` or `403`.

## Related

* [Minting search tokens](/dev-guides/discovery/search-tokens)
* [Discovery indexes](/dev-guides/discovery/indexes)
* [Schools index](/dev-guides/discovery/schools-index)
* [Filtering, sorting, and search on the API](/dev-guides/core-setup/filtering-and-sorting)
