diff --git a/open-api.yaml b/open-api.yaml
index 9a65c25b..3e959071 100644
--- a/open-api.yaml
+++ b/open-api.yaml
@@ -660,6 +660,109 @@ components:
- numberOfDocuments
- isIndexing
- fieldDistribution
+ searchQuery:
+ type: object
+ additionalProperties: false
+ properties:
+ q:
+ type: string
+ description: Query string.
+ default: '""'
+ example: '"Back to the future"'
+ attributesToRetrieve:
+ type: array
+ description: 'Array of attributes whose fields will be present in the returned documents. Defaults to the [displayedAttributes list](https://docs.meilisearch.com/reference/features/settings.html#displayed-attributes) which contains by default all attributes found in the documents.'
+ items:
+ type: string
+ example: '["title", "overview"]'
+ default: '["*"]'
+ attributesToHighlight:
+ type: array
+ description: Array of attributes whose values will contain highlighted matching terms. Highlighted attributes are returned in `_formatted` response object.
+ items:
+ type: string
+ example: '["title", "overview"]'
+ default: '[]'
+ highlightPreTag:
+ type: string
+ description: Specify the tag to put before the highlighted query terms.
+ example: ''
+ default: ''
+ highlightPostTag:
+ type: string
+ description: Specify the tag to put after the highlighted query terms.
+ example: ''
+ default: ''
+ attributesToCrop:
+ type: array
+ description: Array of attributes whose values have to be cropped. Cropped attributes are returned in `_formatted` response object.
+ items:
+ type: string
+ example: '["overview", "author"]'
+ default: '[]'
+ cropMarker:
+ type: string
+ description: Sets the crop marker to apply before and/or after cropped part selected within an attribute defined in `attributesToCrop` parameter.
+ default: '…'
+ cropLength:
+ type: number
+ description: Sets the total number of **words** to keep for the cropped part of an attribute specified in the `attributesToCrop` parameter.
+ default: 10
+ showMatchesPosition:
+ type: boolean
+ description: Defines whether an `_matchesPosition` object that contains information about the matches should be returned or not.
+ default: false
+ matchingStrategy:
+ type: string
+ description: Defines which strategy to use to match the query terms within the documents as search results. Two different strategies are available, `last` and `all`. By default, the `last` strategy is chosen.
+ default: 'last'
+ filter:
+ $ref: '#/components/schemas/filter'
+ facets:
+ type: array
+ description: 'Array of attributes whose fields will be distributed as a facet. If you have [set up filterableAttributes](https://docs.meilisearch.com/reference/features/settings.html#filterable-attributes), you can retrieve the count of matching terms for each [facets](https://docs.meilisearch.com/reference/features/filtering_and_faceted_search.html#faceted-search).[Learn more about facet distribution in the dedicated guide](https://docs.meilisearch.com/reference/features/search_parameters.html#facet-distribution)'
+ items:
+ type: string
+ example: '["genres", "author"]'
+ default: '[]'
+ offset:
+ type: number
+ description: Number of documents to skip.
+ default: 0
+ limit:
+ type: number
+ description: Maximum number of documents returned.
+ default: 20
+ page:
+ type: number
+ description: The specific search results page to fetch.
+ example: 1
+ hitsPerPage:
+ type: number
+ description: Number of returned results per page.
+ example: 20
+ sort:
+ $ref: '#/components/schemas/sort'
+ examples:
+ Example:
+ value:
+ q: Harry
+ offset: 0
+ limit: 20
+ filter: (genres = Horror AND genres = Mystery) OR release_date > 523242000
+ facets:
+ - genres
+ - author
+ attributesToRetrieve:
+ - title
+ - overview
+ attributesToCrop:
+ - overview
+ cropLength: 20
+ attributesToHighlight:
+ - overview
+ showMatchesPosition: true
+ wordsMatchingStrategy: all
error:
title: error
type: object
@@ -1212,10 +1315,11 @@ tags:
[Learn more about documents](https://docs.meilisearch.com/learn/core_concepts/documents.html).
- name: Search
description: |
- Meilisearch exposes 2 routes to perform searches:
+ Meilisearch exposes 3 routes to perform searches:
* A POST route: this is the preferred route when using API authentication, as it allows [preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) caching and better performances.
* A GET route: the usage of this route is discouraged, unless you have good reason to do otherwise (specific caching abilities for example).
Other than the differences mentioned above, the two routes are strictly equivalent.
+ * A POST multi-search route allowing to perform multiple search queries in a single HTTP request.
- name: Tasks
description: |
The `tasks` route gives information about the progress of the [asynchronous operations](https://docs.meilisearch.com/learn/advanced/asynchronous_operations.html).
@@ -1890,108 +1994,7 @@ paths:
content:
application/json:
schema:
- type: object
- additionalProperties: false
- properties:
- q:
- type: string
- description: Query string.
- default: '""'
- example: '"Back to the future"'
- attributesToRetrieve:
- type: array
- description: 'Array of attributes whose fields will be present in the returned documents. Defaults to the [displayedAttributes list](https://docs.meilisearch.com/reference/features/settings.html#displayed-attributes) which contains by default all attributes found in the documents.'
- items:
- type: string
- example: '["title", "overview"]'
- default: '["*"]'
- attributesToHighlight:
- type: array
- description: Array of attributes whose values will contain highlighted matching terms. Highlighted attributes are returned in `_formatted` response object.
- items:
- type: string
- example: '["title", "overview"]'
- default: '[]'
- highlightPreTag:
- type: string
- description: Specify the tag to put before the highlighted query terms.
- example: ''
- default: ''
- highlightPostTag:
- type: string
- description: Specify the tag to put after the highlighted query terms.
- example: ''
- default: ''
- attributesToCrop:
- type: array
- description: Array of attributes whose values have to be cropped. Cropped attributes are returned in `_formatted` response object.
- items:
- type: string
- example: '["overview", "author"]'
- default: '[]'
- cropMarker:
- type: string
- description: Sets the crop marker to apply before and/or after cropped part selected within an attribute defined in `attributesToCrop` parameter.
- default: '…'
- cropLength:
- type: number
- description: Sets the total number of **words** to keep for the cropped part of an attribute specified in the `attributesToCrop` parameter.
- default: 10
- showMatchesPosition:
- type: boolean
- description: Defines whether an `_matchesPosition` object that contains information about the matches should be returned or not.
- default: false
- matchingStrategy:
- type: string
- description: Defines which strategy to use to match the query terms within the documents as search results. Two different strategies are available, `last` and `all`. By default, the `last` strategy is chosen.
- default: 'last'
- filter:
- $ref: '#/components/schemas/filter'
- facets:
- type: array
- description: 'Array of attributes whose fields will be distributed as a facet. If you have [set up filterableAttributes](https://docs.meilisearch.com/reference/features/settings.html#filterable-attributes), you can retrieve the count of matching terms for each [facets](https://docs.meilisearch.com/reference/features/filtering_and_faceted_search.html#faceted-search).[Learn more about facet distribution in the dedicated guide](https://docs.meilisearch.com/reference/features/search_parameters.html#facet-distribution)'
- items:
- type: string
- example: '["genres", "author"]'
- default: '[]'
- offset:
- type: number
- description: Number of documents to skip.
- default: 0
- limit:
- type: number
- description: Maximum number of documents returned.
- default: 20
- page:
- type: number
- description: The specific search results page to fetch.
- example: 1
- hitsPerPage:
- type: number
- description: Number of returned results per page.
- example: 20
- sort:
- $ref: '#/components/schemas/sort'
- examples:
- Example:
- value:
- q: Harry
- offset: 0
- limit: 20
- filter: (genres = Horror AND genres = Mystery) OR release_date > 523242000
- facets:
- - genres
- - author
- attributesToRetrieve:
- - title
- - overview
- attributesToCrop:
- - overview
- cropLength: 20
- attributesToHighlight:
- - overview
- showMatchesPosition: true
- wordsMatchingStrategy: all
+ $ref: '#/components/schemas/searchQuery'
responses:
'200':
description: Ok
@@ -3234,6 +3237,116 @@ paths:
description: Not Found
parameters:
- $ref: '#/components/parameters/indexUid'
+ /multi-search:
+ post:
+ operationId: multi_search
+ summary: Multi Search
+ description: |
+ Make multiple search queries in a single HTTP request.
+
+ The queries can span different indexes or lookup the results of different filters on the same index. Each query has its own results set.
+ tags:
+ - Search
+ security:
+ - apiKey: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ additionalProperties: false
+ properties:
+ queries:
+ type: array
+ description: Array of the search queries to be performed.
+ items:
+ type: object
+ additionalProperties: false
+ properties:
+ indexUid:
+ type: string
+ description: The unique identifier of the index to be searched.
+ $ref: '#/components/schemas/searchQuery/properties'
+ examples:
+ Example:
+ value:
+ indexUid: movies
+ $ref: '#components/schemas/searchQuery/examples/Example/value'
+ required:
+ - indexUid
+ required:
+ - queries
+ examples:
+ Example:
+ queries:
+ - indexUid: movies
+ q: Harry
+ offset: 0
+ limit: 20
+ filter: (genres = Horror AND genres = Mystery) OR release_date > 523242000
+ facets:
+ - genres
+ - author
+ attributesToRetrieve:
+ - title
+ - overview
+ attributesToCrop:
+ - overview
+ cropLength: 20
+ attributesToHighlight:
+ - overview
+ showMatchesPosition: true
+ wordsMatchingStrategy: all
+ responses:
+ '200':
+ description: Ok
+ content:
+ application/json:
+ schema:
+ type: object
+ additionalProperties: false
+ properties:
+ results:
+ type: array
+ items:
+ type: object
+ properties:
+ indexUid:
+ type: string
+ description: The unique identifier of the searched index.
+ $ref: '#/components/schemas/searchResponse/properties'
+ examples:
+ Example:
+ results:
+ - indexUid: movies
+ hits:
+ - id: 25684
+ title: American Ninja 5
+ poster: 'https://image.tmdb.org/t/p/w1280/iuAQVI4mvjI83wnirpD8GVNRVuY.jpg'
+ overview: 'When a scientists daughter is kidnapped, American Ninja, attempts to find her, but this time he teams up with a youngster he has trained in the ways of the ninja.'
+ release_date: 725846400
+ _formatted:
+ id: 25684
+ title: American Ninja 5
+ poster: 'https://image.tmdb.org/t/p/w1280/iuAQVI4mvjI83wnirpD8GVNRVuY.jpg'
+ overview: 'When a scientists daughter is kidnapped, American Ninja, attempts to find her, but this time he teams up with a youngster he has trained in the ways of the ninja.'
+ release_date: 725846400
+ _matchesPosition:
+ overview:
+ - start: 49
+ length: 5
+ - start: 155
+ length: 5
+ limit: 0
+ offset: 0
+ estimatedTotalHits: 0
+ query: string
+ processingTimeMs: 0
+ '401':
+ $ref: '#/components/responses/401'
+ parameters:
+ - $ref: '#/components/parameters/Content-Type'
/keys:
get:
operationId: keys.list
diff --git a/text/0034-telemetry-policies.md b/text/0034-telemetry-policies.md
index 4758ff08..442f363a 100644
--- a/text/0034-telemetry-policies.md
+++ b/text/0034-telemetry-policies.md
@@ -40,6 +40,7 @@ The collected data is sent to [Segment](https://segment.com/). Segment is a plat
| Launched | Occurs when MeiliSearch is launched the first time. |
| Documents Searched POST | Aggregated event on all received requests via the `POST` - `indexes/:indexUid/search` route during one hour or until a batch size reaches `500Kb`. |
| Documents Searched GET | Aggregated event on all received requests via the `GET` - `/indexes/:indexUid/search` route during one hour or until a batch size reaches `500Kb`. |
+| Documents Searched by Multi-Search POST | Aggregated event on all received requests via the `POST`- `/multi-search` route during one hour or until a batch size reaches `500Kb`. |
| Documents Added | Aggregated event on all received requests via the `POST` - `/indexes/:indexUid/documents` route during one hour or until a batch size reaches `500Kb`. |
| Documents Updated | Aggregated event on all received requests via the `PUT` - `/indexes/:indexUid/documents` route during one hour or until a batch size reaches `500Kb`. |
| Documents Deleted | Aggregated event on all received requests via the `DELETE` - `/indexes/:indexUid/documents`, `DELETE` - `/indexes/:indexUid/documents/:documentId`, `POST` - `indexes/:indexUid/documents/delete-batch` routes during one hour or until a batch size reaches `500Kb`. |
@@ -107,11 +108,11 @@ The collected data is sent to [Segment](https://segment.com/). Segment is a plat
| `stats.database_size` | Database size. Expressed in `Bytes` | 2621440 | Every hour |
| `stats.indexes_number` | Number of indexes | 2 | Every hour |
| `start_since_days` | Number of days since instance was launched | 365 | Every hour |
-| `user_agent` | User-agent header encountered during one or more API calls | ["Meilisearch Ruby (v2.1)", "Ruby (3.0)"] | `Documents Searched POST`, `Documents Searched GET`, `Index Created`, `Index Updated`, `Documents Added`, `Documents Updated`, `Documents Deleted`, `Settings Updated`, `Ranking Rules Updated`, `SortableAttributes Updated`, `FilterableAttributes Updated`, `SearchableAttributes Updated`, `TypoTolerance Updated`, `Pagination Updated`, `Faceting Updated`, `DistinctAttribute Updated`, `DisplayedAttributes Updated`, `StopWords Updated`, `Synonyms Updated`, `Dump Created`, `Tasks Seen`, `Stats Seen`, `Health Seen`, `Version Seen` |
+| `user_agent` | User-agent header encountered during one or more API calls | ["Meilisearch Ruby (v2.1)", "Ruby (3.0)"] | `Documents Searched POST`, `Documents Searched GET`, `Index Created`, `Index Updated`, `Documents Added`, `Documents Updated`, `Documents Deleted`, `Settings Updated`, `Ranking Rules Updated`, `SortableAttributes Updated`, `FilterableAttributes Updated`, `SearchableAttributes Updated`, `TypoTolerance Updated`, `Pagination Updated`, `Faceting Updated`, `DistinctAttribute Updated`, `DisplayedAttributes Updated`, `StopWords Updated`, `Synonyms Updated`, `Dump Created`, `Tasks Seen`, `Stats Seen`, `Health Seen`, `Version Seen`, `Documents Searched by Multi-Search POST` |
| `requests.99th_response_time` | Highest latency from among the fastest 99% of successful search requests | 57ms | `Documents Searched POST`, `Documents Searched GET`|
-| `requests.total_succeeded` | Total number of successful requests in this batch | 3456 | `Documents Searched POST`, `Documents Searched GET` |
-| `requests.total_failed` | Total number of failed requests in this batch | 24 | `Documents Searched POST`, `Documents Searched GET` |
-| `requests.total_received` | Total number of received requests in this batch | 3480 | `Documents Searched POST`, `Documents Searched GET`, `Documents Deleted`, `Health Seen`, `Tasks Seen` |
+| `requests.total_succeeded` | Total number of successful requests in this batch | 3456 | `Documents Searched POST`, `Documents Searched GET`, `Documents Searched by Multi-Search POST` |
+| `requests.total_failed` | Total number of failed requests in this batch | 24 | `Documents Searched POST`, `Documents Searched GET`, `Documents Searched by Multi-Search POST` |
+| `requests.total_received` | Total number of received requests in this batch | 3480 | `Documents Searched POST`, `Documents Searched GET`, `Documents Deleted`, `Health Seen`, `Tasks Seen`, `Documents Searched by Multi-Search POST` |
| `sort.with_geoPoint` | `true` if the sort rule `_geoPoint` was used in this batch, otherwise `false` | true | `Documents Searched POST`, `Documents Searched GET` |
| `sort.avg_criteria_number` | Average number of sort criteria among all requests containing the `sort` parameter in this batch | 2 | `Documents Searched POST`, `Documents Searched GET` |
| `filter.with_geoRadius` | `true` if the filter rule `_geoRadius` was used in this batch, otherwise `false` | false | `Documents Searched POST`, `Documents Searched GET` |
@@ -171,6 +172,11 @@ The collected data is sent to [Segment](https://segment.com/). Segment is a plat
| `filtered_by_before_finished_at` | `true` if tasks are filtered by the `beforeFinishedAt` query parameter, otherwise `false` | false | `Tasks Seen`, `Tasks Canceled`, `Tasks Deleted` |
| `filtered_by_after_finished_at` | `true` if tasks are filtered by the `afterFinishedAt` query parameter, otherwise `false` | false | `Tasks Seen`, `Tasks Canceled`, `Tasks Deleted` |
| `per_index_uid` | `true` if an uid is used to fetch an index stat resource, otherwise `false` | false | `Stats Seen` |
+| searches.avg_search_count | The average number of search queries received per call for the aggregated event. | `4.2` | `Documents Searched by Multi-Search POST` |
+| searches.total_search_count | The total number of search queries received for the aggregated event. | `16023` | `Documents Searched by Multi-Search POST` |
+| indexes.avg_distinct_index_count | The average number of queried indexes received per call for the aggregated event. | `1.2` | `Documents Searched by Multi-Search POST`
+| indexes.total_distinct_index_count | The total number of distinct indexes queries for the aggregated event. | `6023` | `Documents Searched by Multi-Search POST`
+| indexes.total_single_index | The total number of calls where only one index where queried. | `2007` | `Documents Searched by Multi-Search POST`
| `swap_operation_number` | The number of swap operation given in `POST /swap-indexes` API call | 2 | `Indexes Swapped` |
| `matching_strategy.most_used_strategy` | Most used word matching strategy among all search requests in this batch | `last` | `Documents Searched POST`, `Documents Searched GET` |
| `per_document_id` | `true` if `DELETE /indexes/:indexUid/documents/:documentUid` endpoint was used in this batch, otherwise `false` | false | `Documents Deleted` |
@@ -308,6 +314,22 @@ This property allows us to gather essential information to better understand on
---
+#### `Documents Searched by Multi-Search POST`
+
+> The Documents Searched event is sent once an hour or when a batch reaches the maximum size of `500kb`. The event's properties are averaged over all requests on `POST` - `/multi-search`.
+
+| Property name | Description | Example |
+|---------------|-------------|---------|
+| user_agent | Represents all the user-agents encountered on this endpoint in the aggregated event.| `["Meilisearch Ruby (v2.1)", "Ruby (3.0)"]` |
+| requests.total_succeeded | The number of succeeded calls on the endpoint for the aggregated event. | `3456` |
+| requests.total_failed | The number of calls on the endpoint for the aggregated event. | `24` |
+| requests.total_received | The number of calls on the endpoint for the aggregated event. | `3480` |
+| searches.avg_search_count | The average number of search queries received per call for the aggregated event. | `4.2` |
+| searches.total_search_count | The total number of search queries received for the aggregated event. | `16023` |
+| indexes.avg_distinct_index_count | The average number of queried indexes received per call for the aggregated event. | `1.2`
+| indexes.total_distinct_index_count | The total number of distinct indexes queries for the aggregated event. | `6023`
+| indexes.total_single_index | The total number of calls where only one index where queried. | `2007`
+
## `Index Created`
| Property name | Description | Example |
diff --git a/text/0061-error-format-and-definitions.md b/text/0061-error-format-and-definitions.md
index 2d24e8b2..b873776e 100644
--- a/text/0061-error-format-and-definitions.md
+++ b/text/0061-error-format-and-definitions.md
@@ -1856,7 +1856,9 @@ This error happens when a requested index can't be found.
### Error Definition
-HTTP Code: `404 Not Found` when `Synchronous`
+HTTP Code when `Synchronous`:
+- if the index uid was specified as part of the URL, `404 Not Found`
+- if the index uid was specified as part of the POST body, `400 Bad Request`
#### Variant: Multiples indexUids can't be found
diff --git a/text/0192-multi-search-api.md b/text/0192-multi-search-api.md
new file mode 100644
index 00000000..02def32e
--- /dev/null
+++ b/text/0192-multi-search-api.md
@@ -0,0 +1,100 @@
+# Multi-search API
+
+## 1. Summary
+
+The multi-search endpoint performs multiple search queries on one or more indexes by bundling them into a single request.
+Each search query has its own results set.
+
+## 2. Motivation
+
+- Perform multiple queries in a single HTTP request
+
+## 3. Functional Specification
+
+Meilisearch exposes 1 route to perform multi-search requests:
+
+- POST `/multi-search`
+
+If a master key is used to secure a Meilisearch instance, the auth layer returns the following errors:
+
+- 🔴 Accessing these routes without the `Authorization` header returns a [missing_authorization_header](0061-error-format-and-definitions.md#missing_authorization_header) error.
+- 🔴 Accessing these routes with a key that does not have permissions (i.e. other than the master key) returns an [invalid_api_key](0061-error-format-and-definitions.md#invalid_api_key) error.
+
+If any of the search queries fail to execute, the response returns the corresponding error instead of the array of results. If multiple queries fail, only the first encountered failure is returned.
+
+`POST` HTTP verb errors:
+
+- 🔴 Omitting the Content-Type header returns a [missing_content_type](0061-error-format-and-definitions.md#missing_content_type) error.
+- 🔴 Sending an empty Content-Type returns an [invalid_content_type](0061-error-format-and-definitions.md#invalid_content_type) error.
+- 🔴 Sending a different Content-Type than `application/json` returns an [invalid_content_type](0061-error-format-and-definitions.md#invalid_content_type) error.
+- 🔴 Sending an empty payload returns a [missing_payload](0061-error-format-and-definitions.md#missing_payload) error.
+- 🔴 Sending an invalid JSON payload returns a [malformed_payload](0061-error-format-and-definitions.md#malformed_payload) error.
+- 🔴 Sending an inexistent `indexUid` in a query object returns an [index_not_found](0061-error-format-and-definitions.md#index_not_found) error.
+- 🔴 Sending an invalid format for the `indexUid` property in a query object returns an [invalid_index_uid](0061-error-format-and-definitions.md#invalid_index_uid) error.
+
+### 3.1. Search Payload Parameters
+
+| Field | Type | Required |
+|--------------|------------------|----------|
+| [`queries`](#311-queries) | Array of Objects | True |
+
+#### 3.1.1. `queries`
+
+- Type: Object
+- Required: True
+
+The `queries` object contains the list of search queries to perform.
+
+Each element of this array is a JSON object with the required field `indexUid`, the uid of the index to be searched. Other fields of this object are optional, and identical to the ones in the [existing search routes](./0118-search-api.md#31-search-payload-parameters) (`q`, `limit`, etc.).
+
+### 3.2. Search Response Properties
+
+| Field | Type | Required |
+|---------------------------|---------------|----------|
+| [`results`](#321-results) | Array[Object] | True |
+
+#### 3.2.1. `results`
+
+- Type: Array[Objets]
+- Required: True
+
+Results of the search queries as an array of search results.
+
+Each element in this array contains the results of the search queries in the same order they have been requested. Additionally to the [usual fields returned by a search result](./0118-search-api.md#31-formatting-search-results), an `indexUid` field is present with the index UID on which the search has been performed.
+
+example:
+
+Search queries:
+```json
+{
+ "queries": [ { "indexUid": "movie", "q": "wonder" }, { "indexUid": "books", "q": "king" } ]
+}
+```
+
+Search results:
+```json
+{
+ "results": [
+ {
+ "indexUid": "movie",
+ "hits": [ { "title": "wonderwoman" } ],
+ // other search results fields: processingTimeMs, limit, ...
+ },
+ {
+ "indexUid": "books",
+ "hits": [ { "title": "king kong theory" } ],
+ // other search results fields: processingTimeMs, limit, ...
+ },
+ ]
+}
+```
+
+The other fields of an element from the `results` array are identical to the fields of the [response from the other search routes](./0118-search-api.md#31-formatting-search-results).
+
+## 2. Technical Details
+n/a
+
+## 3. Future Possibilities
+
+- Allow specifying an index uid pattern instead of an index uid to produce searches on all indexes matching the pattern.
+- Allow additional arguments to the request specifying a strategy to aggregate results from the multiple searches.