Be compliant with the standardized HTTP method semantics summarized as follows:
{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".
-
Modeled as
POST /…/plural-noun
, whereplural-noun
indicates the type of object being created. -
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), 202 (if the request was accepted but has not been finished yet), and exceptionally 204 with Location header (if the actual resource is not returned).
-
If the POST is used to create or update a resource, then the response payload needs to include every field from the request, and may include additional fields.
-
If the POST is used as an action, then the response may be different from the request schema.
-
Specifying a value for a system-generated field in the input results in a 400 Bad Request response.
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.
-
Modeled as
GET /…/plural-noun/{nounId}
-
On success, returns a 200 with the DTO.
-
A 404 is returned if the referenced object is not found.
-
Query params are allowed, for example, to return the object at different levels of detail.
-
Is free of side-effects.
-
Modeled as
GET /…/plural-noun
. -
For API version 3, on success, returns a 200 with an array of objects.
[ { "id": "123", "name": "John" }, ... ]
-
Proposed for API version 4, on success, returns a 200 with a JSON array of objects enveloped inside an object. By returning an object as the top level for all responses, we allow our APIs to extend without breaking backwards compatibility. If there was ever a need to add an additional field to a response that returns an array (ex. pagination links in the body), then we would need to break the API by wrapping it in an object. By requiring objects at the top level from the start, we avoid this in the future.
{ "results": [...], "count": ... }
-
Supports pagination via
limit
andoffset
query parameters unless the back end data store makes this impossible or prohibitively expensive. -
The default value for
limit
is 250 unless the endpoint documentation states otherwise. -
The standard
filters
query parameter is preferred over custom filtering query params. -
If
filters
are used, the supported fields and operations are whitelisted. Unsupported filters should result in an error response. -
If
filters
refer to fields in nested objects, then "." notation is used, for examplefilters=owner.name eq "leah.pierce"
-
Custom query params are allowed if filters cannot be used.
-
If at all possible, supports reading a list of objects by their ids, either in the form of a filter, i.e.
filters=id in (id0, id1, …, idN)
or a custom query param. -
Use of single boolean-valued params should be avoided; strings, enumerated values, or comma-separated values are preferred.
-
The standard
sorters
query parameter may be used for sorting. -
If
sorters
are used, the supported fields are whitelisted. -
Results are not implicitly filtered or scoped based on the current logged in user. If such filtering is required it is via an explicit query param taking an identity id. By convention, me can stand in for the currently logged in user’s identity id as the value for such a param.
-
Is free of side-effects.
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://sailpoint-oss.github.io/sailpoint-api-guidelines/#get-with-body) - no resources created:
Returns all products matching the query passed as request input payload.
requestBody:
required: true
content:
...
Modeled as PUT /…/plural-noun/{nounId}
{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)
-
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)
-
Returns a 404 if the object does not exist and the endpoint does not support PUT as a means of creation.
-
Does a complete replacement of the referenced object and does not attempt to merge the input DTO with the existing object.
Important: It is good practice to prefer {POST} over {PUT} for creation of (at least top-level) resources. This leaves the resource ID management under control of the service and not the client, and focus {PUT} on its usage for updates. However, in situations where {PUT} is 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 (see {MUST} fulfill common method properties).
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.
{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.
-
Modeled as
PATCH /../plural-noun/{nounId}
-
{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)
-
Returns a 404 if the object does not exist.
-
If synchronous, and the patch cannot be successfully applied, returns a 400.
-
Mutable DTO fields are documented.
{DELETE} requests are used to delete resources. The semantic is best described as "please delete the resource identified by the URL".
-
Modeled as
DELETE /../plural-noun/{nounId}
-
{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 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.
{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-7231}#section-4.3.5[RFC-7231] 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 cachable, 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-7231}[RFC 7231]:
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} |
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 {MAY} 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.
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.
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. 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.