Queries
The query part of the URI is for refining and/or modifying what is to be read or modified.
The query consists of keys and/or key-value pairs. Each key is either one of the defined attributes of the API or an attribute described below.
An API MUST return error code 400, Bad Request if one or more of the keys are unrecognized.
Introduction
Keys or key-values MAY be used for:
Selection of a subset of the resource
on the items(
id‘s) returnedon the fields returned for each entry
Sorting of the resource (discouraged since it draws resources in the server, and is better done in the client)
Pagination of the resource
Selection of when in time the data to be used is valid (temporal selection)
Selection of when in time the data to be used is written (temporal selection)
Multiple keys/key-value-pairs MAY be used in the same API call, separated by the ampersand (“&”) character, and the result of such a call is that of all the supplied keys/key-value-pairs used in an “AND” fashion. This means that an API call might look like: .../bicycles?size=28&order=height:asc#..., and will return all bicycles of size 28 ordered by height ascending.
NOTE: Following [RFC3986], some characters are “reserved” in a URI and SHOULD be URL-encoded (the exceptions are “-” (hyphen), “_” (underscore), “.” (period), and “~” (tilde), and of course all characters and numbers). But when used in a query or fragment it is recommended for readability NOT to URL-encode where not necessary in order to increase readability. (The only exception is really the “#” (hash) in a query, since it breaks the query and starts the fragment. So if a value includes hashes, the hashes MUST be URL-encoded.)
_meta
As a service to the client, a response to a query SHOULD include metadata defining what query has been applied to the request. Such metadata SHALL be part of the response, and SHALL be a top level key-value consisting of the key _meta (note the leading underscore), and having an object of key-values as its value.
Selection queries
Key-value-pairs can be used to select a subset of the resource based on item values: ...?size=24,28&lights=on®istered=true#....
There are two different ways of combining key-values; using & (ampersand) or using , (comma). Their meanings are:
&: “And”. The result of the query is the result of the different parts “and”ed together.Example:
...?firstName=John&lastName=Doe#.... The result of the query are all the items havingfirstnameequal toJoeANDlastNameequal toDoe.
,: “Or”. The result of the query is the result of the same key having any of the values in the comma-separated list.It is semantically valid to use
&for OR’ing values when the same key is repeated, ex:...?firstName=John&firstName=Jane&...since the AND’ing of the same parameter (with different values) will otherwise result in the empty set. However, it is a bit counter-intuitive to use&this way and therefore it is also accepted to use,for OR’ing.
Example:
...?firstName=John,Jane&lastName=Doe#.... The result of the query are all the items havingfirstnameequal toJoeORfirstnameequal toJane– ANDlastNameequal toDoe.Note the precedence order: The
,(OR)’s are done before any&(AND)’s.
Note that he above does not let you specify “I want all items with first name Jane or last name Doe”.
When can a selection be used?
Selection queries MAY be used for GET (and hence also HEAD), PATCH, and DELETE methods on collections. Selection queries MUST NOT be used for other methods.
Error returns
Using selection queries where prohibited MUST return status code 400; Bad Request.
Using modifiers that do not apply to a property MUST return status code 400; Bad Request.
Examples
The following selections are examples of what can be used:
Query | Description |
|---|---|
size=24,28 | The attribute exists and has one of the specified values |
lights=on | This is also valid for strings, booleans, enums, and so on |
registered | The attribute exists |
fromDate=gt.2023-01-01T11:12:13 | “greater than” (For more modifiers, see below) |
Different ways to specify selection queries
The selection queries are normally specified as part of the URL. However, there are situations when you do not want to expose data in the query, such as when you are requesting a selection based on sensitive information such as SSN. Therefore, selection queries can also be specified as a header field or in the body (our APIs can accept a body as part of a GET call, however that is nonstandard behavior). If supplied, then selection queries in both the header field Query and in the body are honored.
Modifiers
A modifier is a keyword modifying the selection by allowing for comparing a property to an open or closed range or a pattern, or to any other non-definitive value. This allows for comparing to a range of values, such as within a date range (which might be the typical use case).
A . (period) is used to separate the modifier from the value. If a modifier is unknown, it is treated as part of the value. (So, the query ...?qwe=asd.zxc&... does not treat asd as a modifier, but rather the whole asd.zxc is treated as the value that qwe is matched against)
Modifiers can be specified as below; also other types of ranges are allowed and MUST be documented.
Modifier | Data type | Description |
|---|---|---|
lt, le, ge, gt, ne | Numeric / Time | Less than, Less than or Equal, Greater then or Equal, Greater than, Not equal (Default, without a range, is “Equal”). |
lt, le, ge, gt, ne | String | Lexical string compare. |
~ | String | Lexical string “contains” (exact casing). Ex: |
Response
The response SHOULD include the selection as a key-value list:
{
...
"_meta": {
...
"select": {
"size": [24, 28],
"lights": "on",
"isRegistered": true,
"fromDate": { "gt": "2023-01-01T11:12:13" }
}
...
}
...
}
For selections using , (OR) the value for the select is an array of the values, For selections using modifiers the value is an object with the selector as the first argument and the value as the second.
Ordering queries
Key-value pairs can be used to sort the results. Sorting is done as in the example: ...order=height:asc,length.... This ordering orders the result according to the height property of the data. The meaning of “sorted in ascending order” is depending on the data type for the attribute. Multiple sort orders are separated by a comma (“,”). Orders MAY be either ascending (denoted by the asc keyword) or descending (denoted by the desc keyword). If specified, asc or desc MUST be separated from the property by a colon character (:). If not specified the asc ordering is assumed.
Ordering queries MAY be used for the GET (and hence also HEAD) method. Ordering queries MUST NOT be used for other methods.
Error returns
Using ordering queries where prohibited MUST return status code 400; Bad Request.
Using a ordering query specifying an attribute that does not exist MUST return status code 400; Bad Request.
Using multiple order keys (...&order=height:asc&order=length&...) is not allowed and MUST return status code 400; Bad Request.
Response
The response SHOULD include the ordering as an array where the order of the elements are the sort order, and the columns sorted on is followed by a colon and the sort order (asc or desc).
{
...
"_meta": {
...
"order": [
{ "height": "asc" },
{ "length": "asc" }
]
...
}
...
}
Note: Question: Why an array? Wouldn’t an ordered list of key-values be better? Answer: An array is guaranteed to maintain order, whereas key-values are not. So in the latter case the sort order might be permuted.
Range queries
Query parameters can be used to select a range of entries being returned.
There are different ways of defining a range; one could say “Page size is 20 & give me page 3” (page-based), or “return entries 40 to 59” (item-based) – both yield the same result (if using zero based counting). Another way of specifying a range is to use server based cursors, but we are not supporting that (yet).
It is up to the API to decide what range query method to support; it SHOULD be documented in the API specification.
Ranging queries MAY be used for the GET (and hence also HEAD) method. Paging queries MUST NOT be used for other methods.
NOTE: If a partial response query is used in conjunction with an ordering and/or selection query, it is the list of items after applying the ordering and/or selection query that is subject to ranging!
Errors
Using range queries where prohibited MUST return status code 400; Bad Request.
Only one range query method (from the below) MAY be specified; else status code 400; Bad Request will be returned.
Page-based
Page-based range SHOULD use the format "...page=<#>&pageSize=<#>..." where both page and pageSize are inclusive and with zero-based indexing.
Errors
A page-based query where either value is not a positive number MUST return status code 400; Bad Request.
Response
The response SHOULD include the following information:
{
...
"_meta": {
...
"page": {
"page": <#>,
"pageSize": <#>,
},
"count": <#>,
...
}
...
}
where page and pageSize are echoing the parameters sent in the request and count is the actual number of items returned.
Index-based
Index-based range SHOULD use the format "...from=<#>&to=<#>..." where both from and to are inclusive and with zero-based indexing.
Errors
An index-based range where either value is not a positive number MUST return status code 400; Bad Request.
Using an item-based range where from > to MUST return status code 400; Bad Request. If, however, from > maximum index, or to < minimum index, then 404; Not Found MUST be returned.
Response
The response SHOULD include the following information:
{
...
"_meta": {
...
"index": {
"from": <#>,
"to": <#>
},
"count": <#>,
...
}
...
}
where from and to are echoing the parameters sent in the request and count is the actual number of items returned.
Field selection
The field selection query selects which fields are to be returned. Using this, it is possible to select which fields are to be returned, so that not the whole object(s) is returned when only a few fields are needed.
An API may define which fields are available for field selection, and it MAY be none (not accepting field selection) or a subset of the fields available. This SHALL be documented for each API path/method.
Field selection is done as in the example: ...fields=id,address... where the key fields only appear once in the query and the fields selected (if multiple) are separated by a comma (“,“).
Multiple fields keys in the query is an error and MUST return 400, Bad Request.
Unrecognized field names in the query value is an error and MUST return 400, Bad Request.
Response
The response SHOULD include the following information:
{
...
"_meta": {
...
"fields": [
"fieldname",
"fieldname",
...
],
...
}
...
}
Historic data
There are two ways to request historic data – asOf and asAt.
Data as valid at a point in time – asOf
To select a specific temporal instance of the data, based on the semantics of the data, the selector asOf can be used.
The temporal selector is done as: ...?asOf=<datetime>&..., where the value datetime is a Date-Time or Timestamp value.
This keyword is used to get the data valid for a specific point in time. Example: Let’s say you have a field named “title”. A person has the title “Sales Rep”, but on Mar 1st that person is promoted to “Sr. Sales Rep“. However, the change in the HR system is not done until Mar 5th, but the validity date for that title change is set to Mar 1st. Now, to get the data valid as of Mar 1st, the selector ...?asOf=<datetime>&... can be used! If that request is sent om Mar 4th, the result would be “Sales Rep”, but for the same request sent on Mar 6th, the result would be “Sr. Sales Rep”!
A common pattern for the semantics of asOf is to have the first instance of the data valid as of any point in time. Later updates to the data might then include a date-time or a date from which that data is valid, and that date-time/date might very well be in the past.
Note: It is up to each API to define which field is used for the asOf selector.
Errors
A value that does not evaluate to a date-time or timestamp is an error and MUST return 400, Bad Request. Multiple asOf selectors is an error and MUST return 400, Bad Request.
Response
The response SHOULD include the following information:
{
...
"_meta": {
...
"asOf": "date-time",
...
}
...
}
Data as written at a point in time – asAt
To select a specific temporal instance of the data, based on the “write timestamp” of the data, the selector asAt can be used.
The temporal selector is done as: ...?asAt=<datetime>&..., where the value datetime is a Date-Time or Timestamp value.
This keyword is used to get the data valid at a specific point in time. Example: Let’s say you have a field named “title”. A person has the title “Sales Rep”, but on Mar 1st that person is promoted to “Sr. Sales Rep“. However, the change in the HR system is not done until Mar 5th, but the validity date for that title change is set to Mar 1st. Now, to get the data valid as it was written at Mar 1st, the selector ...?asAt=<datetime>&... can be used! If that request is sent om Mar 4th, the result would be “Sales Rep”, and for the same request sent on Mar 6th, the result would still be “Sales Rep”!
Errors
A value that does not evaluate to a date-time or timestamp is an error and MUST return 400, Bad Request. Multiple asAt selectors is an error and MUST return 400, Bad Request.
Response
The response SHOULD include the following information:
{
...
"_meta": {
...
"asAt": "date-time",
...
}
...
}
Difference between asOf and asAt
In this example, asOf is depending on the field thingUpdated. It is of type date-time and is nullable. (A null value is normally treated as “since epoch”, but this is API dependent.)
As you can see in the example, asking for asOf returns the data as valid for that date-time based on the value of the field thingUpdated, whereas asking for asAt returns the value as it existed at that date-time.