# Multi-search
Source: https://www.meilisearch.com/docs/reference/api/multi_search

The /multi-search route allows you to perform multiple search queries on one or more indexes.

The `/multi-search` route allows you to perform multiple search queries on one or more indexes by bundling them into a single HTTP request. Multi-search is also known as federated search.

## Perform a multi-search

Bundle multiple search queries in a single API request. Use this endpoint to search through multiple indexes at once.

<RouteHighlighter method="POST" />

### Body

| Name             | Type             | Description                                                                                                                                                                |
| :--------------- | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`federation`** | Object           | If present and not `null`, returns a single list merging all search results across all specified queries                                                                   |
| **`queries`**    | Array of objects | Contains the list of search queries to perform. The [`indexUid`](/learn/getting_started/indexes#index-uid) search parameter is required, all other parameters are optional |

<Warning>
  If Meilisearch encounters an error when handling any of the queries in a multi-search request, it immediately stops processing the request and returns an error message. The returned message will only address the first error encountered.
</Warning>

#### `federation`

Use `federation` to receive a single list with all search results from all specified queries, in descending ranking score order. This is called federated search.

`federation` may optionally contain the following parameters:

| Parameter                                    | Type             | Default value | Description                                         |
| :------------------------------------------- | :--------------- | :------------ | :-------------------------------------------------- |
| **[`offset`](/reference/api/search#offset)** | Integer          | `0`           | Number of documents to skip                         |
| **[`limit`](/reference/api/search#limit)**   | Integer          | `20`          | Maximum number of documents returned                |
| **[`facetsByIndex`](#facetsbyindex)**        | Object of arrays | `null`        | Display facet information for the specified indexes |
| **[`mergeFacets`](#mergefacets)**            | Object           | `null`        | Display facet information for the specified indexes |

If `federation` is missing or `null`, Meilisearch returns a list of multiple search result objects, with each item from the list corresponding to a search query in the request.

##### `facetsByIndex`

`facetsByIndex` must be an object. Its keys must correspond to indexes in your Meilisearch project. Each key must be associated with an array of attributes in the filterable attributes list of that index:

```json theme={null}
"facetsByIndex": {
  "INDEX_A": ["ATTRIBUTE_X", "ATTRIBUTE_Y"],
  "INDEX_B": ["ATTRIBUTE_Z"]
}
```

When you specify `facetsByIndex`, multi-search responses include an extra `facetsByIndex` field. The response's `facetsByIndex` is an object with one field for each queried index:

```json theme={null}
{
  "hits" [ … ],
  …
  "facetsByIndex": {
    "INDEX_A": {
      "distribution": {
        "ATTRIBUTE_X": {
          "KEY": <Integer>,
          "KEY": <Integer>,
          …
        },
        "ATTRIBUTE_Y": {
          "KEY": <Integer>,
          …
        }
      },
      "stats": {
        "KEY": {
          "min": <Integer>,
          "max": <Integer>
        }
      }
    },
    "INDEX_B": {
      …
    }
  }
}
```

##### `mergeFacets`

`mergeFacets` must be an object and may contain the following fields:

* `maxValuesPerFacet`: must be an integer. When specified, indicates the maximum number of returned values for a single facet. Defaults to the value assigned to [the `maxValuesPerFacet` index setting](/reference/api/settings#faceting)

When both `facetsByIndex` and `mergeFacets` are present and not null, facet information included in multi-search responses is merged across all queried indexes. Instead of `facetsByIndex`, the response includes two extra fields: `facetDistribution` and `facetStats`:

```json theme={null}
{
  "hits": [ … ],
  …
  "facetDistribution": {
    "ATTRIBUTE": {
      "VALUE": <Integer>,
      "VALUE": <Integer>
    }
  },
  "facetStats": {
    "ATTRIBUTE": {
      "min": <Integer>,
      "max": <Integer>
    }
  }
}
```

##### Merge algorithm for federated searches

Federated search's merged results are returned in decreasing ranking score. To obtain the final list of results, Meilisearch compares with the following procedure:

1. Detailed ranking scores are normalized in the following way for both hits:
   1. Consecutive relevancy scores (related to the rules `words`, `typo`, `attribute`, `exactness` or `vector`) are grouped in a single score for each hit
   2. `sort` and `geosort` score details remain unchanged
2. Normalized detailed ranking scores are compared lexicographically for both hits:
   1. If both hits have a relevancy score, then the bigger score wins. If it is a tie, move to next step
   2. If one result has a relevancy score or a (geo)sort score, Meilisearch picks it
   3. If both results have a sort or geosort score in the same sorting direction, then Meilisearch compares the values according to the common sort direction. The result with the value that must come first according to the common sort direction wins. If it is a tie, go to the next step
   4. Compare the global ranking scores of both hits to determine which comes first, ignoring any sorting or geosorting
   5. In the case of a perfect tie, documents from the query with the lowest rank in the `queries` array are preferred.

<Note>
  Meilisearch considers two documents the same if:

  1. They come from the same index
  2. And their primary key is the same

  There is no way to specify that two documents should be treated as the same across multiple indexes.
</Note>

#### `queries`

`queries` must be an array of objects. Each object may contain the following search parameters:

| Search parameter                                                                                     | Type             | Default value | Description                                                            |
| :--------------------------------------------------------------------------------------------------- | :--------------- | :------------ | :--------------------------------------------------------------------- |
| **[`federationOptions`](#federationoptions)**                                                        | Object           | `null`        | Configure federation settings for a specific query                     |
| **[`indexUid`](/learn/getting_started/indexes#index-uid)**                                           | String           | N/A           | `uid` of the requested index                                           |
| **[`q`](/reference/api/search#query-q)**                                                             | String           | `""`          | Query string                                                           |
| **[`offset`](/reference/api/search#offset)**                                                         | Integer          | `0`           | Number of documents to skip                                            |
| **[`limit`](/reference/api/search#limit)**                                                           | Integer          | `20`          | Maximum number of documents returned                                   |
| **[`hitsPerPage`](/reference/api/search#number-of-results-per-page)**                                | Integer          | `1`           | Maximum number of documents returned for a page                        |
| **[`page`](/reference/api/search#page)**                                                             | Integer          | `1`           | Request a specific page of results                                     |
| **[`filter`](/reference/api/search#filter)**                                                         | String           | `null`        | Filter queries by an attribute's value                                 |
| **[`facets`](/reference/api/search#facets)**                                                         | Array of strings | `null`        | Display the count of matches per facet                                 |
| **[`distinct`](/reference/api/search#distinct-attributes-at-search-time)**                           | String           | `null`        | Restrict search to documents with unique values of specified attribute |
| **[`attributesToRetrieve`](/reference/api/search#attributes-to-retrieve)**                           | Array of strings | `["*"]`       | Attributes to display in the returned documents                        |
| **[`attributesToCrop`](/reference/api/search#attributes-to-crop)**                                   | Array of strings | `null`        | Attributes whose values have to be cropped                             |
| **[`cropLength`](/reference/api/search#crop-length)**                                                | Integer          | `10`          | Maximum length of cropped value in words                               |
| **[`cropMarker`](/reference/api/search#crop-marker)**                                                | String           | `"…"`         | String marking crop boundaries                                         |
| **[`attributesToHighlight`](/reference/api/search#attributes-to-highlight)**                         | Array of strings | `null`        | Highlight matching terms contained in an attribute                     |
| **[`highlightPreTag`](/reference/api/search#highlight-tags)**                                        | String           | `"<em>"`      | String inserted at the start of a highlighted term                     |
| **[`highlightPostTag`](/reference/api/search#highlight-tags)**                                       | String           | `"</em>"`     | String inserted at the end of a highlighted term                       |
| **[`showMatchesPosition`](/reference/api/search#show-matches-position)**                             | Boolean          | `false`       | Return matching terms location                                         |
| **[`sort`](/reference/api/search#sort)**                                                             | Array of strings | `null`        | Sort search results by an attribute's value                            |
| **[`matchingStrategy`](/reference/api/search#matching-strategy)**                                    | String           | `last`        | Strategy used to match query terms within documents                    |
| **[`showRankingScore`](/reference/api/search#ranking-score)**                                        | Boolean          | `false`       | Display the global ranking score of a document                         |
| **[`showRankingScoreDetails`](/reference/api/search#ranking-score-details)**                         | Boolean          | `false`       | Adds a detailed global ranking score field                             |
| **[`rankingScoreThreshold`](/reference/api/search#ranking-score-threshold)**                         | Number           | `null`        | Excludes results with low ranking scores                               |
| **[`attributesToSearchOn`](/reference/api/search#customize-attributes-to-search-on-at-search-time)** | Array of strings | `["*"]`       | Restrict search to the specified attributes                            |
| **[`hybrid`](/reference/api/search#hybrid-search)**                                                  | Object           | `null`        | Return results based on query keywords and meaning                     |
| **[`vector`](/reference/api/search#vector)**                                                         | Array of numbers | `null`        | Search using a custom query vector                                     |
| **[`retrieveVectors`](/reference/api/search#display-_vectors-in-response)**                          | Boolean          | `false`       | Return document vector data                                            |
| **[`locales`](/reference/api/search#query-locales)**                                                 | Array of strings | `null`        | Explicitly specify languages used in a query                           |

Unless otherwise noted, search parameters for multi-search queries function exactly like [search parameters for the `/search` endpoint](/reference/api/search#search-parameters).

##### `limit`, `offset`, `hitsPerPage` and `page`

These options are not compatible with federated searches.

##### `federationOptions`

`federationOptions` must be an object. It accepts the following parameters:

* `weight`: serves as a multiplicative factor to ranking scores of search results in this specific query. If \< `1.0`, the hits from this query are less likely to appear in the final results list. If > `1.0`, the hits from this query are more likely to appear in the final results list. Must be a positive floating-point number. Defaults to `1.0`
* `remote` <NoticeTag type="experimental" label="experimental" />: indicates the remote instance where Meilisearch will perform the query. Must be a string corresponding to a [remote object](/reference/api/network). Defaults to `null`

### Response

The response to `/multi-search` queries may take different shapes depending on the type of query you're making.

#### Non-federated multi-search requests

| Name          | Type             | Description                                                            |
| :------------ | :--------------- | :--------------------------------------------------------------------- |
| **`results`** | Array of objects | Results of the search queries in the same order they were requested in |

Each search result object is composed of the following fields:

| Name                     | Type             | Description                                                                      |
| :----------------------- | :--------------- | :------------------------------------------------------------------------------- |
| **`indexUid`**           | String           | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index         |
| **`hits`**               | Array of objects | Results of the query                                                             |
| **`offset`**             | Number           | Number of documents skipped                                                      |
| **`limit`**              | Number           | Number of documents to take                                                      |
| **`estimatedTotalHits`** | Number           | Estimated total number of matches                                                |
| **`totalHits`**          | Number           | Exhaustive total number of matches                                               |
| **`totalPages`**         | Number           | Exhaustive total number of search result pages                                   |
| **`hitsPerPage`**        | Number           | Number of results on each page                                                   |
| **`page`**               | Number           | Current search results page                                                      |
| **`facetDistribution`**  | Object           | **[Distribution of the given facets](/reference/api/search#facetdistribution)**  |
| **`facetStats`**         | Object           | [The numeric `min` and `max` values per facet](/reference/api/search#facetstats) |
| **`processingTimeMs`**   | Number           | Processing time of the query                                                     |
| **`query`**              | String           | Query originating the response                                                   |
| **`requestUid`**         | String           | A UUID v7 identifying the search request                                         |

#### Federated multi-search requests

Federated search requests return a single object and the following fields:

| Name                     | Type             | Description                                                                        |
| :----------------------- | :--------------- | :--------------------------------------------------------------------------------- |
| **`hits`**               | Array of objects | Results of the query                                                               |
| **`offset`**             | Number           | Number of documents skipped                                                        |
| **`limit`**              | Number           | Number of documents to take                                                        |
| **`estimatedTotalHits`** | Number           | Estimated total number of matches                                                  |
| **`semanticHitCount`**   | Number           | Exhaustive number of semantic search matches (only present in AI-powered searches) |
| **`processingTimeMs`**   | Number           | Processing time of the query                                                       |
| **`facetsByIndex`**      | Object           | [Data for facets present in the search results](#facetsbyindex)                    |
| **`facetDistribution`**  | Object           | [Distribution of the given facets](#mergefacets)                                   |
| **`facetStats`**         | Object           | [The numeric `min` and `max` values per facet](#mergefacets)                       |
| **`remoteErrors`**       | Object           | Indicates which remote requests failed and why                                     |
| **`requestUid`**         | String           | A UUID v7 identifying the search request                                           |

Each result in the `hits` array contains an additional `_federation` field with the following fields:

| Name                       | Type   | Description                                                                         |
| :------------------------- | :----- | :---------------------------------------------------------------------------------- |
| **`indexUid`**             | String | Index of origin for this document                                                   |
| **`queriesPosition`**      | Number | Array index number of the query in the request's `queries` array                    |
| **`remote`**               | String | Remote instance of origin for this document                                         |
| **`weightedRankingScore`** | Number | The product of the \_rankingScore of the hit and the weight of the query of origin. |

### Example

#### Non-federated multi-search

<CodeGroup>
  ```bash cURL theme={null}
  curl \
    -X POST 'MEILISEARCH_URL/multi-search' \
    -H 'Content-Type: application/json' \
    --data-binary '{
      "queries": [
        {
          "indexUid": "movies",
          "q": "pooh",
          "limit": 5
        },
        {
          "indexUid": "movies",
          "q": "nemo",
          "limit": 5
        },
        {
          "indexUid": "movie_ratings",
          "q": "us"
        }
      ]
    }'
  ```

  ```javascript JS theme={null}
  client.multiSearch({ queries: [
    {
      indexUid: 'movies',
      q: 'pooh',
      limit: 5,
    },
    {
      indexUid: 'movies',
      q: 'nemo',
      limit: 5,
    },
    {
      indexUid: 'movie_ratings',
      q: 'us',
    },
  ]})
  ```

  ```python Python theme={null}
  client.multi_search(
    [
      {'indexUid': 'movies', 'q': 'pooh', 'limit': 5},
      {'indexUid': 'movies', 'q': 'nemo', 'limit': 5},
      {'indexUid': 'movie_ratings', 'q': 'us'}
    ]
  )
  ```

  ```php PHP theme={null}
  $client->multiSearch([
      (new SearchQuery())
          ->setIndexUid('movies')
          ->setQuery('pooh')
          ->setLimit(5),
      (new SearchQuery())
          ->setIndexUid('movies')
          ->setQuery('nemo')
          ->setLimit(5),
      (new SearchQuery())
          ->setIndexUid('movie_ratings')
          ->setQuery('us')
    ]);
  ```

  ```java Java theme={null}
  MultiSearchRequest multiSearchRequest = new MultiSearchRequest();
  multiIndexSearch.addQuery(new IndexSearchRequest("movies").setQuery("pooh").setLimit(5));
  multiIndexSearch.addQuery(new IndexSearchRequest("movies").setQuery("nemo").setLimit(5));
  multiIndexSearch.addQuery(new IndexSearchRequest("movie_ratings").setQuery("us"));

  client.multiSearch(multiSearchRequest);
  ```

  ```ruby Ruby theme={null}
  client.multi_search([
    { index_uid: 'books', q: 'prince' },
    { index_uid: 'movies', q: 'pooh', limit: 5 }
    { index_uid: 'movies', q: 'nemo', limit: 5 }
    { index_uid: 'movie_ratings', q: 'us' }
  ])
  ```

  ```go Go theme={null}
  client.MultiSearch(&MultiSearchRequest{
    Queries: []SearchRequest{
      {
        IndexUID: "movies",
        Query:    "pooh",
        Limit:    5,
      },
      {
        IndexUID: "movies",
        Query:    "nemo",
        Limit:    5,
      },
      {
        IndexUID: "movie_ratings",
        Query:    "us",
      },
    },
  })
  ```

  ```csharp C# theme={null}
  await client.MultiSearchAsync(new MultiSearchQuery()
  {
      Queries = new System.Collections.Generic.List<SearchQuery>()
      {
          new SearchQuery() {
            IndexUid = "movies",
            Q = "booh",
            Limit = 5
          },
          new SearchQuery() {
            IndexUid = "movies",
            Q = "nemo",
            Limit = 5
          },
          new SearchQuery() {
            IndexUid = "movie_ratings",
            Q = "us",
          },
      }
  });
  ```

  ```rust Rust theme={null}
  let movie = client.index("movie");
  let movie_ratings = client.index("movie_ratings");

  let search_query_1 = SearchQuery::new(&movie)
      .with_query("pooh")
      .with_limit(5)
      .build();
  let search_query_2 = SearchQuery::new(&movie)
      .with_query("nemo")
      .with_limit(5)
      .build();
  let search_query_3 = SearchQuery::new(&movie_ratings)
      .with_query("us")
      .build();

  let response = client
      .multi_search()
      .with_search_query(search_query_1)
      .with_search_query(search_query_2)
      .with_search_query(search_query_3)
      .execute::<Document>()
      .await
      .unwrap();
  ```

  ```dart Dart theme={null}
  await client.multiSearch(MultiSearchQuery(queries: [
    IndexSearchQuery(query: 'pooh', indexUid: 'movies', limit: 5),
    IndexSearchQuery(query: 'nemo', indexUid: 'movies', limit: 5),
    IndexSearchQuery(query: 'us', indexUid: 'movies_ratings'),
  ]));
  ```
</CodeGroup>

##### Response: `200 Ok`

```json theme={null}
{
  "results": [
    {
      "indexUid": "movies",
      "hits": [
        {
          "id": 13682,
          "title": "Pooh's Heffalump Movie",
          …
        },
        …
      ],
      "query": "pooh",
      "processingTimeMs": 26,
      "limit": 5,
      "offset": 0,
      "estimatedTotalHits": 22,
      "requestUid": "0198e71e-47d2-7cd3-b507-1d0cc930b1f1"
    },
    {
      "indexUid": "movies",
      "hits": [
        {
          "id": 12,
          "title": "Finding Nemo",
          …
        },
        …
      ],
      "query": "nemo",
      "processingTimeMs": 5,
      "limit": 5,
      "offset": 0,
      "estimatedTotalHits": 11,
      "requestUid": "0198e71e-47d2-7cd3-b507-1d0cc930b1f1"
    },
    {
      "indexUid": "movie_ratings",
      "hits": [
        {
          "id": "Us",
          "director": "Jordan Peele",
          …
        }
      ],
      "query": "Us",
      "processingTimeMs": 0,
      "limit": 20,
      "offset": 0,
      "estimatedTotalHits": 1,
      "requestUid": "0198e71e-47d2-7cd3-b507-1d0cc930b1f1"
    }
  ]
}
```

#### Federated multi-search

<CodeGroup>
  ```bash cURL theme={null}
  curl \
    -X POST 'MEILISEARCH_URL/multi-search' \
    -H 'Content-Type: application/json' \
    --data-binary '{
      "federation": {},
      "queries": [
        {
          "indexUid": "movies",
          "q": "batman"
        },
        {
          "indexUid": "comics",
          "q": "batman"
        }
      ]
    }'
  ```

  ```javascript JS theme={null}
  client.multiSearch({
    federation: {},
    queries: [
      {
        indexUid: 'movies',
        q: 'batman',
      },
      {
        indexUid: 'comics',
        q: 'batman',
      },
    ]
  })
  ```

  ```python Python theme={null}
  client.multi_search(
    [{"indexUid": "movies", "q": "batman"}, {"indexUid": "comics", "q": "batman"}],
    {}
  )
  ```

  ```php PHP theme={null}
  $client->multiSearch([
      (new SearchQuery())
        ->setIndexUid('movies'))
        ->setQuery('batman'),
      (new SearchQuery())
        ->setIndexUid('comics')
        ->setQuery('batman'),
    ],
    (new MultiSearchFederation())
  );
  ```

  ```ruby Ruby theme={null}
  client.multi_search(
    queries: [{ index_uid: 'movies', q: 'batman' }, { index_uid: 'comics', q: 'batman' }],
    federation: {}
  )
  ```
</CodeGroup>

##### Response: `200 Ok`

```json theme={null}
{
  "hits": [
    {
      "id": 42,
      "title": "Batman returns",
      "overview": …, 
      "_federation": {
        "indexUid": "movies",
        "queriesPosition": 0
      }
    },
    {
      "comicsId": "batman-killing-joke",
      "description": …,
      "title": "Batman: the killing joke",
      "_federation": {
        "indexUid": "comics",
        "queriesPosition": 1
      }
    },
    …
  ],
  "processingTimeMs": 0,
  "limit": 20,
  "offset": 0,
  "estimatedTotalHits": 2,
  "semanticHitCount": 0,
  "requestUid": "0198e71e-47d2-7cd3-b507-1d0cc930b1f1"
}
```

#### Remote federated multi-search <NoticeTag type="experimental" label="experimental" />

<CodeGroup>
  ```bash cURL theme={null}
  curl \
    -X POST 'MEILISEARCH_URL/multi-search' \
    -H 'Content-Type: application/json' \
    --data-binary '{
      "federation": {},
      "queries": [
        {
          "indexUid": "movies",
          "q": "batman",
          "federationOptions": {
            "remote": "ms-00"
          }
        },
        {
          "indexUid": "movies",
          "q": "batman",
          "federationOptions": {
            "remote": "ms-01"
          }
        }
      ]
  ```
</CodeGroup>

##### Response: `200 Ok`

```json theme={null}
{
  "hits": [
    {
      "id": 42,
      "title": "Batman returns",
      "overview": …, 
      "_federation": {
        "indexUid": "movies",
        "queriesPosition": 0,
        "weightedRankingScore": 1.0,
        "remote": "ms-01"
    }
    },
    {
      "id": 87,
      "description": …,
      "title": "Batman: the killing joke",
      "_federation": {
        "indexUid": "movies",
        "queriesPosition": 1,
        "weightedRankingScore": 0.9848484848484849,
        "remote": "ms-00"
      }
    },
    …
  ],
  "processingTimeMs": 35,
  "limit": 5,
  "offset": 0,
  "estimatedTotalHits": 111,
  "requestUid": "0198e71e-47d2-7cd3-b507-1d0cc930b1f1",
  "remoteErrors": {
    "ms-02": {
      "message": "error sending request",
      "code": "proxy_could_not_send_request",
      "type": "system",
      "link": "https://docs.meilisearch.com/errors#proxy_could_not_make_request"
    }
  }
}
```