From eb0234e722b73652e36dc41eda9eaad5782c74ee Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 1 Oct 2026 09:34:53 +0200 Subject: [PATCH 1/6] feat(search): add the search query endpoint POST /v1beta1/search/query with searchRequest/searchQuery and the searchResponse/searchHitsContainer/searchHit result types, modeled on the MS Graph search API. The query string is KQL. --- api/openapi-spec/v1.0.yaml | 187 +++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/api/openapi-spec/v1.0.yaml b/api/openapi-spec/v1.0.yaml index 74fdb3d..cc99e52 100644 --- a/api/openapi-spec/v1.0.yaml +++ b/api/openapi-spec/v1.0.yaml @@ -4674,6 +4674,66 @@ paths: '@libre.graph.weight': 4 default: $ref: '#/components/responses/error' + /v1beta1/search/query: + post: + tags: + - search + summary: Search for resources + operationId: SearchQuery + description: | + Run a specified search query. Search results are provided in the response. + + The search endpoint allows clients to search for resources across all + accessible spaces and retrieve aggregated metadata (facets) about the + result set. + + The query string uses KQL (Keyword Query Language) syntax for filtering. + + Modeled on the MS Graph search query endpoint + (https://learn.microsoft.com/en-us/graph/api/search-query). Request and + response follow the MS Graph resource types; Libregraph additions carry + the `@libre.graph.` prefix. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - requests + properties: + requests: + type: array + description: A collection of one or more search requests. + minItems: 1 + items: + $ref: '#/components/schemas/searchRequest' + examples: + simple search: + summary: Simple keyword search + value: + requests: + - entityTypes: + - driveItem + query: + queryString: budget report + from: 0 + size: 25 + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + value: + type: array + description: A collection of search response objects, one per request. + items: + $ref: '#/components/schemas/searchResponse' + default: + $ref: '#/components/responses/error' components: schemas: tagAssignment: @@ -6511,6 +6571,133 @@ components: # type: array # items: # type: string + searchRequest: + type: object + description: | + Represents an individual search request within a search query. Follows + the [MS Graph searchRequest](https://learn.microsoft.com/en-us/graph/api/resources/searchrequest) + resource type. + required: + - entityTypes + - query + properties: + entityTypes: + type: array + description: | + One or more types of resources expected in the response. Currently + only `driveItem` is supported. + minItems: 1 + items: + type: string + enum: + - driveItem + query: + $ref: '#/components/schemas/searchQuery' + from: + type: integer + format: int32 + description: | + Specifies the offset for the search results. Offset 0 returns the + very first result. Used together with the `size` property for + pagination. + default: 0 + minimum: 0 + size: + type: integer + format: int32 + description: | + The size of the page to be retrieved. The maximum value is 500. + default: 25 + minimum: 0 + maximum: 500 + searchQuery: + type: object + description: | + Represents the search query. Follows the + [MS Graph searchQuery](https://learn.microsoft.com/en-us/graph/api/resources/searchquery) + resource type. + required: + - queryString + properties: + queryString: + type: string + description: | + The search query string in KQL (Keyword Query Language) format. The + query string can contain free-text keywords and property filters. + + Examples: + - `budget report`: free text search + - `mediatype:audio`: filter by media type + - `audio.artist:"Saxon"`: filter by audio metadata + - `audio.genre:Rock AND audio.year:1979`: combined filters + searchResponse: + type: object + description: | + Represents the response for an individual search request. Follows the + [MS Graph searchResponse](https://learn.microsoft.com/en-us/graph/api/resources/searchresponse) + resource type. + properties: + searchTerms: + type: array + description: Contains the search terms sent in the initial search query. + items: + type: string + hitsContainers: + type: array + description: | + A collection of search result sets. One for each entity type that + was queried. + items: + $ref: '#/components/schemas/searchHitsContainer' + searchHitsContainer: + type: object + description: | + Contains a collection of search results. Follows the + [MS Graph searchHitsContainer](https://learn.microsoft.com/en-us/graph/api/resources/searchhitscontainer) + resource type. + properties: + hits: + type: array + description: A collection of the search results. + items: + $ref: '#/components/schemas/searchHit' + total: + type: integer + format: int64 + description: | + The total number of results. Note this is not the number of results + on the page, but the total number of results satisfying the query. + readOnly: true + moreResultsAvailable: + type: boolean + description: | + Provides information if more results are available. Based on this + information, you can adjust the `from` and `size` properties of the + `searchRequest` accordingly. + readOnly: true + searchHit: + type: object + description: | + Represents an individual search result. Follows the + [MS Graph searchHit](https://learn.microsoft.com/en-us/graph/api/resources/searchhit) + resource type. + properties: + hitId: + type: string + description: The internal identifier for the item. + readOnly: true + rank: + type: integer + format: int32 + description: The rank or the order of the result. + readOnly: true + summary: + type: string + description: | + A summary of the result, if a summary is available. + readOnly: true + resource: + $ref: '#/components/schemas/driveItem' odata.error: required: - error From da6a67f263239b026f403c3c8f57299c71687613 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 1 Oct 2026 09:34:54 +0200 Subject: [PATCH 2/6] feat(search): MS Graph aggregations on the search query aggregationOption with bucketDefinition and manual ranges on the request, searchAggregation/searchBucket on the response, following the MS Graph resource types. --- api/openapi-spec/v1.0.yaml | 225 +++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) diff --git a/api/openapi-spec/v1.0.yaml b/api/openapi-spec/v1.0.yaml index cc99e52..861fcde 100644 --- a/api/openapi-spec/v1.0.yaml +++ b/api/openapi-spec/v1.0.yaml @@ -4687,6 +4687,10 @@ paths: accessible spaces and retrieve aggregated metadata (facets) about the result set. + Aggregations can be used to group results by properties such as file type, + author, or any indexed metadata field. This is useful for building faceted + search UIs or computing statistics about the result set. + The query string uses KQL (Keyword Query Language) syntax for filtering. Modeled on the MS Graph search query endpoint @@ -4719,6 +4723,48 @@ paths: queryString: budget report from: 0 size: 25 + search with aggregations: + summary: Search with term aggregation + value: + requests: + - entityTypes: + - driveItem + query: + queryString: "mediatype:audio" + from: 0 + size: 0 + aggregations: + - field: audio.artist + size: 10 + bucketDefinition: + sortBy: count + isDescending: true + minimumCount: 1 + search with range aggregation: + summary: Search with numeric range aggregation + value: + requests: + - entityTypes: + - driveItem + query: + queryString: "mediatype:audio" + size: 0 + aggregations: + - field: audio.year + bucketDefinition: + sortBy: keyAsString + isDescending: true + minimumCount: 0 + ranges: + - from: "1970" + to: "1980" + - from: "1980" + to: "1990" + - from: "1990" + to: "2000" + - from: "2000" + to: "2010" + - from: "2010" responses: '200': description: OK @@ -4732,6 +4778,26 @@ paths: description: A collection of search response objects, one per request. items: $ref: '#/components/schemas/searchResponse' + examples: + search with aggregations: + summary: Facet counts for the size:0 request (no hits, only the buckets and their filter tokens) + value: + value: + - searchTerms: + - "mediatype:audio" + hitsContainers: + - hits: [] + total: 142 + moreResultsAvailable: true + aggregations: + - field: audio.artist + buckets: + - key: Saxon + count: 42 + - key: Iron Maiden + count: 35 + - key: Judas Priest + count: 28 default: $ref: '#/components/responses/error' components: @@ -6607,9 +6673,17 @@ components: format: int32 description: | The size of the page to be retrieved. The maximum value is 500. + Set to 0 to return only aggregations without any hits. default: 25 minimum: 0 maximum: 500 + aggregations: + type: array + description: | + Specifies aggregations (also known as refiners or facets) to be + returned alongside the search results. Optional. + items: + $ref: '#/components/schemas/aggregationOption' searchQuery: type: object description: | @@ -6630,6 +6704,110 @@ components: - `mediatype:audio`: filter by media type - `audio.artist:"Saxon"`: filter by audio metadata - `audio.genre:Rock AND audio.year:1979`: combined filters + aggregationOption: + type: object + description: | + Specifies an aggregation that should be computed and returned alongside + search results. Follows the + [MS Graph aggregationOption](https://learn.microsoft.com/en-us/graph/api/resources/aggregationoption) + resource type. + + For string fields, terms aggregations return the distinct values and + their counts. For numeric and date fields, range aggregations can be + defined using the `ranges` property of `bucketDefinition`. + required: + - field + properties: + field: + type: string + description: | + Specifies the field in the schema of the specified entity type that + the aggregation should be computed on. Required. + + Examples: `audio.artist`, `audio.genre`, `audio.year`, `mimeType`. + size: + type: integer + format: int32 + description: | + The number of `searchBucket` resources to be returned. This is + optional and only applies to terms aggregations. Combined with + `bucketDefinition.sortBy` and `bucketDefinition.isDescending` to + produce the top N results by count or key. When not specified, all + buckets are returned. + minimum: 1 + bucketDefinition: + $ref: '#/components/schemas/bucketDefinition' + bucketDefinition: + type: object + description: | + Provides the details of how to generate the aggregation buckets in the + response. Follows the + [MS Graph bucketAggregationDefinition](https://learn.microsoft.com/en-us/graph/api/resources/bucketaggregationdefinition) + resource type. + required: + - sortBy + properties: + sortBy: + type: string + description: | + The possible values are `count` to sort by the number of matches in + the aggregation, `keyAsString` to sort alphabetically based on the + key in the aggregation, and `keyAsNumber` to sort numerically based + on the key in the aggregation. Required. + enum: + - count + - keyAsString + - keyAsNumber + isDescending: + type: boolean + description: | + Set to `true` to specify the sort order as descending. Optional, + defaults to `false` (ascending). + default: false + minimumCount: + type: integer + format: int32 + description: | + The minimum number of items that should be present in the + aggregation for the bucket to be returned in the response. + Optional, default is 0. + minimum: 0 + default: 0 + ranges: + type: array + description: | + Specifies the manual ranges to compute the aggregation buckets. + This is only valid for non-string facets of date or numeric type. + Optional. Follows the + [MS Graph bucketAggregationRange](https://learn.microsoft.com/en-us/graph/api/resources/bucketaggregationrange) + resource type. + items: + $ref: '#/components/schemas/bucketAggregationRange' + bucketAggregationRange: + type: object + description: | + Specifies the lower and upper bound to compute a range aggregation + bucket. At least one of `from` or `to` must be provided. + anyOf: + - required: + - from + - required: + - to + properties: + from: + type: string + description: | + Defines the lower bound from which to compute the aggregation. + The value is always a string. Numeric bounds must be provided as + their string representation (e.g. `"1980"`). Date bounds must use + the `YYYY-MM-DDTHH:mm:ssZ` format. Optional if `to` is provided. + to: + type: string + description: | + Defines the upper bound up to which to compute the aggregation. + The value is always a string. Numeric bounds must be provided as + their string representation (e.g. `"2000"`). Date bounds must use + the `YYYY-MM-DDTHH:mm:ssZ` format. Optional if `from` is provided. searchResponse: type: object description: | @@ -6675,6 +6853,13 @@ components: information, you can adjust the `from` and `size` properties of the `searchRequest` accordingly. readOnly: true + aggregations: + type: array + description: | + Contains the collection of aggregations computed based on the + provided `aggregationOption` definitions in the request. + items: + $ref: '#/components/schemas/searchAggregation' searchHit: type: object description: | @@ -6698,6 +6883,46 @@ components: readOnly: true resource: $ref: '#/components/schemas/driveItem' + searchAggregation: + type: object + description: | + Provides the details of a search aggregation in the search response. + Follows the + [MS Graph searchAggregation](https://learn.microsoft.com/en-us/graph/api/resources/searchaggregation) + resource type. + properties: + field: + type: string + description: | + Defines the field in the request on which the aggregation was + computed. + buckets: + type: array + description: | + Defines the computed buckets for this aggregation. Buckets are + sorted according to the `sortBy` and `isDescending` specified in + the `bucketDefinition` of the corresponding `aggregationOption`. + items: + $ref: '#/components/schemas/searchBucket' + searchBucket: + type: object + description: | + Represents a single bucket in a search aggregation result. Follows the + [MS Graph searchBucket](https://learn.microsoft.com/en-us/graph/api/resources/searchbucket) + resource type. + properties: + key: + type: string + description: | + The discrete value of the field that was used to compute the + aggregation. For terms aggregations this is the field value. For + range aggregations this is a string representation of the range. + count: + type: integer + format: int64 + description: | + The approximate number of search matches that share the same value + specified in the `key` property. odata.error: required: - error From 271bb66d308d5ba05c29ef1f77171fa5243e3d44 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 1 Oct 2026 09:34:54 +0200 Subject: [PATCH 3/6] feat(search): aggregation filter tokens for bucket drilldown searchBucket carries a server-issued aggregationFilterToken, consumed verbatim through searchRequest.aggregationFilters to narrow a follow-up search to that bucket, matching exactly and case-sensitively. Same encoding MS Graph uses. --- api/openapi-spec/v1.0.yaml | 80 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/api/openapi-spec/v1.0.yaml b/api/openapi-spec/v1.0.yaml index 861fcde..11317bb 100644 --- a/api/openapi-spec/v1.0.yaml +++ b/api/openapi-spec/v1.0.yaml @@ -4765,6 +4765,18 @@ paths: - from: "2000" to: "2010" - from: "2010" + search with aggregation filter: + summary: Narrow results using an aggregation filter from a previous search + value: + requests: + - entityTypes: + - driveItem + query: + queryString: "mediatype:audio" + from: 0 + size: 25 + aggregationFilters: + - "audio.artist:\"ǂǂ5361786f6e\"" responses: '200': description: OK @@ -4794,10 +4806,37 @@ paths: buckets: - key: Saxon count: 42 + aggregationFilterToken: "\"ǂǂ5361786f6e\"" - key: Iron Maiden count: 35 + aggregationFilterToken: "\"ǂǂ49726f6e204d616964656e\"" - key: Judas Priest count: 28 + aggregationFilterToken: "\"ǂǂ4a7564617320507269657374\"" + search with aggregation filter: + summary: Drilldown with the Saxon bucket's aggregationFilterToken from the previous response; total equals that bucket's count + value: + value: + - searchTerms: + - "mediatype:audio" + hitsContainers: + - hits: + - hitId: "4b478257-c9e8-458a-b764-fd7ed6cc1087$5d58a5c3-b244-4e9c-b082-82c3f1adbe8a!9d94e3d7" + rank: 1 + resource: + name: And the Bands Played On.flac + size: 45678901 + audio: + artist: Saxon + album: Power & the Glory + title: And the Bands Played On + genre: Heavy Metal + year: 1983 + track: 1 + disc: 1 + duration: 235000 + total: 42 + moreResultsAvailable: true default: $ref: '#/components/responses/error' components: @@ -6684,6 +6723,27 @@ components: returned alongside the search results. Optional. items: $ref: '#/components/schemas/aggregationOption' + aggregationFilters: + type: array + description: | + Contains one or more filters to narrow search results to specific + buckets of a prior aggregation. Build each filter from the + response of a prior search that aggregated on the same field: take + the `aggregationFilterToken` of the wanted `searchBucket` and + combine it with the field as `{field}:{aggregationFilterToken}`, + e.g. `audio.artist:"ǂǂ5361786f6e"` for a terms bucket or + `audio.year:range(1980, 1990)` for a range bucket. Several buckets + of the same field are combined with + `{field}:or({aggregationFilterToken},{aggregationFilterToken})`. + Whitespace after the commas of `range(...)` and `or(...)` is + optional. + + Multiple filters can be provided as separate array items. This + results in a logical AND between the filters. Filters that are + not built from server-issued tokens are rejected with + `invalidRequest`. + items: + type: string searchQuery: type: object description: | @@ -6923,6 +6983,26 @@ components: description: | The approximate number of search matches that share the same value specified in the `key` property. + aggregationFilterToken: + type: string + readOnly: true + description: | + A token containing the encoded filter that narrows search matches + to this bucket. To use it, pass it as part of the + `aggregationFilters` property of a subsequent `searchRequest` in + the format `{field}:{aggregationFilterToken}`. The filter matches + the bucket `key` exactly and case-sensitively, so the narrowed + result set is the set of matches counted in this bucket. + + For terms buckets the token is the key encoded as lowercase hex of + its UTF-8 bytes, prefixed with `ǂǂ` (U+01C2 twice) and wrapped in + double quotes, e.g. `"ǂǂ5361786f6e"` for the key + `Saxon`. For range buckets the token is `range({from}, {to})` + with the bounds of the matching `bucketAggregationRange`; an open + lower bound is written as `min`, an open upper bound as `max` + followed by `to="le"`, e.g. `range(min, 1980)`, + `range(1980, 1990)` and `range(2010, max, to="le")`. This is the + same encoding MS Graph uses. odata.error: required: - error From 7282976b00603fe320a9bc303c3fd48004466a44 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 1 Oct 2026 09:34:54 +0200 Subject: [PATCH 4/6] feat(search): libregraph metric aggregations @libre.graph.metricDefinition (sum/min/max/avg) on aggregationOption and the @libre.graph.metric result on searchAggregation, the scalar counterpart of bucketDefinition/buckets. Libregraph extension. --- api/openapi-spec/v1.0.yaml | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/api/openapi-spec/v1.0.yaml b/api/openapi-spec/v1.0.yaml index 11317bb..475b25a 100644 --- a/api/openapi-spec/v1.0.yaml +++ b/api/openapi-spec/v1.0.yaml @@ -6797,6 +6797,33 @@ components: minimum: 1 bucketDefinition: $ref: '#/components/schemas/bucketDefinition' + '@libre.graph.metricDefinition': + $ref: '#/components/schemas/metricDefinition' + metricDefinition: + type: object + description: | + Provides the details of how to compute a scalar metric over the + aggregation `field`, the counterpart of `bucketDefinition` for + metric aggregations. When set on an `aggregationOption`, `size` and + `bucketDefinition` are ignored, and the corresponding + `searchAggregation` in the response carries a `@libre.graph.metric` + rather than `buckets`. Libregraph extension not present in MS Graph. + required: + - kind + properties: + kind: + type: string + description: | + The reducer applied to the field values of all matches. Required. + + `avg` is not a simple reducer (averages of averages are not + averages): the backend carries `(sum, count)` internally and + emits only the final value on the outermost merge. + enum: + - sum + - min + - max + - avg bucketDefinition: type: object description: | @@ -6964,6 +6991,31 @@ components: the `bucketDefinition` of the corresponding `aggregationOption`. items: $ref: '#/components/schemas/searchBucket' + '@libre.graph.metric': + $ref: '#/components/schemas/searchMetric' + searchMetric: + type: object + description: | + The result of a metric aggregation, the counterpart of `buckets` for + aggregations requested with a `@libre.graph.metricDefinition`. + Absent for terms and range aggregations. Libregraph extension not + present in MS Graph. + properties: + kind: + type: string + description: | + Echoes the `kind` of the corresponding `metricDefinition`, + allowing consumers (and the search service's cross-space merge + layer) to pick the right reducer when combining results. + enum: + - sum + - min + - max + - avg + value: + type: number + format: double + description: The scalar result of the metric. searchBucket: type: object description: | From c11b6bc715e32e1dbdbb9fae76bd9d594ce859a7 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 1 Oct 2026 09:34:55 +0200 Subject: [PATCH 5/6] feat(search): libregraph sub-aggregations @libre.graph.subAggregations nest aggregations within each bucket, on aggregationOption and searchBucket. Libregraph extension. --- api/openapi-spec/v1.0.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/api/openapi-spec/v1.0.yaml b/api/openapi-spec/v1.0.yaml index 475b25a..c4756c9 100644 --- a/api/openapi-spec/v1.0.yaml +++ b/api/openapi-spec/v1.0.yaml @@ -6797,6 +6797,18 @@ components: minimum: 1 bucketDefinition: $ref: '#/components/schemas/bucketDefinition' + '@libre.graph.subAggregations': + type: array + description: | + Nested aggregations computed within each bucket of this + aggregation. Libregraph extension not present in MS Graph. + + Backends that don't support native composite aggregations + (e.g. bleve) emulate them by walking the matched result + set; OpenSearch translates them to native composite + aggregations. + items: + $ref: '#/components/schemas/aggregationOption' '@libre.graph.metricDefinition': $ref: '#/components/schemas/metricDefinition' metricDefinition: @@ -7055,6 +7067,14 @@ components: followed by `to="le"`, e.g. `range(min, 1980)`, `range(1980, 1990)` and `range(2010, max, to="le")`. This is the same encoding MS Graph uses. + '@libre.graph.subAggregations': + type: array + description: | + Nested aggregation results, one per sub-aggregation requested + on the parent `aggregationOption`. Libregraph extension not + present in MS Graph. + items: + $ref: '#/components/schemas/searchAggregation' odata.error: required: - error From d6e08e4a73ceed25353fb3658e3621b72fbff7e2 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 1 Oct 2026 09:34:55 +0200 Subject: [PATCH 6/6] feat(search): allow $expand=thumbnails on the search query Libregraph extension: MS Graph search has no $expand and returns no thumbnails on search hits. --- api/openapi-spec/v1.0.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/api/openapi-spec/v1.0.yaml b/api/openapi-spec/v1.0.yaml index c4756c9..829ffff 100644 --- a/api/openapi-spec/v1.0.yaml +++ b/api/openapi-spec/v1.0.yaml @@ -4697,6 +4697,24 @@ paths: (https://learn.microsoft.com/en-us/graph/api/search-query). Request and response follow the MS Graph resource types; Libregraph additions carry the `@libre.graph.` prefix. + parameters: + - name: $expand + in: query + description: | + Relationships to expand inline on each hit's driveItem. Only + `thumbnails` is supported, attaching a preview thumbnail set for + thumbnailable mime types. Libregraph extension: MS Graph search has no + $expand and returns no thumbnails on search hits. + required: false + style: form + explode: false + schema: + type: array + uniqueItems: true + items: + type: string + enum: + - thumbnails requestBody: required: true content: