This original content was originated published at: https://github.com/paypal/api-standards/blob/master/patterns.md Thanks PayPal for such great reference, that can help many developer around the world.
This document lists various useful patterns for API design. We encourage API developers to consider the following patterns as a guide while designing APIs for services.
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
The words “REST” and “RESTful” MUST be written as presented here, representing the acronym as all upper-case letters. This is also true of “JSON,” “XML,” and other acronyms.
Machine-readable text, such as URLs, HTTP verbs, and source code, are represented using a fixed-width font.
URIs containing variable blocks are pecified according to URI Template RFC 6570. For example, a URL containing a variable called account_id would be shown as https://paypal.com/accounts/{account_id}/.
HTTP headers are written in camelCase + hyphenated syntax, e.g. Foo-Request-Id.
Sanjay Dalal (former member: PayPal API Platform), Jason Harmon (former member: PayPal API Platform), Jayadeba Jena (PayPal API Platform), Nikhil Kolekar (PayPal API Platform), Gagan Maheshwari (former member: PayPal API Platform), George Petkov (former member: PayPal API Platform) and Andrew Todd (PayPal Credit).
For creating a new resource, use POST
method. The request body for POST
may be somewhat different than for GET
/PUT
response/request (typically fewer fields as the server will generate some values). In most cases, the service produces an identifier for the resource. In cases where identifier is supplied by the API consumer, use Create New Resource - Client Supplied ID
Once the POST
has successfully completed, a new resource will be created. Hypermedia links provide an easy way to get the URL of the newly created resource, using the rel
: self
, in addition to other links for operations that are allowed on the newly created resource. You may provide complete representation of the resource or partial with just HATEOAS links to retrieve the complete representation.
POST /{version}/{namespace}/{resource}
Note that server-generated values are not provided in the request.
POST /v1/vault/credit-cards
{
"payer_id": "user12345",
"type": "visa",
"number": "4417119669820331",
"expire_month": "11",
"expire_year": "2018",
"first_name": "Betsy",
"last_name": "Buyer",
"billing_address": {
"line1": "111 First Street",
"city": "Saratoga",
"country_code": "US",
"state": "CA",
"postal_code": "95070"
}
}
On successful execution, the method returns with status code 201
.
201 Created
{
"id": "CARD-1MD19612EW4364010KGFNJQI",
"valid_until": "2016-05-07T00:00:00Z",
"state": "ok",
"payer_id": "user12345",
"type": "visa",
"number": "xxxxxxxxxxxx0331",
"expire_month": "11",
"expire_year": "2018",
"first_name": "Betsy",
"last_name": "Buyer",
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/vault/credit-cards/CARD-1MD19612EW4364010KGFNJQI",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v1/vault/credit-cards/CARD-1MD19612EW4364010KGFNJQI",
"rel": "delete",
"method": "DELETE"
}
]
}
When an API consumer provides the resource identifier, PUT
method SHOULD be utilized, as the operation is idempotent, even during creation.
The same interaction as Create Resource is used here. 201
+ response body on resource creation, and 204
+ no response body when an existing resource is updated.
A collection resource should return a list of representation of all of the given resources (instances), including any related metadata. An array of resources should be in the items
field. Consistent naming of collection resource fields allow API clients to create generic handling for using the provided data across various resource collections.
The GET
verb should not affect the system, and should not change response on subsequent requests (unless the underlying data changes), i.e. it should be idempotent
. Exceptions to ‘changing the system’ are typically instrumentation/logging-related.
The list of data is presumed to be filtered based on the privileges available to the API client. In other words, it should not be a list of all resources in the domain. It should only be resources for which the client has authorization to view within its current context.
Providing a summarized, or minimized version of the data representation can reduce the bandwidth footprint, in cases where individual resources contain a large object.
If the service allows partial retrieval of the set, the following patterns MUST be followed.
Query parameters with regard to time range could be used to select a subset of items in the following manner:
start_time
or {property_name}_after
: A timestamp (in either Unix time or ISO-8601 format) indicating the start of a temporal range. start_time
may be used when there is only one unambiguous time dimension; otherwise, the property name should be used (e.g., processed_after
, updated_after
). The property SHOULD map to a time field in the representation.end_time
or {property_name}_before
: A timestamp (in either Unix time or ISO-8601 format) indicating the end of a temporal range. end_time
may be used when there is only one unambiguous time dimension; otherwise, the property name should be used (e.g., processed_before
, updated_before
). The property SHOULD map to a time field in the representation.Results could be ordered according to sorting related instructions given by the client. This includes sorting by a specific field’s value and sorting order as described in the query parameters below.
sort_by
: A dimension by which items should be sorted; the dimensions SHOULD be an attribute in the item’s representation; the default (ascending or descending) is left to the implementation and MUST be specified in the documentation.sort_order
: The order, one of asc
or desc
, indicating ascending or descending order.Any resource that could return a large, potentially unbounded list of resources in its GET
response SHOULD implement pagination using the patterns described here.
Sample URI path: /accounts?page_size={page_size}&page={page}
Clients MUST assume no inherent ordering of results unless a default sort order has been specified for this collection. It is RECOMMENDED that service implementers specify a default sort order whenever it would be useful.
page_size
: A non-negative, non-zero integer indicating the maximum number of results to return at one time. This parameter:
page
: A non-zero integer representing the page of the results. This parameter:
400 Bad Request
.page_size=100&page=3
–the resource MUST respond with the HTTP status code 200 OK
and an empty result list.page_token
: In certain cases such as querying on a large data set, in order to optimize the query execution while paginating, querying and retrieving the data based on result set of previous page migh be appropriate. Such a page_token
could be an encrypted value of primary keys to navigate next and previous page along with the directions.total_required
: A boolean indicating total number of items (total_items
) and pages (total_pages
) are expected to be returned in the response. This parameter:
false
.JSON response to a request of this type SHOULD be an object containing the following properties:
items
MUST be an array containing the current page of the result list.total_items
SHOULD be used to indicate the total number of items in the full result list, not just this page.
total_required
has been implemented by an API, then the value SHOULD only be returned when total_required
is set to true
.total_required
has not been implemented by an API, then the value MAY be returned in every response if necessary, useful, and performant.total_items
is constant. The value MAY change from one request to the next.total_pages
SHOULD be used to indicate how many pages are available, in total.
total_required
has been implemented by an API, then the value SHOULD only be returned when total_required
is set to true
.total_required
has not been implemented by an API, then the value MAY be returned in every response if necessary, useful, and performant.total_pages
is constant. The value MAY change from one request to the next.links
SHOULD be an array containing one or more HATEOAS link relations that are relevant for traversing the result list.Relationship | Description |
---|---|
self |
Refers to the current page of the result list. |
first |
Refers to the first page of the result list. If you are using page_token , you may not return this link. |
last |
Refers to the last page of the result list. Returning of this link is optional. You need to return this link only if total_required is specified as a query parameter. If you are using page_token , you may not return this link. |
next |
Refers to the next page of the result list. |
prev |
Refers to the previous page of the result list. |
This is a sample JSON schema that returns a collection of resources with pagination:
{
"id": "plan_list:v1",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Resource representing a list of billing plans with basic information.",
"name": "plan_list Resource",
"type": "object",
"required": true,
"properties": {
"plans": {
"type": "array",
"description": "Array of billing plans.",
"items": {
"type": "object",
"description": "Billing plan details.",
"$ref": "plan.json"
}
},
"total_items": {
"type": "string",
"readonly": true,
"description": "Total number of items."
},
"total_pages": {
"type": "string",
"readonly": true,
"description": "Total number of pages."
},
"links": {
"type": "array",
"items": {
"$ref": "http://json-schema.org/draft-04/hyper-schema#"
}
}
},
"links": [
{
"href": "https://api.foo.com/v1/payments/billing-plans?page_size={page_size}&page={page}&status={status}",
"rel": "self"
},
{
"rel": "first",
"href": "https://api.foo.com/v1/payments/billing-plans?page_size={page_size}&page={page}&start={start_id}&status={status}"
},
{
"rel": "next",
"href": "https://api.foo.com/v1/payments/billing-plans?page_size={page_size}&page={page+1}&status={status}"
},
{
"rel": "prev",
"href": "https://api.foo.com/v1/payments/billing-plans?page_size={page_size}&page={page-1}&status={status}"
},
{
"rel": "last",
"href": "https://api.foo.com/v1/payments/billing-plans?page_size={page_size}&page={last}&status={status}"
}
]
}
This is a sample JSON response that returns a collection of resources with pagination:
{
"total_items": "166",
"total_pages": "83",
"plans": [
{
"id": "P-6EM196669U062173D7QCVDRA",
"state": "ACTIVE",
"name": "Testing1-Regular3",
"description": "Create Plan for Regular",
"type": "FIXED",
"create_time": "2014-08-22T04:41:52.836Z",
"update_time": "2014-08-22T04:41:53.169Z",
"links": [
{
"href": "https://api.foo.com/v1/payments/billing-plans/P-6EM196669U062173D7QCVDRA",
"rel": "self"
}
]
},
{
"id": "P-83567698LH138572V7QCVZJY",
"state": "ACTIVE",
"name": "Testing1-Regular4",
"description": "Create Plan for Regular",
"type": "INFINITE",
"create_time": "2014-08-22T04:41:55.623Z",
"update_time": "2014-08-22T04:41:56.055Z",
"links": [
{
"href": "https://api.foo.com/v1/payments/billing-plans/P-83567698LH138572V7QCVZJY",
"rel": "self"
}
]
}
],
"links": [
{
"href": "https://api.foo.com/v1/payments/billing-plans?page_size=2&page=3&status=active",
"rel": "self"
},
{
"href": "https://api.foo.com/v1/payments/billing-plans?page_size=2&page=1&first=3&status=active",
"rel": "first"
},
{
"href": "https://api.foo.com/v1/payments/billing-plans?page_size=2&page=2&status=active",
"rel": "prev"
},
{
"href": "https://api.foo.com/v1/payments/billing-plans?page_size=2&page=4&status=active",
"rel": "next"
},
{
"href": "https://api.foo.com/v1/payments/billing-plans?page_size=2&page=82&status=active",
"rel": "last"
}
]
}
A single resource is typically derived from the parent collection of resources, often more detailed than an item in the represenation of a collection resource.
Executing GET
should never affect the system, and should not change response on subsequent requests, i.e. it should be idempotent
.
All identifiers for sensitive data should be non-sequential, and preferably non-numeric. In scenarios where this data might be used as a subordinate to other data, immutable string identifiers should be utilized for easier readability and debugging (i.e. NAME_OF_VALUE
vs 1421321).
GET /{version}/{namespace}/{resource}/{resource-id}
GET /v1/vault/customers/CUSTOMER-66W27667YB813414MKQ4AKDY
{
"merchant_customer_id": "merchant-1",
"merchant_id": "target",
"create_time": "2014-10-10T16:10:55Z",
"update_time": "2014-10-10T16:10:55Z",
"first_name": "Kartik",
"last_name": "Hattangadi"
}
If the provided resource identifier is not found, the response 404 Not Found
HTTP status should be returned (even with ’soft deleted’ records in data sources). Otherwise, 200 OK
HTTP status should be utilized when data is found.
In order to enable retries (e.g., poor connectivity), DELETE
is treated as idempotent
, so it should always respond with a 204 No Content
HTTP status. 404 Not Found
HTTP status should not be utilized here, as on a second retry a client might mistakenly think the resource never existed at all. GET
can be utilized to verify the resources exists prior to DELETE
.
For a number of reasons, some data exposed as a resource MAY disappear: because it has been specifically deleted, because it expired, because of a policy (e.g., only transactions less than 2 years old are available), etc. Services MAY return a 410 Gone
error to a request related to a resource that no longer exists. However, there may be significant costs associated with doing so. Service designers are advised to weigh in those costs and ways to reduce them (e.g., using resource identifiers that can be validated without access to a data store), and MAY return a 404 Not Found
instead if those costs are prohibitive.
DELETE /{version}/{namespace}/{resource}/{resource-id}
DELETE /v1/vault/customers/CUSTOMER-66W27667YB813414MKQ4AKDY
204 No Content
To perform an update to an entire resource, PUT
method MUST be utilized. The same response body supplied in the resource’s GET
should be provided in the resource’s PUT
request body.
If the update is successful, a 204 No Content
HTTP status code (with no response body) is appropriate. Where there is a justifying use case (typically to optimize some client interaction), a 200 OK
HTTP status code with a response body can be utilized.
While the entire resource’s representation must be supplied with the PUT
method, the APIs validation logic can enforce constraints regarding fields that are allowed to be updated. These fields can be specified as readOnly
in the JSON schema. Fields in the request body can be optional or ignored during deserialization, such as create_time
or other system-calculated values. Typical error handling, utilizing the 400 Bad Request
status code, should be applied in cases where the client attempts to update fields which are not allowed or if the resource is in a non-updateable state.
See Sample Input Validation Error Response for examples of error handling.
PUT /{version}/{namespace}/{resource}/{resource-id}
PUT /v1/vault/customers/CUSTOMER-66W27667YB813414MKQ4AKDY
{
"merchant_customer_id": "merchant-1",
"merchant_id": "target",
"create_time": "2014-10-10T16:10:55Z",
"update_time": "2014-10-10T16:10:55Z",
"first_name": "Kartik",
"last_name": "Hattangadi"
}
Any failed request validation responds 400 Bad Request
HTTP status. If clients attempt to modify read-only fields, this is also a 400 Bad Request
.
If there are business rules (more than simple data-type or length constraints), it is best to provide a specific
error code and message (in addition to the 400
) for that validation.
For situations which require interaction with APIs or processes outside of the current request, the 422
status code is appropriate.
After successful update, PUT
operations should respond with 204 No Content
status, with no response body.
Often, previously created resources need to be updated based on customer or facilitator-initiated interactions (like adding items to a cart). In such cases, APIs SHOULD provide an RFC 6902 JSON Patch compatible solution. JSON patches use the HTTP PATCH
method defined in RFC 5789 to enable partial updates to resources.
A JSON patch expresses a sequence of operations to apply to a target JSON document. The operations defined by the JSON patch specification include add
, remove
, replace
, move
, copy
, and test
. To support partial updates to resources, APIs SHOULD support add
, remove
and replace
operations. Support for the other operations (move, copy, and test) is left to the individual API owner based onneeds.
Below is a sample PATCH
request to do partial updates to a resource:
PATCH /v1/namespace/resources/:id HTTP/1.1
Host: api.foo.com
Content-Length: 326
Content-Type: application/json-patch+json
If-Match: "etag-value"
[
{
"op": "remove",
"path": "/a/b/c"
},
{
"op": "add",
"path": "/a/b/c",
"value": [ "foo", "bar" ]
},
{
"op": "replace",
"path": "/a/b/c",
"value": 42
}
]
The value of path
is a string containing a RFC 6901 JSON Pointer] that references a location within the target document where the operation is performed. For example, the value /a/b/c
refers to the element c
in the sample JSON below:
{
"a": {
"b": {
"c": "",
"d": ""
},
"e": ""
}
}
path
ParameterWhen JSON Pointer is used with arrays, concurrency protection is best implemented with ETags.
In many cases, ETags are not an option:
In cases where ETags are not available to provide concurrency protection when updating arrays, PayPal has created an extension to RFC 6901 which provides expressions of the following form.
"path": "/object-name/@filter-expression/attribute-name"
object-name
is the name of the collection.The symbol “@” refers to the current object. It also signals the beginning of a filter-expression.filter-expression
SHOULD only contain the following operators: a comparison operator (==
for equality) or a Logical AND (&&
) operator or both. For example:”/address/@id==123/street_name”, “address/@id==123 && primary==true”
are valid filter expressions.“addresss/@integer_id == 123”,”/address/@string_name == ‘james’”,”/address/@boolean_primary == true”,/address/@decimal_number == 12.1
are valid expressions.PATCH
operation is applied if the filter condition is met.Example1:
"op": "replace","path": “/address/@id==12345/primary”,"value": true
This would set the array element “primary” to true if the the element “id” has a value “12345”.
Example2:
"op": "replace","path": “/address/@country_code==’GB’ && type==’office’/active”,"value": true
This would set the array element “active” to true if the the element “country_code” equals to “GB” and type equals to “office”.
It is not necessary that an API support the updating of all attributes via a PATCH
operation. An API implementer SHOULD make an informed decision to support PATCH
updates only for a subset of attributes through a specific resource operation.
Note that the operations are applied sequentially in the order they appear in the payload. If the update is successful, a 204 No Content
HTTP status code (with no response body) is appropriate. Where there is a justifying use case (typically to optimize some client interaction) and the request has the header Prefer:return=representation
, a 200 OK
HTTP status code with a response body can be utilized.
Responses body with 200 OK
SHOULD return the entire resource representation unless the client uses the fields parameter to reduce the response size.
If a PATCH
request results in a new resource state that is invalid, the API SHOULD return a 400 Bad Request
or 422 Unprocessable Entity
.
See Sample Input Validation Error Response for examples of error handling.
PATCH
examples for modifying objects can be found in RFC 6902.
An API typically responds with full representation of a resource after processing requests for methods such as GET
. For efficiency, the client can ask the service to return partial representation using Prefer: return=minimal
HTTP header. Here, The determination of what constitutes an appropriate “minimal” response is solely at the discretion of the service.
To request partial representation with specific field(s), a client can use the fields
query parameter. For selecting multiple fields, a comma-separated list of fields SHOULD be used.
The following example shows the use of the fields parameter with users API.
Request: HTTP GET
without fields
parameter
GET https://api.foo.com/v1/users/bob
Authorization: Bearer your_auth_token
Response: The complete resource representation is returned in the response.
{
"uid": "dbrown",
"given_name": "David",
"sn": "Brown",
"location": "Austin",
"department": "RISK",
"title": "Manager",
"manager": "ipivin",
"email": "dbrown@foo.com",
"employeeId": "234167"
}
fields
query parameter.Request:
GET https://api.foo.com/v1/users/bob?fields=department,title,location
Authorization: Bearer your_auth_token
The response has only fields specified by the fields
query parameter as well as mandatory fields.
Response:
200 OK
{
"uid": "dbrown",
"department": "RISK",
"title": "Manager",
"location": "Austin"
}
You could use the same pattern for Collection Resource as well as following.
GET https://api.foo.com/v1/users?fields=department,title,location
The response will have entries with the fields specified in request as well as mandatory fields.
Sometimes, multiple identifiers are required (‘composite keys’, in the database lexicon) to identify a given resource. In these scenarios, all behaviors of a Collection Resource are implemented, as a subordinate of another resource. It is always implied that the resource-id
in the URL must be the parent of the sub-resources.
/{version}/{namespace}/{resource}/{resource-id}/{sub-resource-id}
is not appropriate, as the sub-resource-id
has ambiguous meaning.Note these templates/examples are brief: for more detail on the Collection Resource style, see above. Although this section explains the sub-resource collection, all interactions should be the same, simply with the addition of a parent identifier.
POST /{version}/{namespace}/{resource}/{resource-id}/{sub-resource}
GET /{version}/{namespace}/{resource}/{resource-id}/{sub-resource}
GET /{version}/{namespace}/{resource}/{resource-id}/{sub-resource}/{sub-resource-id}
PUT /{version}/{namespace}/{resource}/{resource-id}/{sub-resource}/{sub-resource-id}
DELETE /{version}/{namespace}/{resource}/{resource-id}/{sub-resource}/{sub-resource-id}
GET /v1/notifications/webhooks/{webhook-id}/event-types
POST /v1/factory/widgets/PART-4312/sub-assemblies
GET /v1/factory/widgets/PART-4312/sub-assemblies/INNER-COG
PUT /v1/factory/widgets/PART-4312/sub-assemblies/INNER-COG
DELETE /v1/factory/widgets/PART-4312/sub-assemblies/INNER-COG
When a sub-resource has a one-to-one relationship with the parent resource, it could be modeled as a singleton sub-resource. This approach is usually used as a means to reduce the size of a resource, when use cases support segmenting a large resource into smaller resources.
For a singleton sub-resource, the name should be a singular noun. As often as possible, that single resource should always be present (i.e. does not respond with 404
).
The sub-resource should be owned by the parent resource; otherwise this sub-resource should probably be promoted to its own collection resource, and relationships represented with sub-resource collections in the other direction. Sub-resource singletons should not duplicate a resource from another collection.
If the singleton sub-resource needs to be created, PUT
should be used, as the operation is idempotent, on creation or update. PATCH
can be used for partial updates, but should not be available on creation (in part because it is not idempotent).
This should not be used as a mechanism to update single or subsets of fields with PUT
. The resource should remain intact, and PATCH
should be utilized for partial update. Creating sub-resource singletons for each use case of updates is not a scalable design approach, as many endpoints could result long-term.
GET/PUT /{version}/{namespace}/{resource}/{resource-id}/{sub-resource}
GET /v1/customers/devices/DEV-FDU233FDSE213f)/vendor-information
Idempotency is an important aspect of building a fault-tolerant API. Idempotent APIs enable clients to safely retry an operation without worrying about the side-effects that the operation can cause. For example, a client can safely retry an idempotent request in the event of a request failing due to a network connection error.
Per HTTP Specification, a method is idempotent if the side-effects of more than one identical requests are the same as those for a single request. Methods GET
, HEAD
, PUT
and DELETE
(additionally, TRACE
and OPTIONS
) are defined idempotent.
POST
operations by definition are neither safe nor idempotent.
All service implementations MUST ensure that safe and idempotent behaviour of HTTP methods is implemented as per HTTP specifications. Services that require idempotency for POST
operations MUST be implemented as per the following guidelines.
POST
operations by definition are not idempotent which means that executing POST
more than once with the same input creates as many resources. To avoid creation of duplicate resources, an API SHOULD implement the protocol defined in the section below. This guarantees that only one record is created for the same input payload.
For many use cases that require idempotency for POST
requests, creation of a duplicate record is a severe problem. For example, duplicate records for the use cases that create or execute a payment on an account are not allowed by definition.
To track an idempotent request, a unique idempotency key
is used and sent in every request. Define a header and use its value as idempotency key
for every request.
For the very first request from the client:
On the client side:
The API client sends a new POST
request with the Foo-Request-Id
header that contains the idempotency key
.
POST /v1/payments/referenced-payouts-items HTTP/1.1
Host: api.foo.com
Content-Type: application/json
Authorization: Bearer oauth2_token
Foo-Request-Id: 123e4567-e89b-12d3-a456-426655440000
{
"reference_id": "4766687568468",
"reference_type": "egflf465vbk7468mvnb"
}
On the server side:
If the call is successful and leads to a resource creation, the service MUST return a 201
response to indicate both success and a change of state.
Sample response:
HTTP/1.1 201 CREATED
Content-Type: application/json
{
"item_id": "CDZEC5MJ8R5HY",
"links": [{
"href": "https://api.foo.com/v1/payments/referenced-payouts-items/CDZEC5MJ8R5HY",
"rel": "self",
"method": "GET"
}]
}
The service MAY send back the idempotency key
as part of Foo-Request-Id
header in the response.
For subsequent requests from the client with same input payload:
On the client side:
The API client sends a POST
request with the same idempotency key
and input body as before.
POST /v1/payments/referenced-payouts-items HTTP/1.1
Host: api.foo.com
Content-Type: application/json
Authorization: Bearer oauth2_token
Foo-Request-Id: 123e4567-e89b-12d3-a456-426655440000
{
"reference_id": "4766687568468",
"reference_type": "egflf465vbk7468mvnb"
}
On the server side:
The server, after checking that the call is identical to the first execution, MUST return a 200
response with a representation of the resource to indicate that the request has already been processed successfully.
Sample response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"item_id": "CDZEC5MJ8R5HY",
"processing_state": {
"status": "PROCESSING"
},
"reference_id": "4766687568468",
"reference_type": "egflf465vbk7468mvnb",
"payout_amount": {
"currency_code": "USD",
"value": "2.0"
},
"payout_destination": "9C8SEAESMWFKA",
"payout_transaction_id": "35257aef-54f7-43cf-a258-3b45caf3293",
"links": [{
"href": "https://api.foo.com/v1/payments/referenced-payouts-items/CDZEC5MJ8R5HY",
"rel": "self",
"method": "GET"
}]
}
The idempotency key
that is supplied as part of every POST
request MUST be unique and can not be reused with another request with a different input payload. See error scenarios described below to understand the server behavior for repeating idempotency
keys in requests.
How to make the key unique is up to the client and it’s agreed protocol with the server. It is recommended that UUID or a similar random identifier be used as the idempotency key. It is also recommended that the server implements the idempotency keys to be time-based and, thus, be able to purge or delete a key upon its expiry.
Foo-Request-Id
header is missing for an idempotent request, the service MUST reply with a 400
error with a link
pointing to the public documentation about this pattern.idempotency
key with a different request payload, the service MUST reply with a 422
error with a link
pointing to the public documentation about this pattern.Certain types of operations might require processing of the request in an asynchronous manner (e.g. validating a bank account, processing an image, etc.) in order to avoid long delays on the client side and prevent long-standing open client connections waiting for the operations to complete. For such use cases, APIs MUST employ the following pattern:
For POST
requests:
Return the 202 Accepted
HTTP response code.
In the response body, include one or more URIs as hypermedia links, which could include:
GET
request to that URI in order to obtain the completed resource. Until the resource is ready, the final URI SHOULD return the HTTP status code 404 Not Found
.{ "rel": "self", "href": "/v1/namespace/resources/{resource_id}", "method": "GET" }
GET
request to obtain the status of the operation which MAY include such information as completion state, ETA, and final URI once it is completed.{ "rel": "self", "href": "/v1/queue/requests/{request_id}, "method": "GET" }"
For PUT
/PATCH
/DELETE
/GET
requests:
Like POST
, you can support PUT/PATCH
/DELETE
/GET
to be asynchronous. The behaviour would be as follows:
Return the 202 Accepted
HTTP response code.
In the response body, include one or more URIs as hypermedia links, which could include:
GET
request to obtain the status of the operation which MAY include such information as completion state, ETA, and final URI once it is completed.{ "rel": "self", "href": "/v1/queue/requests/{request_id}, "method": "GET" }"
APIs that support both synchronous and asynchronous processing for an URI:
APIs that support both synchronous and asynchronous operations for a particular URI and an HTTP method combination, MUST recognize the Prefer
header and exhibit following behavior:
Prefer=respond-async
header, the service MUST switch the processing to asynchronous mode.Prefer=respond-async
header, the service MUST process the request synchronously.It is desirable that all APIs that implement asynchronous processing, also support webhooks as a mechanism of pushing the processing status to the client.
Controller (aka Procedural) resources challenge the fundamental notion or concept of resource orientation where resources usually represent mapping to a conceptual set of entities or things in a domain system. However, often API developers come across a situation where they are unable to model a service executing a business process or a part of a business process as a pure RESTful service. Some examples of use cases for controller resources are:
GET
/read operations)./action-resource-history
), or not at all.For further reading on controller
concepts, please refer to section 2.6 of the RESTful Web Services Cookbook.
Below are the set of guidelines for modelling controller resources.
Because a controller operation represents an action or a processing function in the server, it is more intuitive to express it using an English verb, i.e. the action itself as the resource name. Verbs such as ‘activate’, ‘cancel’, ‘validate’, ‘accept’, and ‘deny’ are usual suspects.
There are many styles that you can use to define a controller resource. You can use one of the following styles when defining a controller resource.
/v1/credit/assess-eligibility
). This is typically only applicable for creating a variety of resources in an optimized operation. It is usually an anti-pattern./
) of the parent resource (e.g. v1/identity/external-profiles/{id}/confirm
).v1/customer/search-deposits
.Note: A controller action is a terminal resource. A sub-resource for a controller resource is thus invalid. For the same reason, you SHOULD never define a sub-resource to a controller resource. It is also important to scope a controller to the minimum possible level of nesting in order to avoid resource pollution as such resources are use case or action centric.
In general, for most cases the HTTP POST
method SHOULD be used as the default method for executing a controller operation.
In scenarios where it is desired that the response of a controller operation be cache-able, GET
SHOULD be used to execute the controller. For example,you can use a GET
operation (GET /calculate-shortest-path?from=x &to=y
) to calculate the shortest path between two nodes (origin and destination). The result of the GET
operation is a collection of routes and their maps so you would like to cache the map for future use (GET /retrieve
).
In general, the following response codes can be used for a controller operation.
200
- This is the default status code for any controller operation. The response MUST contain a body that describes the result of a controller operation.
201
- If the controller operation leads to creation of a resource. If a composite controller is used to create one or more resources and it is not possible to expresss them as a composite record, you MAY instead use 200
as response code.
204
- If the server declines to return anything as part of a controller action (Most of the out-of-band actions fall in this category. e.g. v1/users/{id}/notify
).
For errors, appropriate 4XX
or 5XX
error codes MAY be returned.
Following sections provide some examples for modeling of controller resources to carry out various kinds of complex operations.
NOTE: Use with caution
For associated risks, see Controller Resource above
There are often situations in which a canonical resource needs to impart certain actions or state changes which are not appropriate in a PUT
or PATCH
. These URIs look like other Sub-Resources, but imply action.
A good use for this pattern is when a particular state change requires a “comment” (e.g. cancellation “reason”). Adding this comment, or other data such as location, would make the GET
/PUT
unnecessarily include those extra fields on every request/response. This action may change the status of the given resource implicitly.
Additionally, when a resource identifier is required for an action, it’s best to keep it in the URL. Some actions are business processes which are not innately a resource (and in some cases might not even change resource state).
The response is typically 200 OK
and the resource itself, if there are changes expected in the resource the consumer needs to capture. However, if no resource state change occurs, 204 No Content
and no response body could also be considered appropriate.
POST /{version}/{namespace}/{resource}/{resource-id}/{complex-operation}
POST /v1/payments/billing-agreements/I-0LN988D3JACS/suspend
{
"note": "Suspending the agreement."
}
204 No Content
However, when state changes are imparted in this manner, it does not mean that all state changes for the given resource should use a complex operation. Simple state transitions (i.e. changes to a status
field) should still utilize PUT
/PATCH
. It is completely appropriate to mix patterns using PUT
/PATCH
on a Collection Resource + Complex Operation, as to minimize the number of operations.
PUT
)PATCH /v1/payments/billing-agreements/I-0LN988D3JACS
[
{
"op": "replace",
"path": "/",
"value": {
"description": "New Description",
"shipping_address": {
"line1": "2065 Hamilton Ave",
"city": "San Jose",
"state": "CA",
"postal_code": "95125",
"country_code": "US"
}
}
}
]
Keep in mind that if there is any need to see the history of these actions, a Sub-resource Collection is appropriate to show all of the prior executions of this action. In that case, the verb should be reified, or changed to a plural noun (e.g. ‘execute’ would become ‘executions’).
This type of complex operation creates/updates/deletes multiple resources in one operation. This serves as both a performance and usability optimization, as well as adding better atomicity when values in the request might affect multiple resources at the same time.
Note in the sample below, the capture and the payment are both potentially affected by refund. A PUT
or PATCH
operation on the capture resource would have unintended side effects on the payment resource. To encapsulate both of these changes, the ‘refund’ action is used.
POST /{version}/{namespace}/{action}
POST /v1/payments/captures/{capture-id}/refund
{
"id": "0P209507D6694645N",
"create_time": "2013-05-06T22:11:51Z",
"update_time": "2013-05-06T22:11:51Z",
"state": "completed",
"amount": {
"total": "110.54",
"currency": "USD"
},
"capture_id": "8F148933LY9388354",
"parent_payment": "PAY-8PT597110X687430LKGECATA",
"links": [
{
"href": "https://api.foo.com/v1/payments/refund/0P209507D6694645N",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.foo.com/v1/payments/payment/PAY-8PT597110X687430LKGECATA",
"rel": "parent_payment",
"method": "GET"
},
{
"href": "https://api.foo.com/v1/payments/capture/8F148933LY9388354",
"rel": "capture",
"method": "GET"
}
]
}
This type of complex operation does not maintain state for the client, and creates no resources. This is about as RPC as it gets; other alternatives should be considered first.
This is not usually utilized in sub-resources, as a sub-resource action would typically affect the parent resource.
HTTP status 200 OK
is always appropriate. Response body
contains calculated values, which could potentially change if run again.
As with all actions, resource-oriented alternatives should be considered first.
POST /{version}/{namespace}/{action}
POST /v1/risk/evaluate-payment
{
"code": "h43j5k6iop"
}
200 OK
{
"status": "VALID"
}
When Collection Resources are used, it is best to use query parameters on the collection to filter the set. However, there are some situations that demand a very complex search syntax, where query parameter filtering on a collection might present usability problems, or run up against theoretical query parameter length limitations.
In these situations, POST
can be utilized with a request object to specify the search parameters.
Assuming pagination will be required with large response quantities, it is important to remember that the consumer will need to use POST
on each subsequent page. As such, it’s important to maintain paging in the query parameters (one of the rare exceptions where POST
body + query parameters are utilized).
Paging query parameters should follow the same conventions as in Collection Resources.
This allows for hypermedia links to provide next
, previous
, first
, last
page relationships with paging specified in the URL.
POST /{version}/{namespace}/{search-resource}
POST /v1/factory/widgets-search
{
"created_before":"1975-05-13",
"status": "ACTIVE",
"vendor": "Parts Inc."
}
200 OK
{
"items": [
<<lots of part objects here>>
]
"links": [
{
"href": "https://api.sandbox.factory.io/v1/factory/widgets-search?page=2&page_size=10",
"rel": "next",
"method": "POST"
},
{
"href": "https://api.sandbox.factory.io/v1/factory/widgets-search?page=124&page_size=10",
"rel": "last",
"method": "POST"
},
]
}
A better pattern is to create a Collection Resource of actions and provide a history of those actions taken in GET /{actions}
. This allows for future expansion of use cases around a resource model, instead of a single action-oriented, RPC-style URL.
Additionally, for various use cases, filtering the resource collection of historical actions is usually desirable. This also feeds well into event sourcing concepts, where the history of a given event can drive further functionality.
Certains types of API operations require uploading a file (e.g. jpeg, png, pdf) as part of the API call. Services for such use cases, MUST not support or allow encoding the file content within a JSON body using Base64
encoding.
For uploading a file, one of the following options SHOULD be used.
Services supporting such an operation SHOULD provide a separate dedicated URI for uploading and retrieving the files. Clients of such services upload the files using the file upload URI and retrieve the file metadata as part of the response to an upload operation.
Format of the file upload request SHOULD conform to multipart/form-data
content type (RFC 2388).
Example of a multipart/form-data
request:
The client first uploads the file using a file-upload URI provided by the service.
POST /v1/identity/limit-resolution-files
Content-Type: multipart/form-data; boundary=--foo_bar_baz
Authorization: Bearer YOUR_ACCESS_TOKEN_HERE
MIME-Version: 1.0
--foo_bar_baz
Content-Type: text/plain
Content-Disposition: form-data; name="title"
Identity Document
--foo_bar_baz
Content-Type: image/jpeg
Content-Disposition: form-data; filename="passport.jpg"; name="artifact"
...(binary bytes of the image)...
--foo_bar_baz--
Sample file upload response:
If the file upload is successful, the server responds with the metadata of the uploaded file.
{
"id": "file_egflf465vbk7468mvnb",
"created_at": 748557607545,
"size" : 3457689458369,
"url" : "https://api.foo.com/v1/files/file_egflf465vbk7468mvnb"
"type" : "image/jpeg"
}
The client can use the uploaded file’s URI (received in the above response) for any subsequent operation that requires the uploaded file as shown below.
Example Request
POST /v1/identity/limits-resolutions
Host: api.foo.com
Content-Type: application/json
Authorization: Bearer oauth2_token
{
...
"identity_document_reference" : "https://api.foo.com/v1/files/file_egflf465vbk7468mvnb"
}
This option SHOULD be used if you have to combine the uploading of a file with an API request body or parameters in one API request (e.g. for the purpose of optimization or to process both the file upload and request data in an atomic manner).
For such use cases, the request SHOULD either use content-type multipart/mixed
or multipart/related
(RFC 2387). Following is an example of such a request.
Example of a multipart/related
request:
The first part in the below multipart request is the request metadata, while the second part contains the binary file content
POST /v1/identity/limits-resolutions
Host: api.foo.com
Content-Type: multipart/related; boundary=--foo_bar_baz
Authorization: Bearer oauth2_token
--foo_bar_baz
Content-Type: application/json; charset=UTF-8
{
...
}
--foo_bar_baz
Content-Type: image/jpeg
[JPEG_DATA]
--foo_bar_baz--
This section describes various use cases where HATEOAS could be used.
As a guiding principle, every API SHOULD strive for a single entry point. Any response from this entry point will have HATEOAS links using which the client can navigate to all other methods on the same resource or releated resources and sub-resources. Following are different patterns for defining such an API entry point.
For most APIs, there’s a natural top level object or a collection which can be the resources addressed by the entry point. For example, the API defined in the previous section has a collection resource /users
which can be the entry point URI.
A complex multi step operation always has a logical entry point. For example, you want to build an API for a credit application process that involves multiple steps- a create application step, consumer consent step (to sign, agree to terms and conditions), an approval step- the last step of a successful credit application.
/apply-credit
is the API’s entry point. All other steps would be guided by the application create step in the from of links based on the captured data. For example a successful create application step would return the link to the next state of the application process apply.sign
.PATCH
link).Consider an API that provides a set of independent controller style utility methods. For example, you want to build an identity API that provides the following utility methods.
For such cases, the API MAY provide a separate resource /actions
to return links to all resources that can be served by this API.
GET /actions
in the above example would return links to other api methods (/generate-otp
,/encrypt
,/decrypt
,/link-tokens
).
For collection resources, a service MAY automatically provide paginated collection. Client can also specify its pagination preferences, if the query resultset is quite large. In such cases, the resultset is returned as a paginated collection with appropriate pagination related links. Client utilizes these links to navigate through the resultset back-and-forth. For more details on this linking pattern, please refer to Pagination and HATEOAS links.
There are often use cases where an API wants to provide additional context in case of error along with other error details (HTTP status code and error messages. See Error Standards for more). An API could return additional resource links to provide more hints on the error in order to resolve it.
Consider an example from the /users
API where the user wants to update his address details.
Request:
PATCH /v1/users/ALT-JFWXHGUV7VI
{
"address": {
...
}
}
The service, however, finds that the user account is currently not active. So it responds with an error that update of this account is not possible given the current state. It also returns an HATEOAS link in the response to activate the user account.
Response:
HTTP/1.1 422 Unprocessable Entity
{
"name":"INVALID_OPERATION",
"debug_id":"123456789",
"message":"update to an inactive account is not supported",
"links": [
{
"href": "https://api.foo.com/v1/customer/partner-referrals/ALT-JFWXHGUV7VI/activate",
"rel": "activate",
"method": "POST"
}
]
}
The client can now prompt the user to first activate his account and then change his address details.
In a complex business operation that has one or more sub business operations and business rules govern the state transitions at run-time, using HATEOAS links to describe or emit the allowed state transitions prevents clients from embedding the service-specific business logic into their code. Loose coupling or no coupling with server’s business logic enables better evolvability for both client and server.
For example, an order can be cancelled when it is in a PENDING state. The order cannot be cancelled once it moves to a COMPLETED state. Following example shows how a service can use HATEOAS links to guide clients about next possible step(s) in business process.
Order is in PENDING state so the services returns the cancel
HATEOAS link.
GET v1/checkout/orders/52181732T9513405D HTTP/1.1
Host: api.foo.com
Content-Type: application/json
Authorization: Bearer oauth2_token
HTTP/1.1 200 OK
Content-Type: application/json
{
"payment_details":{
...
},
"status":"PENDING",
"links":[
{
"rel": "self",
"href": "https://api.foo.com/v1/checkout/orders/19S86694A9334040A",
"method": "GET"
},
{
"rel": "cancel",
"href": "https://api.foo.com/v1/checkout/orders/19S86694A9334040A/cancel",
"method": "POST"
}
]
}
Order is in COMPLETED state so the services does not return the cancel
link anymore.
GET v1/checkout/orders/52181732T9513405D HTTP/1.1
Host: api.foo.com
Content-Type: application/json
Authorization: Bearer oauth2_token
HTTP/1.1 200 OK
Content-Type: application/json
{
"payment_details":{
...
},
"status":"COMPLETED",
"links":[
{
"rel": "self",
"href": "https://api.foo.com/v1/checkout/orders/19S86694A9334040A",
"method": "GET"
}
]
}
Note: The service MAY decide to support cancellation of orders (for orders with COMPLETED status) in some countries in future but that does not require the client to change anything in its code. All that a client knows or has coded when it first integrated with the service is the request body that is required to cancel
an order.
When an operation is carried out asynchronously, it is important to provide relevant links to client so that the client can find out more details about the operation such as finding out status or perform get, update and delete operations. Please refer to Asynchronous Operations to find how the HATEOAS links could be used in response of an asynchronous operation.
Some services always return very large response because of the nature of the domain they address. APIs of such services are sometimes referred as Composite
APIs (they accumulate data from various sources or an aggregate of more than one services). For such APIs, sending the entire response drastically impacts performance of the API consumer, API server and the underlying network. In such cases, the client can ask the service to return partial representation using Prefer: return=minimal
HTTP header. A service could send response with relevant HATEOAS links with minimal data to improve the performance.
This section describes guidelines for handling bulk calls in APIs. There are two different methods that you could use for bulk processing.
Homogeneous: operation involves request and response payload representing collection of resources of the same type. Same operation is applied on all items in the collection.
Heterogeneous: operation involves a request and response payloads that contain one or more requests and reponse payloads respectively. Each nested request and response represents an operation on a specific type of resource. However, the container request and response have one or more operations operating on one or more types of resources. It is recommended to use a public domain standard such as [OData Batch Specification] 1 in such cases.
This section only addresses bulk processing of payloads using the homogenous method.
Each bulk request is a single HTTP request to one target API endpoint. This example illustrates a bulk add operation.
Example Request:
POST /v1/devices/cards HTTP/1.1
Host: api.foo.com
Content-Length: total_content_length
{
...
"items": [
{
"account_number": "2097094104180012037",
"address_id": "466354",
"phone_id": "0",
"first_name": "M",
"last_name": "Shriver",
"primary_card_holder": false
},
{
"account_number": "2097094104180012047",
"address_id": "466354",
"phone_id": "0",
"first_name": "M",
"last_name": "Shriver",
"primary_card_holder": false
},
{
"account_number": "2097094104180012023",
"address_id": "466354",
"phone_id": "0",
"first_name": "M",
"last_name": "Shriver",
"primary_card_holder": false
}
]
}
The response usually contains the status of each item. Failure of an individual item is described using Error Handling Guidelines for an individual item. Given below is such an example.
Example Response:
HTTP/1.1 200 OK
{
...
"batch_result":[
{
… <Success_body>
},
{
"name": "VALIDATION_ERROR",
"details": [
{
"field": "#/credit_card/expire_month",
"issue": "Required field is missing",
"location": "body"
}
],
"debug_id": "123456789",
"message": "Invalid data provided",
"information_link": "http://developer.foo.com/apidoc/blah#VALIDATION_ERROR"
},
{
"name": "VALIDATION_ERROR",
"details": [
{
"field":"#/credit_card/currency",
"value":"XYZ",
"issue":"Currency code is invalid",
"location":"body"
}
],
"debug_id": "123456789",
"message": "Invalid data provided",
"information_link": "http://developer.foo.com/apidoc/blah#VALIDATION_ERROR"
}
]
}
If the API supports atomic semantics to processing requests, there would be a single response code for the entire request with one or more errors as applicable.
Example Response:
Note:
HTTP/1.1 400 Bad Request
{
"name": "VALIDATION_ERROR",
"details": [
{
"field": "#/credit_card/currency",
"value": "XYZ",
"issue": "Currency code is invalid",
"location": "body"
}
],
"debug_id": "123456789",
"message": "Invalid data provided",
"information_link": "http://developer.foo.com/apidoc/blah#VALIDATION_ERROR"
}
]
Similar to bulk add, a service can support bulk update operation (replace using HTTP PUT
or partial update using PATCH
). This is possible provided the bulk add request also creates a first-class resource (e.g. a batch resource) that is uniquely identifiable using an id and returned to the client. The subsequent update operations could then use this id and perform updates on constituent elements of the batch as if an update is performed on a single resource.
For bulk replace and update operations, every effort should be made to make the execution atomic (all or nothing semantics). When it is not possible to make it so, the response should be similar to the partial response of bulk add operation described in the previous section.
Tne following guidelines describe HTTP status code and error handling for bulk operations.
200 OK
as the overall bulk processing status with individual status of each bulk item. In case of an error while processing a bulk item, the error description MUST follow the Error Handling Guidelines.202 Accepted
with a status URI for the client to monitor the request. The client may choose to ignore the status URI if it has registered itself with the API server for notification via webhooks.For a failed item, you MAY use the JSON Pointer Expressions in the error response for that item using the field
attribute of error.json
. The caller can then map a response item’s processing state to the exact request item in the original bulk request. Given below is an error response sample using the JSON Pointer Expressions.
Error Response Sample:
HTTP/1.1 200 OK
[
{
… <Success_body>
},
{
"name": "VALIDATION_ERROR",
"details": [
{
"field": "/items/@account_number=='2097094104180012047'/address_id",
"issue": "Invalid Address Id for the account",
"location": "body"
}
],
"debug_id": "123456789",
"message": "Invalid data provided",
"information_link": "http://developer.foo.com/apidoc/blah#VALIDATION_ERROR"
},
{
"name": "VALIDATION_ERROR",
"details": [
{
"field": "/items/@account_number=='2097094104180012023'/phone_id",
"value": "XYZ",
"issue": "Phone Id is invalid",
"location": "body"
}
],
"debug_id": "123456789",
"message": "Invalid data provided",
"information_link": "http://developer.foo.com/apidoc/blah#VALIDATION_ERROR"
}
]
The alternative is to create a response that contains the processing status of each item in the same order as it was received in the original request. The failed item would be represented using error.json
with appropriate value in the field
attribute.
Error Response Sample:
HTTP/1.1 200 OK
[
{
… <Success_body>
},
{
"name": "VALIDATION_ERROR",
"details": [
{
"field": "/items/0/address_id",
"issue": "Invalid Address Id for the account",
"location": "body"
}
],
"debug_id": "123456789",
"message": "Invalid data provided",
"information_link": "http://developer.foo.com/apidoc/blah#VALIDATION_ERROR"
},
{
"name": "VALIDATION_ERROR",
"details": [
{
"field": "/items/2/phone_id",
"value": "XYZ",
"issue": "Phone Id is invalid",
"location": "body"
}
],
"debug_id": "123456789",
"message": "Invalid data provided",
"information_link": "http://developer.foo.com/apidoc/blah#VALIDATION_ERROR"
}
]
Designers of new services SHOULD refer to the RESTful Web Services Cookbook at Safari Books Online for other useful patterns.