Be compliant with the standardized HTTP semantics (see {RFC-9110}[RFC-9110 "HTTP semantics"]) as summarized here.
{GET} requests are used to read either a single or a collection resource.
-
{GET} requests for individual resources will usually generate a {404} (if the resource does not exist).
-
{GET} requests for collection resources may return either {200} (if the collection is empty) or {404} (if the collection is missing).
-
{GET} requests must NOT have a request body payload (see {GET-with-Body}).
Note: {GET} requests on collection resources should provide sufficient filter and Pagination mechanisms.
APIs sometimes face the problem, that they have to provide extensive structured request information with {GET}, that may conflict with the size limits of clients, load-balancers, and servers. As we require APIs to be standard conform (request body payload in {GET} must be ignored on server side), API designers have to check the following two options:
-
{GET} with URL encoded query parameters: when it is possible to encode the request information in query parameters, respecting the usual size limits of clients, gateways, and servers, this should be the first choice. The request information can either be provided via multiple query parameters or by a single structured URL encoded string.
-
{POST} with body payload content: when a {GET} with URL encoded query parameters is not possible, a {POST} request with body payload must be used, and explicitly documented with a hint like in the following example:
paths:
/products:
post:
description: >
[GET with body payload](https://opensource.zalando.com/restful-api-guidelines/#get-with-body)
- no resources created: Returns all products matching the query passed
as request input payload.
requestBody:
required: true
content:
...
Note: It is no option to encode the lengthy structured request information using header parameters. From a conceptual point of view, the semantic of an operation should always be expressed by the resource names, as well as the involved path and query parameters. In other words by everything that goes into the URL. Request headers are reserved for general context information (see [183]). In addition, size limits on query parameters and headers are not reliable and depend on clients, gateways, server, and actual settings. Thus, switching to headers does not solve the original problem.
Hint: As {GET-with-Body} is used to transport extensive query parameters, the {cursor} cannot any longer be used to encode the query filters in case of cursor-based pagination. As a consequence, it is best practice to transport the query filters in the body payload, while using pagination links containing the {cursor} that is only encoding the page position and direction. To protect the pagination sequence the {cursor} may contain a hash over all applied query filters (See also [161]).
{PUT} requests are used to update (and sometimes to create) entire resources – single or collection resources. The semantic is best described as "please put the enclosed representation at the resource mentioned by the URL, replacing any existing resource.".
-
{PUT} requests are usually applied to single resources, and not to collection resources, as this would imply replacing the entire collection.
-
{PUT} requests are usually robust against non-existence of resources by implicitly creating the resource before updating.
-
On successful {PUT} requests, the server will replace the entire resource addressed by the URL with the representation passed in the payload. Subsequent reads will deliver the same payload, plus possibly server-generated fields like
modified_at
. -
Successful {PUT} requests return {200} or {204} (if the resource was updated — with or without returning the resource), {201} (if the resource was newly created), and {202} (if the request was accepted for asynchronous processing).
The updated/created resource may be returned as response payload. We recommend,
to not use it per default, but if the resource is enriched with
server-generated fields like version_number
. You may also support client side
steering (see [181]).
Important: It is good practice to keep the resource identifier management under control of the service provider and not the client, and, hence, to prefer {POST} for creation of (at least top-level) resources, and focus {PUT} on its usage for updates. However, in situations where the identifier and all resource attributes are under control of the client as input for the resource creation you should use {PUT} and pass the resource identifier as URL path parameter. Putting the same resource twice is required to be idempotent and to result in the same single resource instance (see {MUST} fulfill common method properties) without data duplication in case of repetition.
Hint: To prevent unnoticed concurrent updates and duplicate creations when using {PUT}, you [182] to allow the server to react on stricter demands that expose conflicts and prevent lost updates. See also [optimistic-locking] for details and options.
{POST} requests are idiomatically used to create single resources on a collection resource endpoint, but other semantics on single resources endpoint are equally possible. The semantic for collection endpoints is best described as "please add the enclosed representation to the collection resource identified by the URL". The semantic for single resource endpoints is best described as "please execute the given well specified request on the resource identified by the URL".
-
On a successful {POST} request, the server will create one or multiple new resources, usually returning the resources or their URI/URLs in the response.
-
For a single resource (tree) {POST} is expected to utilize the {Location} header pointing to the URL of the newly created resource (tree) — with or without returning the resource (tree).
-
For multiple resources {POST} may either return a collection of newly created resources as served by the {GET} collection endpoint, or a bulk response using the status code {207} (see also [152]).
-
Successful {POST} requests return {200} or {204} (if the resource was updated — with or without returning the resource), {201} (if the resource was newly created), and {202} (if the request was accepted for asynchronous processing).
Note: By using {POST} to create resources the resource ID must not be passed as request input date by the client, but created and maintained by the service and returned with the response payload.
Apart from resource creation, {POST} should be also used for scenarios that cannot be covered by the other methods sufficiently. However, in such cases make sure to document the fact that {POST} is used as a workaround (see e.g. {GET-with-Body}).
Hint: Posting the same resource twice is not required to be idempotent
(check {MUST} fulfill common method properties) and may result in multiple resources. However, you {SHOULD} consider to design POST
and PATCH
idempotent to
prevent this.
{PATCH} method extends HTTP via {RFC-5789}[RFC-5789] standard to update parts of the resource objects where e.g. in contrast to {PUT} only a specific subset of resource fields should be changed. The set of changes is represented in a format called a patch document passed as payload and identified by a specific media type. The semantic is best described as "please change the resource identified by the URL according to my patch document". The syntax and semantics of the patch document is not defined in {RFC-5789}[RFC-5789] and must be described in the API specification by using specific media types.
-
{PATCH} requests are usually applied to single resources as patching entire collection is challenging.
-
{PATCH} requests are usually not robust against non-existence of resource instances.
-
On successful {PATCH} requests, the server will update parts of the resource addressed by the URL as defined by the change request in the payload.
-
Successful {PATCH} requests return {200} or {204} (if the resource was updated — with or without returning the resource), and {202} (if the request was accepted for asynchronous processing).
Note: since implementing {PATCH} correctly is a bit tricky, we strongly suggest to choose one and only one of the following patterns per endpoint (unless forced by a backwards compatible change). In preference order:
-
Use {PUT} with complete objects to update a resource as long as feasible (i.e. do not use {PATCH} at all).
-
Use {PATCH} with {RFC-7396}[JSON Merge Patch] standard, a specialized media type
application/merge-patch+json
for partial resource representation to update parts of resource objects. -
Use {PATCH} with {RFC-6902}[JSON Patch] standard, a specialized media type
application/json-patch+json
that includes instructions on how to change the resource. -
Use {POST} (with a proper description of what is happening) instead of {PATCH}, if the request does not modify the resource in a way defined by the semantics of the standard media types above.
In practice {RFC-7396}[JSON Merge Patch] quickly turns out to be too limited, especially when trying to update single objects in large collections (as part of the resource). In this case {RFC-6902}[JSON Patch] is more powerful while still showing readable patch requests (see also JSON patch vs. merge). JSON Patch supports changing of array elements identified via its index, but not via (key) fields of the elements as typically needed for collections.
Note: Patching the same resource twice is not required to be idempotent
(check {MUST} fulfill common method properties) and may result in a changing result. However, you {SHOULD} consider to design POST
and PATCH
idempotent to
prevent this.
Hint: To prevent unnoticed concurrent updates when using {PATCH} you [182]
to allow the server to react on stricter demands that expose conflicts and
prevent lost updates. See [optimistic-locking] and {SHOULD} consider to design POST
and PATCH
idempotent for details and
options.
{DELETE} requests are used to delete resources. The semantic is best described as "please delete the resource identified by the URL".
-
{DELETE} requests are usually applied to single resources, not on collection resources, as this would imply deleting the entire collection.
-
{DELETE} request can be applied to multiple resources at once using query parameters on the collection resource (see DELETE with query parameters).
-
Successful {DELETE} requests return {200} or {204} (if the resource was deleted — with or without returning the resource), or {202} (if the request was accepted for asynchronous processing).
-
Failed {DELETE} requests will usually generate {404} (if the resource cannot be found) or {410} (if the resource was already traceably deleted before).
Important: After deleting a resource with {DELETE}, a {GET} request on the resource is expected to either return {404} (not found) or {410} (gone) depending on how the resource is represented after deletion. Under no circumstances the resource must be accessible after this operation on its endpoint.
{DELETE} request can have query parameters. Query parameters should be used as filter parameters on a resource and not for passing context information to control the operation behavior.
DELETE /resources?param1=value1¶m2=value2...¶mN=valueN
Note: When providing {DELETE} with query parameters, API designers must carefully document the behavior in case of (partial) failures to manage client expectations properly.
The response status code of {DELETE} with query parameters requests should be similar to usual {DELETE} requests. In addition, it may return the status code {207} using a payload describing the operation results (see [152] for details).
In rare cases {DELETE} may require additional information, that cannot be classified as filter parameters and thus should be transported via request body payload, to perform the operation. Since {RFC-9110}#section-9.3.5[RFC-9110 Section 9.3.5] states, that {DELETE} has an undefined semantic for payloads, we recommend to utilize {POST}. In this case the POST endpoint must be documented with the hint {DELETE-with-Body} analog to how it is defined for {GET-with-Body}. The response status code of {DELETE-with-Body} requests should be similar to usual {DELETE} requests.
{HEAD} requests are used to retrieve the header information of single resources and resource collections.
-
{HEAD} has exactly the same semantics as {GET}, but returns headers only, no body.
Hint: {HEAD} is particular useful to efficiently lookup whether large resources or collection resources have been updated in conjunction with the {ETag}-header.
{OPTIONS} requests are used to inspect the available operations (HTTP methods) of a given endpoint.
-
{OPTIONS} responses usually either return a comma separated list of methods in the
Allow
header or as a structured list of link templates.
Note: {OPTIONS} is rarely implemented, though it could be used to self-describe the full functionality of a resource.
Request methods in RESTful services can be…
-
{RFC-safe} — the operation semantic is defined to be read-only, meaning it must not have intended side effects, i.e. changes, to the server state.
-
{RFC-idempotent} — the operation has the same intended effect on the server state, independently whether it is executed once or multiple times. Note: this does not require that the operation is returning the same response or status code.
-
{RFC-cacheable} — to indicate that responses are allowed to be stored for future reuse. In general, requests to safe methods are cacheable, if it does not require a current or authoritative response from the server.
Note: The above definitions, of intended (side) effect allows the server to provide additional state changing behavior as logging, accounting, pre- fetching, etc. However, these actual effects and state changes, must not be intended by the operation so that it can be held accountable.
Method implementations must fulfill the following basic properties according to {RFC-9110}#section-9.2[RFC 9110 Section 9.2]:
Method | Safe | Idempotent | Cacheable |
---|---|---|---|
{GET} |
{YES} |
{YES} |
{YES} |
{HEAD} |
{YES} |
{YES} |
{YES} |
{POST} |
{NO} |
{AT} No, but {SHOULD} consider to design |
{AT} May, but only if specific {POST} endpoint is safe. Hint: not supported by most caches. |
{PUT} |
{NO} |
{YES} |
{NO} |
{PATCH} |
{NO} |
{AT} No, but {SHOULD} consider to design |
{NO} |
{DELETE} |
{NO} |
{YES} |
{NO} |
{OPTIONS} |
{YES} |
{YES} |
{NO} |
{TRACE} |
{YES} |
{YES} |
{NO} |
Note: [227].
In many cases it is helpful or even necessary to design {POST} and {PATCH} idempotent for clients to expose conflicts and prevent resource duplicate (a.k.a. zombie resources) or lost updates, e.g. if same resources may be created or changed in parallel or multiple times. To design an idempotent API endpoint owners should consider to apply one of the following three patterns.
-
A resource specific conditional key provided via
If-Match
header in the request. The key is in general a meta information of the resource, e.g. a hash or version number, often stored with it. It allows to detect concurrent creations and updates to ensure idempotent behavior (see [182]). -
A resource specific secondary key provided as resource property in the request body. The secondary key is stored permanently in the resource. It allows to ensure idempotent behavior by looking up the unique secondary key in case of multiple independent resource creations from different clients (see {Should} use secondary key for idempotent
POST
design). -
A client specific idempotency key provided via {Idempotency-Key} header in the request. The key is not part of the resource but stored temporarily pointing to the original response to ensure idempotent behavior when retrying a request (see [230]).
Note: While conditional key and secondary key are focused on handling concurrent requests, the idempotency key is focused on providing the exact same responses, which is even a stronger requirement than the idempotency defined above. It can be combined with the two other patterns.
To decide, which pattern is suitable for your use case, please consult the following table showing the major properties of each pattern:
Conditional Key | Secondary Key | Idempotency Key | |
---|---|---|---|
Applicable with |
{PATCH} |
{POST} |
{POST}/{PATCH} |
HTTP Standard |
{YES} |
{NO} |
{NO} |
Prevents duplicate (zombie) resources |
{YES} |
{YES} |
{NO} |
Prevents concurrent lost updates |
{YES} |
{NO} |
{NO} |
Supports safe retries |
{YES} |
{YES} |
{YES} |
Supports exact same response |
{NO} |
{NO} |
{YES} |
Can be inspected (by intermediaries) |
{YES} |
{NO} |
{YES} |
Usable without previous {GET} |
{NO} |
{YES} |
{YES} |
Note: The patterns applicable to {PATCH} can be applied in the same way to {PUT} and {DELETE} providing the same properties.
If you mainly aim to support safe retries, we suggest to apply conditional key and secondary key pattern before the idempotency key pattern.
Note: like for {PUT}, successful {POST} or {PATCH} returns {200} or {204} (if the resource was updated — with or without returning the resource), or {201} (if resource was created). Hence, clients can differentiate successful robust repetition from resource created server activity of idempotent {POST}.
The most important pattern to design {POST} idempotent for creation is to introduce a resource specific secondary key provided in the request body, to eliminate the problem of duplicate (a.k.a zombie) resources.
The secondary key is stored permanently in the resource as alternate key or combined key (if consisting of multiple properties) guarded by a uniqueness constraint enforced server-side, that is visible when reading the resource. The best and often naturally existing candidate is a unique foreign key, that points to another resource having one-on-one relationship with the newly created resource, e.g. a parent process identifier.
A good example here for a secondary key is the shopping cart ID in an order resource.
Note: When using the secondary key pattern without {Idempotency-Key} all subsequent retries should fail with status code {409} (conflict). We suggest to avoid {200} here unless you make sure, that the delivered resource is the original one implementing a well defined behavior. Using {204} without content would be a similar well defined option.
Typically REST APIs are designed as synchronous interfaces where all server-side processing and state changes initiated by the call are finished before delivering the result as response. However, in long running request processing situations you may make use of asynchronous interface design with multiple calls: one for initiating the asynchronous processing and subsequent ones for accessing the processing status and/or result.
We recommend an API design that represents the asynchronous request processing
explicitly via a job resource that has a status and is different from the
actual business resource. For instance, POST /report-jobs
returns HTTP status
code {201} to indicate successful initiation of asynchronous processing
together with the job-id passed in the response payload and/or via the URL of
the {Location} header. The job-id or {Location} URL then can be used to poll
the processing status via GET /report-jobs/{id}
which returns HTTP status
code {200} with job status and optional report-id as response payload. Once
returned with job status finished
, the report-id is provided and can be used
to fetch the result via GET /reports/{id}
which returns {200} and the report
object as response payload.
Alternatively, if you do not to follow the recommended practice of providing a
separate job resource, you may use POST /reports
returning a status code
{202} together with the {Location} header to indicate successful initiation of
the asynchronous processing. The {Location} URL is used to fetch the report via
GET /reports/{id}
which returns either {200} and the report resource or {202}
without payload, if the asynchronous processing is still ongoing.
Hint: Do not use response code {204} or {404} instead of {202} here — it is misleading since neither is the processing successfully finished, nor do we want to suggest a client failure.
Header and query parameters allow to provide a collection of values, either by providing a comma-separated list of values or by repeating the parameter multiple times with different values as follows:
Parameter Type | Comma-separated Values | Multiple Parameters | Standard |
---|---|---|---|
Header |
|
|
{RFC-9110}#section-5.3[RFC 9110 Section 5.3] |
Query |
|
|
{RFC-6570}#section-3.2.8[RFC 6570 Section 3.2.8] |
As OpenAPI does not support both schemas at once, an API specification must explicitly define the collection format to guide consumers as follows:
Parameter Type | Comma-separated Values | Multiple Parameters |
---|---|---|
Header |
|
not allowed (see {RFC-9110}#section-5.3[RFC 9110 Section 5.3]) |
Query |
|
|
When choosing the collection format, take into account the tool support, the escaping of special characters and the maximal URL length.
We prefer the use of query parameters to describe resource-specific query languages for the majority of APIs because it’s native to HTTP, easy to extend and has an excellent implementation support in HTTP clients and web frameworks.
By simple query language we mean one or more name-value pairs that are combined
in one way only with and
semantics.
Query parameters should have the following aspects specified:
-
Reference to corresponding property, if any
-
Value range, e.g. inclusive vs. exclusive
-
Comparison semantics (equals, less than, greater than, etc)
-
Implications when combined with other queries, e.g. and vs. or
How query parameters are named and used is up to individual API designers, here are a few tips that could help to decide whether to use simple or more complex query language:
-
Consider using simple query language when API is built to be used by others (external teams):
-
no additional effort/logic to form the query
-
no ambiguity in meaning of the query parameters. For example in
GET /items?user_id=gt:100
, isuser_id
greater than100
or isuser_id
equal togt:100
? -
easy to read, no learning curve
-
-
For internal usage or specific use case a more complex query language can be used (such as
price gt 10
orprice[gt]=10
orprice>10
etc.). Also please consider following our guidance for designing complex query languages with JSON.
The following examples should serve as ideas for simple query language:
-
name=Zalando
,creation_year=2023
,updated_by=user1
(query elements based on property equality) -
created_at=2023-09-18T12:12:00.000Z
,sort=id:desc
(query elements based on logical properties) -
color=red,green,blue,multicolored
(query elements based on multiple choice possibility)-
for these type of filters, consider to use guidance to have smth like
filters={"color":["red","green","blue"]}
.
-
-
max_length=5
— query elements based on upper/lower bounds (min
andmax
) -
shorter_than=5
— query elements using terminology specific e.g. to length -
price_lower_than=50
orprice_lower_than_or_equal=50
-
created_before=2019-07-17
oractive_until=2023-09-18T12:12:00.000Z
-
Using terminology specific to time: before, after, since and until
-
-
min_length=2
— query elements based on upper/lower bounds (min
andmax
) -
created_after=2019-07-17
ormodified_since=2019-07-17
-
Using terminology specific to time: before, after, since and until
-
-
price_higher_than=50
orprice_equal_or_higher_than=50
-
offset=10
andlimit=5
(query elements for pagination regardless customer sorting) -
limit=5
andcreated_after=2019-07-17
(query elements for keyset pagination)-
when sorting is in place and new elements are inserted, it prevents showing repeated/missing results due to offset shift.
-
Please check conventional query parameters for pagination and sorting and you can also find additional info in Pagination section below.
We don’t advocate for or against certain names because in the end APIs should be free to choose the terminology that fits their domain the best.
Minimalistic query languages based on query parameters are suitable for simple use cases with a small set of available filters that are combined in one way and one way only (e.g. and semantics). Simple query languages are generally preferred over complex ones.
Some APIs will have a need for sophisticated and more complex query languages. Dominant examples are APIs around search (incl. faceting) and product catalogs.
Aspects that set those APIs apart from the rest include but are not limited to:
-
Unusual high number of available filters
-
Dynamic filters, due to a dynamic and extensible resource model
-
Free choice of operators, e.g.
and
,or
andnot
APIs that qualify for a specific, complex query language are encouraged to use nested JSON data structures and define them using OpenAPI directly. The provides the following benefits:
-
Data structures are easy to use for clients
-
No special library support necessary
-
No need for string concatenation or manual escaping
-
-
Data structures are easy to use for servers
-
No special tokenizers needed
-
Semantics are attached to data structures rather than text tokens
-
-
Consistent with other HTTP methods
-
API is defined in OpenAPI completely
-
No external documents or grammars needed
-
Existing means are familiar to everyone
-
JSON-specific rules and most certainly needs to make use
of the GET
-with-body pattern.
The following JSON document should serve as an idea how a structured query might look like.
{
"and": {
"name": {
"match": "Alice"
},
"age": {
"or": {
"range": {
">": 25,
"<=": 50
},
"=": 65
}
}
}
}
Feel free to also get some inspiration from:
Sometimes certain collection resources or queries will not list all the possible elements they have, but only those for which the current client is authorized to access.
Implicit filtering could be done on:
-
the collection of resources being returned on a {GET} request
-
the fields returned for the detail information of the resource
In such cases, the fact that implicit filtering is applied must be documented in the API specification’s endpoint description. Consider caching aspects when implicit filtering is provided. Example:
If an employee of the company Foo accesses one of our business-to-business
service and performs a {GET} /business-partners
, it must, for legal reasons,
not display any other business partner that is not owned or contractually
managed by her/his company. It should never see that we are doing business
also with company Bar.
Response as seen from a consumer working at FOO
:
{
"items": [
{ "name": "Foo Performance" },
{ "name": "Foo Sport" },
{ "name": "Foo Signature" }
]
}
Response as seen from a consumer working at BAR
:
{
"items": [
{ "name": "Bar Classics" },
{ "name": "Bar pour Elle" }
]
}
The API Specification should then specify something like this:
paths:
/business-partner:
get:
description: >-
Get the list of registered business partner.
Only the business partners to which you have access to are returned.