Be compliant with the standardized HTTP method semantics summarized as follows:
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
Note: GET requests on collection resources should provide sufficient #137 and [pagination] mechanisms.
APIs sometimes face the problem, that they have to provide extensive structured request information with GET, that may conflicts with the size limits of clients, load-balancers, and servers. As we require APIs to be standard conform (body 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 distributed to multiple query parameters or a single structured URL encoded string.
-
POST with body content: when a GET with URL encoded query parameters is not possible, a POST with body content must be used. In this case the endpoint must be documented with the hint
GET with body
to transport the GET semantic of this call.
Note: It is no option to encode the lengthy structured request information in header parameters. From a conceptual point of view, the semantic of an operation should always be expressed by resource name and query parameters, i.e. what goes into the URL. Request headers are reserved for general context information, e.g. FlowIDs. 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.
PUT requests are used to update (in rare cases 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 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)
-
successful PUT requests will usually generate 200 or 204 (if the resource was updated - with or without actual content returned), and 201 (if the resource was created)
Important: It is best practice to prefer POST over PUT for creation of (at least top-level) resources. This leaves the resource ID under control of the service and allows to concentrate on the update semantic using PUT as follows.
Tip: To prevent unnoticed concurrent updates when using PUT, the combination
of ETag
and If-(None-)Match
headers should be considered to signal
the server stricter demands to expose conflicts and prevent lost updates.
The section [optimistic-locking] also describes some alternatives to this
approach.
Note: In the rare cases where PUT is although used for resource creation, the resource IDs are maintained by the client and passed as a URL path segment. Putting the same resource twice is required to be idempotent and to result in the same single resource instance.
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".
-
on a successful POST request, the server will create one or multiple new resources and provide their URI/URLs in the response
-
successful POST requests will usually generate 200 (if resources have been updated), 201 (if resources have been created), and 202 (if the request was accepted but has not been finished yet)
The semantic for single resource endpoints is best described as "please execute the given well specified request on the resource identified by the URL".
Generally: POST should be used for scenarios that cannot be covered by the other methods sufficiently. In such cases, make sure to document the fact that POST is used as a workaround (see GET with Body).
Note: Resource IDs with respect to POST requests are created and maintained by server and returned with response payload.
Tip: Posting the same resource twice is by itself not required to be idempotent and may result in multiple resource instances. However, it is best practice to implement POST idempotent, by temporary or permanently storing foreign keys or idempotency keys to identify re-send requests on the server. Here, a foreign key is a natural partner resource ID, e.g. the shopping cart ID is a foreign key candidate for an order, while an idempotency key is an artificial request key that is preserved when re-sending the POST request.
PATCH requests are used to update parts of single resources, i.e. where only a specific subset of resource fields should be replaced. The semantic is best described as "please change the resource identified by the URL according to my change request". The semantic of the change request is not defined in the HTTP standard and must be described in the API specification by using suitable 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 will usually generate 200 or 204 (if resources have been updated with or without updated content returned)
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 partial objects to only update parts of a resource, whenever possible. (This is basically JSON Merge Patch, a specialized media type
application/merge-patch+json
that is a partial resource representation.) -
use PATCH with JSON Patch, 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 media type.
In practice 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 cases JSON Patch can shown its full power while still showing readable patch requests (see also JSON patch vs. merge).
Tip: To prevent unnoticed concurrent updates when using PATCH, the
combination of ETag
and If-Match
headers should be considered to
signal the server stricter demands to expose conflicts and prevent lost
updates.
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
-
successful DELETE requests will usually generate 200 (if the deleted resource is returned) or 204 (if no content is returned)
-
failed DELETE requests will usually generate 404 (if the resource cannot be found) or 410 (if the resource was already 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.
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: This 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.
An operation can be…
-
idempotent, i.e. operation will have the same effect on the server’s state if executed once or multiple times (note: this does not necessarily mean returning the same response or status code)
-
safe, i.e. must not have side effects such as state changes
Method implementations must fulfill the following basic properties:
HTTP method | safe | idempotent |
---|---|---|
OPTIONS |
Yes |
Yes |
HEAD |
Yes |
Yes |
GET |
Yes |
Yes |
PUT |
No |
Yes |
POST |
No |
No |
DELETE |
No |
Yes |
PATCH |
No |
No |
Sometimes, query parameters and headers allow to provide a list of values,
either by providing a comma-separated list (csv
) or by repeating the
parameter multiple times with different values (multi
). The API
specification should explicitly define one type as follows:
Description | OpenAPI 3.0 | OpenAPI 2.0 | Example |
---|---|---|---|
Comma separated values |
|
|
|
Multiple parameters |
|
|
|
When choosing the collection format, take into account the tool support, the escaping of special characters and the maximal URL length.
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 return on a parent
GET
request -
the fields returned for the resource’s detail
In such cases, the implicit filtering must be in the API Specification (in its description).
Consider caching considerations when implicitely filtering.
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.