The PayPal platform is a collection of reusable services that encapsulate well-defined business capabilities. Developers are encouraged to access these capabilities through Application Programming Interfaces (APIs) that enable consistent design patterns and principles. This facilitates a great developer experience and the ability to quickly compose complex business processes by combining multiple, complementary capabilities as building blocks.
PayPal APIs follow the RESTful architectural style as much as possible. To support our objectives, we have developed a set of rules, standards, and conventions that apply to the design of RESTful APIs. These have been used to help design and maintain hundreds of APIs and have evolved over several years to meet the needs of a wide variety of use cases.
We are sharing these guidelines to help propagate good API design practices in general. We have pulled extensively from the broader community and believe that it is important to give back. The documentation is as generic as possible to make it easier to incorporate into the guidelines you use in your projects. If you have any updates, suggestions, or additions that you would like to contribute, please feel free to submit a PR or create an issue.
The keywords "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 specified according to URI Template RFC 6570. For example, a URL containing a variable called account_id would be shown as https://foo.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), Erik Hogan (PayPal API Platform), Jayadeba Jena (PayPal API Platform), Nikhil Kolekar (PayPal API Platform), Gagan Maheshwari (former member: PayPal API Platform), Michael McKenna (PayPal Globalization), George Petkov (former member: PayPal API Platform) and Andrew Todd (PayPal Credit).
- Interpreting the Guidelines
- Service Design Principles
- HTTP Methods, Headers and Statuses
- Hypermedia
- Naming Conventions
- URI
- JSON Schema
- JSON Types
- Error Handling
- API Versioning
- Deprecation
- Patterns And Use Cases
- References
The key abstraction of information in REST is a resource. According to Fielding's dissertation section 5.2, any information that can be named can be a resource: a document or image, a temporal service (e.g. "today's weather in Los Angeles"), a collection of other resources, a non-virtual object (e.g. a person), and so on. A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time. More precisely, a resource R is a temporally varying membership function MR(t)
, that for time t
maps to a set of entities, or values, that are equivalent. The values in the set may be resource representations and/or resource identifiers.
A resource can also map to the empty set, that allows references to be made to a concept before any realization of that concept exists.
REST uses a resource identifier to identify the particular resource instance involved in an interaction between components. The naming authority (an organization providing APIs, for example) that assigned the resource identifier making it possible to reference the resource, is responsible for maintaining the semantic validity of the mapping over time (ensuring that the membership function does not change). - Fielding's dissertation section 5.2
REST components perform actions on a resource by using a representation to capture the current or intended state of that resource and by transferring that representation between components. A representation is a sequence of bytes, plus representation metadata to describe those bytes - Fielding dissertation section 5.2.
According to Wikipedia, a domain model is a system of abstractions that describes selected aspects of a sphere of knowledge, influence, or activity. The concepts include the data involved in a business, and the rules that the business uses in relation to that data. As an example, the PayPal domain model includes domains such as Payment, Risk, Compliance, Identity, Customer Support, etc.
Capability represents a business-oriented and customer-facing view of an organization's business logic. Capabilities could be used to organize portfolio of APIs as a stable, business-driven view of its system, consumable by customers and experiences. Examples of capability are: Compliance, Credit, Identity, Retail and Risk, among other things.
Capabilities drive the interface, while domains are more coarse-grained and closer to the code and the organization's structure. Capability and domain are seen as orthogonal concerns from a service perspective.
Capabilities drive service modeling and namespace concerns in an API portfolio. Namespaces are part of the Business Capability Model. Examples of namespace are: compliance, devices, transfers, credit, limits, etc.
Namespaces should reflect the domain that logically groups a set of business capabilities. Domain definition should reflect the customer's perspective on how platform capabilities are organized. Note that these may not necessarily reflect the company's hierarchy, organization, or (existing) code structure. In some cases, domain definitions are aspirational, in the sense that these reflect the target, customer-oriented platform organization model. Underlying service implementations and organization structures may need to migrate to reflect these boundaries over time.
Services provide a generic API for accessing and manipulating the value set of a resource, regardless of how the membership function is defined or the type of software that is handling the request. Services are generic pieces of software that can perform any number of functions. It is, therefore, instructive to think about the different types of services that exist.
Logically, we can segment the services and the APIs that they expose into two categories:
- Capability APIs are public APIs exposed by services implementing generic, reusable business capabilities.
- Experience-specific APIs are built on top of capability APIs and expose functionality which may be either specific to a channel, or optimized for a context-specific specialization of a generic capability. Contextual information could be related to time, location, device, channel, identity, user, role, privilege level among other things.
Capability APIs are public interfaces to reusable business capabilities. Public implies that these APIs are limited only to the interfaces meant for consumption by front-end experiences, external consumers, or internal consumers from a different domain.
Experience-specific services provide minimal additional business logic over core capabilities, and mainly provide transformation and lightweight orchestration to tailor an interaction to the needs of a specific experience, channel or device. Their input/output functionality is limited to service calls.
An entity that invokes an API request and consumes the API response.
This section captures the principles guiding the design of the services that expose APIs to internal and external developers, adjacencies, partners and affiliates. A service refers to functionality pertaining to a particular capability, exposed as an API.
Following are the core design principles for a service.
Services and consumers must be loosely coupled from each other.
Coupling refers to a connection or relationship between two things. A measure of coupling is comparable to a level of dependency. This principle advocates the design of service contracts, with a constant emphasis on reducing (loosening) dependencies between the service contract, its implementation, and service consumers.
The principle of Loose Coupling promotes the independent design and evolution of a service’s logic and implementation while still emphasizing baseline interoperability with consumers that have come to rely on the service’s capabilities.
This principle implies the following:
- A service contract should not expose implementation details
- A service contract can evolve without impacting existing consumers
- A service in a particular domain can evolve independently of other domains
A domain service can access data and functionality it does not own through other service contracts only.
A service exposes functionality that comprises the functionality and data it owns and implements, as well as the functionality and data it depends upon which it does not own. This principle advocates that any functionality or data that a service depends on and which it does not own must be accessed through service contracts only.
This principle implies the following:
- A service has a clear isolation boundary - a clear scope of ownership in terms of functionality and data
- A service cannot expose the data it does not own directly
Service contracts must be stable.
Services must be designed in such a way that the contract they expose remains valid for existing customers. Should the service contract need to evolve in an incompatible fashion for the consumer, this should be communicated clearly.
This principle implies the following:
- Existing clients of a service must be supported for a documented period of time
- Additional functionality must be introduced in a way that does not impact existing consumers
- Deprecation and migration policies must be clearly stated to set consumers' expectations
Services must be developed to be reusable across multiple contexts and by multiple consumers.
The main goal of an API platform is to enable applications to be developed quickly and cost effectively by using and combining services. This is possible only if the service contracts have been developed with flexibility for multiple use-cases and multiple consumers. This principle advocates that services be developed in a manner that enables them to be used by multiple consumers and in multiple contexts, some of which may evolve over time.
This principle implies the following:
- A service contract should be designed for not just the immediate context, but with support and/or extensibility to be used by multiple consumers in different contexts
- A service contract may need to incrementally evolve to support multiple contexts and consumers over time
Functionality and data must only be exposed through standardized service contracts.
A service exposes its purpose and capabilities via a service contract. A service contract comprises of functional aspects, non-functional aspects (such as availability, response-time), and business aspects (such as cost-per-call, terms and conditions). Standardized means that the service contracts must be compliant with the contract design standards.
This principle advocates that all functionality and data must only be exposed through standardized service contracts. Consumers of services can, therefore, understand and access functionality and data only through service contracts.
This principle implies the following:
- Functionality and data cannot be understood or accessed outside of service contracts
- Each piece of data (such as that managed in a datastore) is owned by only one service
Services must follow a common set of rules, interaction styles, vocabulary and shared types.
A set of rules prescribes the definition of services in order to expose those in a consistent manner. This principle increases the ease of use of the API platform by reducing the learning curve for consumers of new services.
This principle implies the following:
- A set of standards is defined for services to comply with
- A service should use vocabulary from common and shared dictionaries
- Compatible interaction styles, service granularity and shared types are key for full interoperability and ease of service compositions
Services must be easy to use and compose in consumers (and applications).
A service that is difficult and time consuming to use reduces the benefits of a microservices architecture by encouraging consumers to find alternate mechanisms to access the same functionality. Composability means that services can be combined easily because the service contracts and access protocols are consistent, and each service contract does not have to be understood differently.
This principle implies the following:
- A service contract is easily discoverable and understandable
- Service contracts and protocols are consistent on all aspects that they can be - e.g. identification and authentication mechanisms, error semantics, common type usage, pagination, etc.
- A service has clear ownership, so that consumer providers can reach service owners regarding SLAs, requirements, issues
- A consumer provider can easily integrate, test, and deploy a consumer that uses this service
- A consumer provider can easily monitor the non-functional aspects of a service
Service must be designed so that the functionality it provides is easily externalizable.
A service is developed for use by consumers that may be from another domain or team, another business unit or another company. In all of these cases, the functionality exposed is the same; what changes is the access mechanism or the policies enforced by a service, like authentication, authorization and rate-limiting. Since the functionality exposed is the same, the service should be designed once and then externalized based on business needs through appropriate policies.
This principle implies the following:
- The service interface must be derived from the domain model and the intended use-cases it is meant to support
- The service contract and access (binding) protocols supported must meet the consumer's needs
- The externalization of a service must not require reimplementation, or a change in service contract
Various business capabilities of an organization are exposed through APIs as a set of resources. Functionality MUST not be duplicated across APIs; rather resources (e.g., user account, credit card, etc.) are expected to be re-used as needed across use-cases.
Most services will fall easily into the standard data resource model where primary operations can be represented by the acronym CRUD (Create, Read, Update, and Delete). These map very well to standard HTTP verbs.
HTTP Method | Description |
---|---|
GET |
To retrieve a resource. |
POST |
To create a resource, or to execute a complex operation on a resource. |
PUT |
To update a resource. |
DELETE |
To delete a resource. |
PATCH |
To perform a partial update to a resource. |
The actual operation invoked MUST match the HTTP method semantics as defined in the table above.
- The
GET
method MUST NOT have side effects. It MUST NOT change the state of an underlying resource. POST
: method SHOULD be used to create a new resource in a collection.- Example: To add a credit card on file,
POST https://api.foo.com/v1/vault/credit-cards
- Idempotency semantics: If this is a subsequent execution of the same invocation (including the
Foo-Request-Id
header) and the resource was already created, then the request SHOULD be idempotent.
- Example: To add a credit card on file,
- The
POST
method SHOULD be used to create a new sub-resource and establish its relationship with the main resource.- Example: To refund a payment with transaction ID 12345:
POST https://api.foo.com/v1/payments/payments/12345/refund
- Example: To refund a payment with transaction ID 12345:
- The
POST
method MAY be used in complex operations, along with the name of the operation. This is also known as the controller pattern and is considered an exception to the RESTful model. It is more applicable in cases when resources represent a business process, and operations are the steps or actions to be performed as part of it. For more information, please refer to section 2.6 of the RESTful Web Services Cookbook. - The
PUT
method SHOULD be used to update resource attributes or to establish a relationship from a resource to an existing sub-resource; it updates the main resource with a reference to the sub-resource.
It is assumed throughout these guidelines that request bodies and response bodies MUST be sent using JavaScript Object Notation (JSON). JSON is a light-weight data representation for an object composed of unordered key-value pairs. JSON can represent four primitive types (strings, numbers, booleans, and null) and two structured types (objects and arrays). When processing an API method call, the following guidelines SHOULD be followed.
The data model for representation MUST conform to the JSON Data Interchange Format as described in RFC 7159.
- Resource endpoints MUST support
application/json
as content type. - If an
Accept
header is sent andapplication/json
is not an acceptable response, a406 Not Acceptable
error MUST be returned.
APIs MUST be strict in the information they produce, and they SHOULD be strict in what they consume as well.
Since we are dealing with programming interfaces, we need to avoid guessing the meaning of what is being sent to us as much as possible. Given that integration is typically a one-time task for a developer and we provide good documentation, we need to be strict with using the data that is being received. Postel's law must be weighed against the many dangers of permissive parsing.
The purpose of HTTP headers is to provide metadata information about the body or the sender of the message in a uniform, standardized, and isolated way. HTTP header names are NOT case sensitive.
- HTTP headers SHOULD only be used for the purpose of handling cross-cutting concerns.
- API implementations SHOULD NOT introduce or depend on headers.
- Headers MUST NOT include API or domain specific values.
- If available, HTTP standard headers MUST be used instead of creating a custom header.
Service Consumers and Service Providers:
- SHOULD NOT expect that a particular HTTP header is available. It is possible that an intermediary component in call chain can drop an HTTP header. This is the reason business logic SHOULD NOT be based on HTTP headers.
- SHOULD NOT assume the value of a header has not been changed as part of HTTP message transmission.
Infrastructure Component (Web-services framework, Client invocation library, Enterprise Service Bus (ESB), Load Balancers (LB), etc. involved in HTTP message delivery):
- MAY return an error based on availability and validity of a particular header without transmitting the message forward. For example, an authentication or authorization error for a request based on client identity and credentials.
- MAY add, remove, or change a value of an HTTP header.
These are headers defined or referenced from HTTP/1.1 specification (RFC 7231). Their purpose, syntax, values, and semantics are well defined and understood by many infrastructure components.
HTTP Header Name | Description |
---|---|
Accept |
This request header specifies the media types that the API client is capable of handling in the response. Systems issuing the HTTP request SHOULD send this header. Systems handling the request SHOULD NOT assume it is available. It is assumed throughout these API guidelines that APIs support application/json . |
Accept-Charset |
This request header specifies what character sets the API client is capable of handling in the response.
|
Content-Language |
This request/response header is used to specify the language of the content. The default locale is en-US . API clients SHOULD identify the language of the data using Content-Language header. APIs MUST provide this header in the response. Example: Content-Language: en-US |
Content-Type |
This request/response header indicates the media type of the request or response body.
(in HTTP request) Accept: application/json |
Link |
According to Web Linking RFC 5988, a link is a typed connection between two resources that are identified by Internationalised Resource Identifiers (IRIs). The Link entity-header field provides a means for serializing one or more links in HTTP headers. APIs SHOULD be built with a design assumption that neither an API, nor an API client's business logic should depend on information provided in the headers. Headers must only be used to carry cross-cutting concern information such as security, traceability, monitoring, etc. Therefore, usage of the Link header is prohibited with response codes 201 or 3xx . Consider using HATEOAS links in the response body instead. |
Location |
This response-header field is used to redirect the recipient to a location other than the Request-URI for completion of the request or identification of a new resource. APIs SHOULD be built with a design assumption that neither an API, nor an API client's business logic should depend on information provided in the headers. Headers must only be used to carry cross-cutting concern information such as security, traceability, monitoring, etc. Therefore, usage of the Location header is prohibited with response codes 201 or 3xx . Consider using HATEOAS links in response body instead. |
Prefer |
The Prefer request header field is used to indicate that a particular server behavior(s) is preferred by the client but is not required for successful completion of the request. It is an end to end field and MUST be forwarded by a proxy if the request is forwarded unless Prefer is explicitly identified as being hop by hop using the Connection header field. Following token values are possible to use for APIs provided an API documentation explicitly indicates support for Prefer .respond-async : API client prefers that API server processes its request asynchronously. Prefer: respond-asyncServer returns a 202 (Accepted) response and processes the request asynchronously. API server could use a webhook to inform the client subsequently, or the client may call GET to get the response at a later time. Refer to Asynchronous Operations for more details.read-consistent : API client prefers that API server returns response from a durable store with consistent data. For APIs that are not offering any optimization preferences for their clients, this behavior would be the default and it would not require the client to set this token. Prefer: read-consistent read-eventual-consistent : API client prefers that API server returns response from either cache or presumably eventually consistent datastore if applicable. If there is a miss in finding the data from either of these two types of sources, the API server might return response from a consistent, durable datastore.Prefer: read-eventual-consistent read-cache : API client prefers that API server returns response from cache if available. If the cache hit is a miss, the server could return response from other sources such as eventual consistent datastore or a consistent, durable datastore.Prefer: read-cache return=representation : API client prefers that API server include an entity representing the current state of the resource in the response to a successful request. This preference is intended to provide a means of optimizing communication between the client and server by eliminating the need for a subsequent GET request to retrieve the current representation of the resource following a creation (POST ) modification operation (PUT or PATCH ).Prefer: return=representation return=minimal : API client indicates that the server returns only a minimal response to a successful request. The determination of what constitutes an appropriate "minimal" response is solely at the discretion of the server.Prefer: return=minimal |
Following are some custom headers used in these guidelines. These are not part of the HTTP specifications.
HTTP Header Name | Description |
---|---|
Foo-Request-Id |
API consumers MAY choose to send this header with a unique ID identifying the request header for tracking purpose. Such a header can be used internally for logging and tracking purpose too. It is RECOMMENDED to send this header back as a response header if response is synchronous or as request header of a webhook as applicable. |
When services receive request headers, they MUST pass on relevant custom headers in addition to the HTTP standard headers in requests/messages dispatched to downstream applications.
RESTful services use HTTP status codes to specify the outcomes of HTTP method execution. HTTP protocol specifies the outcome of a request execution using an integer and a message. The number is known as the status code and the message as the reason phrase. The reason phrase is a human readable message used to clarify the outcome of the response. HTTP protocol categorizes status codes in ranges.
When responding to API requests, the following status code ranges MUST be used.
Range | Meaning |
---|---|
2xx |
Successful execution. It is possible for a method execution to succeed in several ways. This status code specifies which way it succeeded. |
4xx |
Usually these are problems with the request, the data in the request, invalid authentication or authorization, etc. In most cases the client can modify their request and resubmit. |
5xx |
Server error: The server was not able to execute the method due to site outage or software defect. 5xx range status codes SHOULD NOT be utilized for validation or logical error handling. |
Success and failure apply to the whole operation not just to the SOA framework portion or to the business logic portion of code exectuion.
Following are the guidelines for status codes and reason phrases.
- Success MUST be reported with a status code in the
2xx
range. - HTTP status codes in the
2xx
range MUST be returned only if the complete code execution path is successful. This includes any container/SOA framework code as well as the business logic code execution of the method. - Failures MUST be reported in the
4xx
or5xx
range. This is true for both system errors and application errors. - There MUST be a consistent, JSON-formatted error response in the body as defined by the
error.json
schema. This schema is used to qualify the kind of error. Please refer to Error Handling guidelines for more details. - A server returning a status code in the
4xx
or5xx
range MUST return theerror.json
response body. - A server returning a status code in the
2xx
range MUST NOT return response followingerror.json
, or any kind of error code, as part of the response body. - For client errors in the
4xx
code range, the reason phrase SHOULD provide enough information for the client to be able to determine what caused the error and how to fix it. - For server errors in the
5xx
code range, the reason phrase and an error response followingerror.json
SHOULD limit the amount of information to avoid exposing internal service implementation details to clients. This is true for both external facing and internal APIs. Service developers should use logging and tracking utilities to provide additional information.
All REST APIs MUST use only the following status codes. Status codes in BOLD
SHOULD be used by API developers. The rest are primarily intended for web-services framework developers reporting framework-level errors related to security, content negotiation, etc.
- APIs MUST NOT return a status code that is not defined in this table.
- APIs MAY return only some of status codes defined in this table.
Status Code | Description |
---|---|
200 OK |
Generic successful execution. |
201 Created |
Used as a response to POST method execution to indicate successful creation of a resource. If the resource was already created (by a previous execution of the same method, for example), then the server should return status code 200 OK . |
202 Accepted |
Used for asynchronous method execution to specify the server has accepted the request and will execute it at a later time. For more details, please refer Asynchronous Operations. |
204 No Content |
The server has successfully executed the method, but there is no entity body to return. |
400 Bad Request |
The request could not be understood by the server. Use this status code to specify:
|
401 Unauthorized |
The request requires authentication and none was provided. Note the difference between this and 403 Forbidden . |
403 Forbidden |
The client is not authorized to access the resource, although it may have valid credentials. API could use this code in case business level authorization fails. For example, accound holder does not have enough funds. |
404 Not Found |
The server has not found anything matching the request URI. This either means that the URI is incorrect or the resource is not available. For example, it may be that no data exists in the database at that key. |
405 Method Not Allowed |
The server has not implemented the requested HTTP method. This is typically default behavior for API frameworks. |
406 Not Acceptable |
The server MUST return this status code when it cannot return the payload of the response using the media type requested by the client. For example, if the client sends an Accept: application/xml header, and the API can only generate application/json , the server MUST return 406 . |
415 Unsupported Media Type |
The server MUST return this status code when the media type of the request's payload cannot be processed. For example, if the client sends a Content-Type: application/xml header, but the API can only accept application/json , the server MUST return 415 . |
422 Unprocessable Entity |
The requested action cannot be performed and may require interaction with APIs or processes outside of the current request. This is distinct from a 500 response in that there are no systemic problems limiting the API from performing the request. |
429 Too Many Requests |
The server must return this status code if the rate limit for the user, the application, or the token has exceeded a predefined value. Defined in Additional HTTP Status Codes RFC 6585. |
500 Internal Server Error |
This is either a system or application error, and generally indicates that although the client appeared to provide a correct request, something unexpected has gone wrong on the server. A 500 response indicates a server-side software defect or site outage. 500 SHOULD NOT be utilized for client validation or logic error handling. |
503 Service Unavailable |
The server is unable to handle the request for a service due to temporary maintenance. |
For each HTTP method, API developers SHOULD use only status codes marked as "X" in this table. If an API needs to return any of the status codes marked with an X
, then the use case SHOULD be reviewed as part of API design review process and maturity level assessment. Most of these status codes are used to support very rare use cases.
Status Code | 200 Success | 201 Created | 202 Accepted | 204 No Content | 400 Bad Request | 404 Not Found | 422 Unprocessable Entity | 500 Internal Server Error |
---|---|---|---|---|---|---|---|---|
GET |
X | X | X | X |
X | |||
POST |
X | X | X |
X | X |
X |
X | |
PUT |
X | X |
X | X | X | X |
X | |
PATCH |
X | X | X | X | X |
X | ||
DELETE |
X | X | X | X | X |
X |
-
GET
: The purpose of theGET
method is to retrieve a resource. On success, a status code200
and a response with the content of the resource is expected. In cases where resource collections are empty (0 items in/v1/namespace/resources
),200
is the appropriate status (resource will contain an emptyitems
array). If a resource item is 'soft deleted' in the underlying data,200
is not appropriate (404
is correct) unless the 'DELETED' status is intended to be exposed. -
POST
: The primary purpose ofPOST
is to create a resource. If the resource did not exist and was created as part of the execution, then a status code201
SHOULD be returned.- It is expected that on a successful execution, a reference to the resource created (in the form of a link or resource identifier) is returned in the response body.
- Idempotency semantics: If this is a subsequent execution of the same invocation (including the
Foo-Request-Id
header) and the resource was already created, then a status code of200
SHOULD be returned. For more details on idempotency in APIs, refer to idempotency. - If a sub-resource is utilized ('controller' or data resource), and the primary resource identifier is non-existent,
404
is an appropriate response.
-
POST
can also be used while utilizing the controller pattern,200
is the appropriate status code. -
PUT
: This method SHOULD return status code204
as there is no need to return any content in most cases as the request is to update a resource and it was successfully updated. The information from the request should not be echoed back.- In rare cases, server generated values may need to be provided in the response, to optimize client flow (if the client necessarily has to perform a
GET
afterPUT
). In these cases,200
and a response body are appropriate.
- In rare cases, server generated values may need to be provided in the response, to optimize client flow (if the client necessarily has to perform a
-
PATCH
: This method should follow the same status/response semantics asPUT
,204
status and no response body.200
+ response body should be avoided at all costs, asPATCH
performs partial updates, meaning multiple calls per resource is normal. As such, responding with the entire resource can result in large bandwidth usage, especially for bandwidth-sensitive mobile clients.
-
DELETE
: This method SHOULD return status code204
as there is no need to return any content in most cases as the request is to delete a resource and it was successfully deleted.- As the
DELETE
method MUST be idempotent as well, it SHOULD still return204
, even if the resource was already deleted. Usually the API consumer does not care if the resource was deleted as part of this operation, or before. This is also the reason why204
instead of404
should be returned.
- As the
Hypermedia, an extension of the term hypertext, is a nonlinear medium of information which includes graphics, audio, video, plain text and hyperlinks according to wikipedia. Hypermedia As The Engine Of Application State (HATEOAS
) is a constraint of the REST application architecture described by Roy Fielding in his dissertation.
In the context of RESTful APIs, a client could interact with a service entirely through hypermedia provided dynamically by the service. A hypermedia-driven service provides representation of resource(s) to its clients to navigate the API dynamically by including hypermedia links in the responses. This is different than other form of SOA, where servers and clients interact based on WSDL-based specification defined somewhere on the web or exchanged off-band.
A hypermedia compliant API exposes a finite state machine of a service. Here, requests such as DELETE
, PATCH
, POST
and PUT
typically initiate a transition in state while responses indicate the change in the state. Lets take an example of an API that exposes a set of operations to manage a user account lifecycle and implements the HATEOAS interface constraint.
A client starts interaction with a service through a fixed URI /users
. This fixed URI supports both GET
and POST
operations. The client decides to do a POST
operation to create a user in the system.
Request:
POST https://api.foo.com/v1/customer/users
{
"given_name": "James",
"surname" : "Greenwood",
...
}
Response:
The API creates a new user from the input and returns the following links to the client in the response.
- A link to retrieve the complete representation of the user (aka
self
link) (GET
). - A link to update the user (
PUT
). - A link to partially update the user (
PATCH
). - A link to delete the user (
DELETE
).
{
HTTP/1.1 201 CREATED
Content-Type: application/json
...
"links": [
{
"href": "https://api.foo.com/v1/customer/users/ALT-JFWXHGUV7VI",
"rel": "self",
},
{
"href": "https://api.foo.com/v1/customer/users/ALT-JFWXHGUV7VI",
"rel": "delete",
"method": "DELETE"
},
{
"href": "https://api.foo.com/v1/customer/users/ALT-JFWXHGUV7VI",
"rel": "replace",
"method": "PUT"
},
{
"href": "https://api.foo.com/v1/customer/users/ALT-JFWXHGUV7VI",
"rel": "edit",
"method": "PATCH"
}
]
}
A client can store these links in its database for later use.
A client may then want to display a set of users and their details before the admin decides to delete one of the users. So the client does a GET
to the same fixed URI /users
.
Request:
GET https://api.foo.com/v1/customer/users
The API returns all the users in the system with respective self
links.
Response:
{
"total_items": "166",
"total_pages": "83",
"users": [
{
"given_name": "James",
"surname": "Greenwood",
...
"links": [
{
"href": "https://api.foo.com/v1/customer/users/ALT-JFWXHGUV7VI",
"rel": "self"
}
]
},
{
"given_name": "David",
"surname": "Brown",
...
"links": [
{
"href": "https://api.foo.com/v1/customer/users/ALT-MDFSKFGIFJ86DSF",
"rel": "self"
}
},
...
}
The client MAY follow the self
link of the user and figure out all the possible operations that it can perform on the user resource.
Request:
GET https://api.foo.com/v1/customer/users/ALT-JFWXHGUV7VI
Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"given_name": "James",
"surname": "Greenwood",
...
"links": [
{
"href": "https://api.foo.com/v1/customer/users/ALT-JFWXHGUV7VI",
"rel": "self",
},
{
"href": "https://api.foo.com/v1/customer/users/ALT-JFWXHGUV7VI",
"rel": "delete",
"method": "DELETE"
},
{
"href": "https://api.foo.com/v1/customer/users/ALT-JFWXHGUV7VI",
"rel": "replace",
"method": "PUT"
},
{
"href": "https://api.foo.com/v1/customer/users/ALT-JFWXHGUV7VI",
"rel": "edit",
"method": "PATCH"
}
}
To delete the user, the client retrieves the URI of the link relation type delete
from its data store and performs a delete operation on the URI.
Request:
DELETE https://api.foo.com/v1/customer/users/ALT-JFWXHGUV7VI
In summary:
- There is a well defined entry point for an API which a client navigates to in order to access all other resources.
- The client does not need to build the logic of composing URIs to execute different requests or code any kind of business rule by looking into the response details (more in detail is described in the later sections) that may be associated with the URIs and state changes.
- The client acknowledges the fact that the process of creating URIs belongs to the server.
- Client treats URIs as opaque identifiers.
- APIs using hypermedia in representations could be extended seamlessly. As new methods are introduced, responses could be extended with relevant HATEOAS links. In this way, clients could take advantage of the functionality in incremental fashion. For example, if the API starts supporting a new
PATCH
operation then clients could use it to do partial updates.
The mere presence of links does not decouple a client from having to learn the data required to make requests for a transition and all associated link semantics, particularly for POST
/PUT
/PATCH
operations. An API MUST provide documentation to clearly describe all the links, link relation types and request response formats for each of the URIs.
Subsequent sections provide more details about the structure of a link and what different relationship types mean.
Links MUST be described using the [Link Description Object (LDO)] 4 schema. An LDO describes a single link relation in the links array. Following is brief description for properties of Link Description Object.
-
href
:-
A value for the
href
property MUST be provided. -
The value of the
href
property MUST be a [URI template] 6 used to determine the target URI of the related resource. It SHOULD be resolved as a URI template per RFC 6570. -
Use ONLY absolute URIs as a value for
href
property. Clients usually bookmark the absolute URI of a link relation type from the representation to make API requests later. Developers MUST use the URI Component Naming Conventions to construct absolute URIs. The value from the incomingHost
header (e.g. api.foo.com) MUST be used as thehost
field of the absolute URI.
-
-
rel
:-
rel
stands for relation as defined in Link Relation Type -
The value of the
rel
property indicates the name of the relation to the target resource. -
A value for the
rel
property MUST be provided.
-
-
method
:- The
method
property identifies the HTTP verb that MUST be used to make a request to the target of the link. Themethod
property assumes a default value ofGET
if it is ommitted.
- The
-
title
:- The
title
property provides a title for the link and is a helpful documentation tool to facilitate understanding by the end clients. This property is NOT REQUIRED.
- The
Note that these API guidelines do not recommend using the HTTP Location
header to provide a link. Also, they do not recommend using the Link
header as described in JAX-RS. The scope of HTTP header is limited to point-to-point interaction between a client and a service. Since responses might be passed around to other layers and components on the client side which may not directly interact with the service, any information that is stored in a header may not be available. Therefore, we recommend returning Link Description Object(s) in HTTP response body.
The links
array property of schemas is used to associate a Link Description Objects with a [JSON hyper-schema draft-04] 3 instance.
- This property MUST be an array.
- Items in the array must be of type Link Description Object.
Here's an example of how you would describe links in the schema.
- A links array similar to the one defined in the sample JSON schema below MUST be provided as part of the API resource schema definition. Please note that the links array needs to be declared within the
properties
keyword of an object. This is required for code generators to add setter/getter methods for the links array in the generated object. - All possible links that an API returns as part of the response MUST be declared in the response schema using a URI template. The links array of URI templates MUST be declared outside the
properties
keyword.
{
"type": "object",
"$schema": "http://json-schema.org/draft-04/hyper-schema#",
"description": "A sample resource representing a customer name.",
"properties": {
"id": {
"type": "string",
"description": "Unique ID to identify a customer."
},
"first_name": {
"type": "string",
"description": "Customer's first name."
},
"last_name": {
"type": "string",
"description": "Customer's last name."
},
"links": {
"type": "array",
"items": {
"$ref": "http://json-schema.org/draft-04/hyper-schema#definitions/linkDescription"
}
}
},
"links": [
{
"href": "https://api.foo.com/v1/customer/users/{id}",
"rel": "self"
},
{
"href": "https://api.foo.com/v1/customer/users/{id}",
"rel": "delete",
"method": "DELETE"
},
{
"href": "https://api.foo.com/v1/customer/users/{id}",
"rel": "replace",
"method": "PUT"
},
{
"href": "https://api.foo.com/v1/customer/users/{id}",
"rel": "edit",
"method": "PATCH"
}
]
}
Below is an example response that is compliant with the above schema.
{
"id": "ALT-JFWXHGUV7VI",
"first_name": "John",
"last_name": "Doe",
"links": [
{
"href": "https://api.foo.com/v1/cusommer/users/ALT-JFWXHGUV7VI",
"rel": "self"
},
{
"href": "https://api.foo.com/v1/customer/users/ALT-JFWXHGUV7VI",
"rel": "edit",
"method": "PATCH"
}
]
}
A Link Relation Type
serves as an identifier for a link. An API MUST assign a meaningful link relation type that unambiguously describes the semantics of the link. Clients use the relevant Link Relation Type in order to identify the link to use from a representation.
When the semantics of a Link Relation Type defined in [IANA's list of standardized link relations] 5 matches with the one you want to define, then it MUST be used. The table below describes some of the commonly used link relation types. It also lists some additional lin relation types defined by these guidelines.
Link Relation Type | Description |
---|---|
self |
Conveys an identifier for the link's context. Usually a link pointing to the resource itself. |
create |
Refers to a link that can be used to create a new resource. |
edit |
Refers to editing (or partially updating) the representation identified by the link. Use this to represent a PATCH operation link. |
delete |
Refers to deleting a resource identified by the link. Use this Extended link relation type to represent a DELETE operation link. |
replace |
Refers to completely update (or replace) the representation identified by the link. Use this Extended link relation type to represent a PUT operation link. |
first |
Refers to the first page of the result list. |
last |
Refers to the last page of the result list provided total_required is specified as a query parameter. |
next |
Refers to the next page of the result list. |
prev |
Refers to the previous page of the result list. |
collection |
Refers to a collections resource (e.g /v1/users). |
latest-version |
Points to a resource containing the latest (e.g., current) version. |
search |
Refers to a resource that can be used to search through the link's context and related resources. |
up |
Refers to a parent resource in a hierarchy of resources. |
For all controller
style complex operations, the controller action
name must be used as the link relation type (e.g. activate
,cancel
,refund
).
See HATEOAS Use Cases to find where HATEOAS could be used.
Naming conventions for URIs, query parameters, resources, fields and enums are described in this section. Let us emphasize here that these guidelines are less about following the conventions exactly as described here but they are more about defining some naming conventions and sticking to them in a consistent manner while designing APIs. For example, we have followed snake_case for field and file names, however, you could use other forms such as CamelCase or something else that you have devised yourself. It is important to adhere to a defined convention.
URIs follow RFC 3986 specification. This specification simplifies REST API service development and consumption. The guidelines in this section govern your URI structure and semantics following the RFC 3986 constraints.
According to RFC 3986, the generic URI syntax consists of a hierarchical sequence of components referred to as the scheme, authority, path, query, and fragment as shown in example below.
https://example.com:8042/over/there?name=ferret#nose
\___/ \_______________/\_________/\_________/\__/
| | | | |
scheme authority path query fragment
Following is a brief description of the URI specific naming convention guidelines for APIs. This specification uses parentheses "( )" to group, an asterisk " * " to specify zero or more occurrences, and brackets "[ ]" for optional fields.
[scheme"://"][host[':'port]]"/v" major-version '/'namespace '/'resource ('/'resource)* '?' query
- URIs MUST start with a letter and use only lower-case letters.
- Literals/expressions in URI paths SHOULD be separated using a hyphen ( - ).
- Literals/expressions in query strings SHOULD be separated using underscore ( _ ).
- URI paths and query strings MUST percent encode data into UTF-8 octets.
- Plural nouns SHOULD be used in the URI where appropriate to identify collections of data resources.
/invoices
/statements
- An individual resource in a collection of resources MAY exist directly beneath the collection URI.
/invoices/{invoice_id}
- Sub-resource collections MAY exist directly beneath an individual resource. This should convey a relationship to another collection of resources (invoice-items, in this example).
/invoices/{invoice_id}/items
- Sub-resource individual resources MAY exist, but should be avoided in favor of top-level resources.
/invoices/{invoice_id}/items/{item_id}
- Better:
/invoice-items/{invoice_item_id}
- Resource identifiers SHOULD follow recommendations described in subsequent section.
Examples
https://api.foo.com/v1/vault/credit-cards
https://api.foo.com/v1/vault/credit-cards/CARD-7LT50814996943336KESEVWA
https://api.foo.com/v1/payments/billing-agreements/I-V8SSE9WLJGY6/re-activate
Formal Definition:
Term | Defiition |
---|---|
URI | [end-point] '/' resource-path ['?'query] |
end-point | [scheme "://"][ host [':' port]] |
scheme | "http" or "https" |
resource-path | "/v" version '/' namespace-name '/' resource ('/' resource) |
resource | resource-name ['/' resource-id] |
resource-name | Alpha (Alpha | Digit | '-')* |
resource-id | value |
query | name '=' value ('&' name = value)* |
name | Alpha (Alpha | Digit | '_')* |
value | URI Percent encoded value |
Legend
' Surround a special character with single quotes
" Surround strings with double quotes
() Use parentheses for grouping
[] Use brackets to specify optional expressions
* An expression can be repeated zero or more times
When modeling a service as a set of resources, developers MUST follow these principles:
-
Nouns MUST be used, not verbs.
-
Resource names MUST be singular for singletons; collections' names MUST be plural.
- A description of the automatic payments configuration on a user's account
GET /autopay
returns the full representation
- A collection of hypothetical charges:
GET /charges
returns a list of charges that have been madePOST /charges
creates a new charge resource,/charges/1234
GET /charges/1234
returns a full representation of a single charge
- A description of the automatic payments configuration on a user's account
-
Resource names MUST be lower-case and use only alphanumeric characters and hyphens.
- The hyphen character, ( - ), MUST be used as a word separator in URI path literals. Note that this is the only place where hyphens are used as a word separator. In nearly all other situations, the underscore character, ( _ ), MUST be used.
- Literals/expressions in query strings SHOULD be separated using underscore ( _ ).
- Query parameters values MUST be percent-encoded.
- Query parameters MUST start with a letter and SHOULD be all in lower case. Only alpha characters, digits and the underscore ( _ ) character SHALL be used.
- Query parameters SHOULD be optional.
- Some query parameter names are reserved, as indicated in Resource Collections.
For more specific info on the query parameter usage, see URI Standards.
The data model for the representation MUST conform to JSON. The values may themselves be objects, strings, numbers, booleans, or arrays of objects.
- Key names MUST be lower-case words, separated by an underscore character, ( _ ).
foo
bar_baz
- Prefix such as
is_
orhas_
SHOULD NOT be used for keys of type boolean. - Fields that represent arrays SHOULD be named using plural nouns (e.g. authenticators-contains one or more authenticators, products-contains one or more products).
Entries (values) of an enum SHOULD be composed of only upper-case alphanumeric characters and the underscore character, ( _ ).
FIELD_10
NOT_EQUAL
If there is an industry standard that requires us to do otherwise, enums MAY contain other characters.
A link relation type represented by rel
must be in lower-case.
- Example
"links": [
{
"href": "https://uri.foo.com/v1/customer/partner-referrals/ALT-JFWXHGUV7VI/activate",
"rel": "activate",
"method": "POST"
}
]
JSON schema for various types used by API SHOULD each be contained in separate files, referenced using the $ref
syntax (e.g. "$ref":"object.json"
). JSON Schema files SHOULD use underscore naming syntax, e.g. transaction_history.json
.
An API's resource path consists of URI's path, query and fragment components. It would include API's major version followed by namespace, resource name and optionally one or more sub-resources. For example, consider the following URI.
https://api.foo.com/v1/vault/credit-cards/CARD-7LT50814996943336KESEVWA
Following table lists various pieces of the above URI's resource path.
Path Piece | Description | Definition |
---|---|---|
v1 |
Specifies major version 1 of the API | The API major version is used to distinguish between two backward-incompatible versions of the same API. The API major version is an integer value which MUST be included as part of the URI. |
vault |
The namespace | Namespace identifiers are used to provide a context and scope for resources. They are determined by logical boundaries in the business capability model implemented by the API platform. |
credit-cards |
The resource name | If the resource name represents a collection of resources, then the GET method on the resource should retrieve the list of resources. Query parameters should be used to specify the search criteria. |
CARD-7LT50814996943336KESEVWA |
The resource ID | To retrieve a particular resource out of the collection, a resource ID MUST be specified as part of the URI. Sub-resources are discussed below. |
Sub-resources represent a relationship from one resource to another. The sub-resource name provides a meaning for the relationship. If cardinality is 1:1, then no additional information is required. Otherwise, the sub-resource SHOULD provide a sub-resource ID for unique identification. If cardinality is 1:many, then all the sub-resources will be returned. No more than two levels of sub-resources SHOULD be supported.
Example | Description |
---|---|
GET https://api.foo.com/v1/customer-support/disputes/ABCD1234/documents |
This call should return all the documents associated with dispute ABCD1234. |
GET https://api.foo.com/v1/customer-support/disputes/ABCD1234/documents/102030 |
This call should return only the details for a particular document associated with this dispute. Keep in mind that this is only an illustrative example to show how to use sub-resource IDs. In practice, two step invocations SHOULD be avoided. If second identifier is unique, top-level resource (e.g. /v1/customer-support/documents/102030 ) is preferred. |
GET https://api.foo.com/v1/customer-support/disputes/ABCD1234/transactions |
The following example should return all the transactions associated with this dispute and their details, so there SHOULD NOT be a need to specify a particular transaction ID. If specific transaction ID retrieval is needed, /v1/customer-support/transactions/ABCD1234 is preferred (assuming IDs are unique). |
Resource identifiers identify a resource or a sub-resource. These MUST conform to the following guidelines.
- The lifecycle of a resource identifier MUST be owned by the resource's domain model, where they can be guaranteed to uniquely identify a single resource.
- APIs MUST NOT use the database sequence number as the resource identifier.
- A UUID, Hashed Id (HMAC based) is preferred as a resource identifier.
- For security and data integrity reasons all sub-resource IDs MUST be scoped within the parent resource only.
Example:/users/1234/linked-accounts/ABCD
Even if account "ABCD" exists, it MUST NOT be returned unless it is linked to user: 1234. - Enumeration values can be used as sub-resource IDs. String representation of the enumeration value SHOULD be used.
- There MUST NOT be two resource identifiers one after the other.
Example:https://api.foo.com/v1/payments/payments/12345/102030
- Resource IDs SHOULD try to use either Resource Identifier Characters or ASCII characters. There SHOULD NOT be any ID using UTF-8 characters.
- Resource IDs and query parameter values MUST perform URI percent-encoding for any character other than URI unreserved characters. Query parameter values using UTF-8 characters MUST be encoded.
Query parameters are name/value pairs specified after the resource path, as prescribed in RFC 3986. Naming Conventions should also be followed when applying the following section.
- Query parameters SHOULD be used only for the purpose of restricting the resource collection or as search or filtering criteria.
- The resource identifier in a collection SHOULD NOT be used to filter collection results, resource identifier should be in the URI.
- Parameters for pagination SHOULD follow pagination guidelines.
- Default sort order SHOULD be considered as undefined and non-deterministic. If a explicit sort order is desired, the query parameter
sort
SHOULD be used with the following general syntax:{field_name}|{asc|desc},{field_name}|{asc|desc}
. For instance:/accounts?sort=date_of_birth|asc,zip_code|desc
In typical cases where one resource is utilized (e.g. /v1/payments/billing-plans/P-94458432VR012762KRWBZEUA
), query parameters SHOULD NOT be used.
In rare cases where a resource needs to be highly cacheable (usually data with minimal change), query parameters MAY be utilized as opposed to POST
+ request body. As POST
would make the response uncacheable, GET
is preferred in these situations. This is the only scenario in which query parameters MAY be required.
When POST
is utilized for an operation, query parameters are usually NOT RECOMMENDED in favor of request body fields. In cases where POST
provides paged results (typically in complex search APIs where GET
is not appropriate), query parameters MAY be used in order to provide hypermedia links to the next page of results.
When using query parameters for search functionality, it is often necessary to pass multiple values. For instance, it might be the case that a resource could have many states, such as OPEN
, CLOSED
, and INVALID
. What if an API client wants to find all items that are either CLOSED
or INVALID
?
-
It is RECOMMENDED that APIs implement this functionality by repeating the query parameter. This is inherently supported by HTTP standards and already built in to most client libraries.
- The query above would be implemented as
?status=CLOSED&status=INVALID
. - The parameter MUST be marked as repeatable in API specifications using
"repeated": true
in the parameter's definition section. - The parameter's name SHOULD be singular.
- The query above would be implemented as
-
URIs have practical length limits that are quite low - most conservatively, about 2,000 characters. Therefore, there are situations where API designers MAY choose to use a single query parameter that accepts comma-separated values in order to accommodate more values in the query-string. Keep in mind that server and client libraries don't consistently provide this functionality, which means that implementers will need to write additional string parsing code. Due to the additional complexity and divergence from HTTP standards, this solution is NOT RECOMMENDED unless justified.
- The query above would be implemented as
?statuses=CLOSED,INVALID
. - The parameter MUST NOT be marked as repeatable in API specifications.
- The parameter MUST be marked as
"type": "string"
in API specifications in order to accommodate comma-separated values. Any othertype
value MUST NOT be used. The parameter description should indicate that comma separated values are accepted. - The query-parameter name SHOULD be plural, to provide a hint that this pattern is being employed.
- The comma character (Unicode
U+002C
) SHOULD be used as the separator between values. - The API documentation MUST define how to escape the separator character, if necessary.
- The query above would be implemented as
We would assume that JSON Schema is used to describe request/response models. Determine the version of JSON Schema to use for your APIs. At the time of writing this, draft-04 is the latest version. JSON Schema draft-03 has been deprecated, as support in tools is mostly focused on draft-04. The draft-04 is backwards incompatible with draft-03.
There are various options available to define the API's contract interface (API specification or API description). Examples are: OpenAPI (fka Swagger), Google Discovery Document, RAML, API BluePrint and so on.
OpenAPI is a vendor neutral API description format. The OpenAPI Schema Object (or OpenAPI JSON) is based on the draft-04 and uses a predefined subset of the draft-04 schema. In addition, there are extensions provided by the specification to allow for more complete documentation.
We have used OpenAPI wherever we need to describe the API specification throughout this document.
A note about using $schema with OpenAPI
As of writing this (Q12017), OpenAPI tools DO NOT recognize $schema
value and (incorrectly) assume the value of $schema to be http://swagger.io/v2/schema.json#
only. The following description applies to JSON schema (http://json-schema.org) used in API specification specified using specifications other than OpenAPI.
Use $schema to indicate the version of JSON schema used by each JSON type you define as shown below.
{
"type": "object",
"$schema": "http://json-schema.org/draft-04/schema#",
"name": "Order",
"description": "An order transaction.",
"properties": {
}
}
In case your JSON type uses links
, media
and other such keywords or schemas such as for linkDescription
that are defined in http://json-schema.org/draft-04/hyper-schema, you should provide the value for $schema
accordingly as shown below.
{
"type": "object",
"$schema": "http://json-schema.org/draft-04/hyper-schema#",
"name": "Linked-Order",
"description": "An order transaction using HATEOAS links.",
"properties": {
}
}
If you are unsure about the specific schema version to refer to, it would be safe to refer http://json-schema.org/draft-04/hyper-schema#
schema since it would cover all aspects of any JSON schema.
When resources contain immutable fields, PUT
/PATCH
operations can still be utilized to update that resource. To indicate immutable fields in the resource, the readOnly
field can be specified on the immutable fields.
"properties": {
"id": {
"type": "string",
"description": "Identifier of the resource.",
"readOnly": true
},
Key differences between JSON schema draft-03 and draft-04 have been captured by JSON Schema contributors in a changelog. Additionally, this StackOverflow thread provides some additional quality migration guidance.
The following items are the most common migration issues API specifications will need to address:
draft-03 defines required fields in the composition of a field:
{
"type": "object",
"$schema": "http://json-schema.org/draft-03/schema#",
"name": "Order",
"description": "An order transaction.",
"properties": {
"id": {
"type": "string",
"description": "Identifier of the order transaction."
},
"amount": {
"required": true,
"description": "Amount being collected.",
"$ref": "v1/schema/json/draft-04/amount.json"
}
}
}
In draft-04, an array as a peer to properties is used to designate the required fields:
{
"type": "object",
"$schema": "http://json-schema.org/draft-04/schema#",
"name": "Order",
"description": "An order transaction.",
"required": [
"amount"
],
"properties": {
"id": {
"type": "string",
"description": "Identifier of the order transaction."
},
"amount": {
"description": "Amount being collected.",
"$ref": "v1/schema/json/draft-04/amount.json"
}
}
}
The readonly
field was removed from draft-03, and replaced by readOnly
(note the upper case O
).
The format attribute is used when defining fields which fall into a certain predefined pattern.
These following formats are deprecated in draft-04:
date
time
Therefore, only "format": "date-time"
MUST be used for any variation of date, time, or date and time. Any references to formats of date
or time
should be updated to date-time
.
Field descriptions SHOULD indicate specifics of whether date or time is accepted. date-time
specifies that ISO-8601/RFC3339 dates are utilized, which includes date-only and time-only.
Be aware that anyOf
/allOf
/oneOf
syntax can cause issues with tooling, as code generators, documentation tools and validation of these keywords is often not implemented.
The allOf
keyword MUST only be used for the purposes listed here.
The allOf
keyword in JSON Schema SHOULD be used for extending objects. In draft-03, this was implemented with the extends
keyword, which has been deprecated in draft-04.
A common need is to extend a common type with additional fields. In this example, we will extend the address with a type
field.
"shipping_address": { "$ref": "v1/schema/json/draft-04/address_portable.json" }
Using the allOf
keyword, we can combine both the common type address schema and an extra schema snippet for the address type:
"shipping_address": {
"allOf": [
// Here, we include our "core" address schema...
{ "$ref": "v1/schema/json/draft-04/address_portable.json" },
// ...and then extend it with stuff specific to a shipping
// address
{ "properties": {
"type": { "enum": [ "residential", "business" ] }
},
"required": ["type"]
}
]
}
The anyOf
and oneOf
keywords SHOULD NOT be used to design APIs. A variety of problems occur from these keywords:
- Codegen tools do not have accurate way to generate models/objects from these definitions.
- Developer portals would have significant difficulty in clearly explaining to API consumers the meaning of these relationships.
- Consumers using statically typed languages (e.g. C#, Java) have a more complex experience trying to conditionally deserialize objects which change based on some element.
- Custom deserialization code is required to represent objects based on the response, standard libraries do not accommodate this out of the box.
- Flat structures which combine all possible fields in an object are automatically deserialized properly.
{
"activity_type": {
"description": "The entity type of the item. One of 'PAYMENT', 'MONEY-REQUEST', 'RECURRING-PAYMENT-PROFILE', 'ORDER', 'PAYOUT', 'SUBSCRIPTION', 'INVOICE'",
"type": "string",
"enum": [
"PAYMENT",
"MONEY-REQUEST"
]
},
"extensions": {
"type": "object",
"description": "Extension to core activity fields",
"oneOf": [
{
"$ref": "extended_properties.json#/definitions/payment_properties"
},
{
"$ref": "extended_properties.json#/definitions/money_request_properties"
}
]
}
}
In order for an API consumer to deserialize this response (where POJO/POCO objects are used), standard mechanisms would not work. Because the extensions
field can change on any given response, the consumer is forced to create a composite object to represent both payment_properties.json
and money_request_properties.json
.
A better approach:
{
"activity_type": {
"description": "The entity type of the item. One of 'PAYMENT', 'MONEY-REQUEST', 'RECURRING-PAYMENT-PROFILE', 'ORDER', 'PAYOUT', 'SUBSCRIPTION', 'INVOICE'",
"type": "string",
"enum": [
"PAYMENT",
"MONEY-REQUEST"
]
},
"payment": {
"type": "object",
"description": "Payment-specific activity.",
"$ref": "payment.json"
},
"money_request": {
"type": "object",
"description": "Money request-specific activity.",
"$ref": "money_request.json"
}
}
In this scenario, both payment
and money_request
are in the definition. However, in practice, only one field would be serialized based on the activity_type
.
For API consumers, this is a very predictable response, and allows for easy deserialization through standard libraries, without writing custom deserializers.
This section provides guidelines related to usage of JSON primitive types as well as commonly useful JSON types for address, name, currency, money, country, phone, among other things.
JSON Schema draft-04 SHOULD be used to define all fields in APIs. As such, the following notes about the JSON Schema primitive types SHOULD be respected. Following are the guidelines governing use of JSON primitive type representations.
At a minimum, strings
SHOULD always explicitly define a minLength
and maxLength
.
There are several reasons for doing so.
- Without a maximum length, it is impossible to reliably define a database column to store a given string.
- Without a maximum and minimum, it is also impossible to predict whether a change in length will break backwards-compatibility with existing clients.
- Finally, without a minimum length, it is often possible for clients to send an empty string when they should not be allowed to do so.
APIs MAY avoid defining minLength
and maxLength
only if the string value is from another upstream system that has refused to provide any information on these values. This decision must be documented in the schema.
API authors SHOULD consider practical limitations when defining maxLength
. For example, when using the VARCHAR2 type, modern versions of Oracle can safely store a Unicode string of no more than 1,000 characters. (The size limit is 4,000 bytes and each Unicode character may take up to four bytes for storage).
string
SHOULD utilize the pattern
property as appropriate, especially when defining enumerated values or numbers. However, it is RECOMMENDED not to overly constrain fields without a valid technical reason.
The JSON Schema enum
keyword is difficult to use safely. It is not possible to add new values to an enum
in a schema that describes a service response without breaking backwards compatibility. In that scenario, clients will often reject responses with values that are not in the older copy of the schema that they posess. This is usually not the desired behavior. Clients should usually handle unknown values more gracefully, but since you can't control nor verify their behavior, it is not safe to add new enum values.
For the reasons stated above, the schema author MUST comply with the following guidelines while using an enum
with the JSON type string
.
- The keyword
enum
SHOULD be used only when the set of values are fixed and would never change in future. - If you anticipate adding new values to the
enum
array in future, avoid using the keywordenum
. You SHOULD instead use astring
type and document all acceptable values for thestring
. When using astring
type to express enumerated values, you SHOULD enforce naming conventions through apattern
field. - If there is no technical reason to do otherwise -- for instance, a pre-existing database column of smaller size --
maxLength
should be set to 255.minLength
should be set to 1 to prevent clients sending the empty string. - All possible values of an
enum
field SHOULD be precisely defined in the documentation. If there is not enough space in thedescription
field to do so, you SHOULD use the API’s user guide to define them.
Given below is the JSON snippet for enforcing naming conventions and length constraints.
{
"type": "string",
"minLength": 1,
"maxLength": 255,
"pattern": "^[0-9A-Z_]+$",
"description": "A description of the field. The possible values are OPTION_ONE and OPTION_TWO."
}
There are a number of difficulties associated with number
type in JSON.
JSON itself defines a number
very simply: it is an unbounded, fixed-point value. This is illustrated well by the railroad diagram for number
at JSON. There is only one number
type in JSON; there is no separate integer
type.
JSON Schema diverges from JSON and defines two number types: number
and integer
. This is purely a convenience for schema validation; the JSON number type is used to implement both. Just as in JSON, both types are unbounded unless the schema author provides explicit minimum
and maximum
values.
Many programming languages do not safely handle unbounded values in JSON. JavaScript is an excellent example. A JSON deserializer is provided as part of the ECMAScript specification. However, it requires that all JSON numbers are deserialized into the only number
type supported by JavaScript -- 64-bit floating point. This means that attempting to deserialize any JSON number larger than about 2^53 in a JavaScript client will result in an exception.
To ensure the greatest degree of cross-client compatibility possible, schema authors SHOULD:
-
Never use the JSON Schema
number
type. Some languages may interpret it as a fixed-point value, and some as floating-point. Always usestring
to represent a decimal value. -
Only define
integer
types for values that can be represented in a 32-bit signed integer, that is to say, values between ((2^31) - 1) and -(2^31
). This ensures compatibility across a wide range of programming languages and circumstances. For example, array indices in JavaScript are signed 32-bit integers. -
When using an
integer
type, always provide an explicit minimum and a maximum. This not only allows backwards-incompatible changes to be detected, it also guarantees that all values can fit in a 32-bit signed integer. If there are no technical reasons to do otherwise, themaximum
andminimum
should be defined as2147483647 (((2^31) - 1))
and-2147483648 (-(2^31))
or0
respectively. Common sense should be used to determine whether to allow negative values. Business logic that could change in the future generally SHOULD NOT be used to determine boundaries; business rules can easily change and break backwards compatibility.
If there is any possibility that the value could not be represented by a signed 32-bit integer, now or in the future, not use the JSON Schema integer
type. Use a string
instead.
Examples
This integer
type might be used to define an array index or page count, or perhaps the number of months an account has been open.
{
"type": "integer",
"minimum": 0,
"maximum": 2147483647
}
When using a string
type to represent a number, authors MUST provide boundaries on size using minLength
and maxLength
, and constrain the definition of the string to only represent numbers using pattern
.
For example, this definition only allows positive integers and zero, with a maximum value of 999999:
{
"type": "string",
"pattern": "^[0-9]+$",
"minLength": 1,
"maxLength": 6
}
The following definition allows the representation of fixed-point decimal values both positive or negative, with a maximum length of 32 and no requirements on scale:
{
"type": "string",
"pattern": "^(-?[0-9]+|-?([0-9]+)?[.][0-9]+)$"
"maxLength": 32,
"minLength": 1,
}
JSON defines array
as unbounded.
Although practical limits are often much lower due to memory constraints, many programming languages do place maximum theoretical limits on the size of arrays. For example, JavaScript is limited to the length of a 32-bit unsigned integer by the ECMA-262 specification. Java is limited to about Integer.MAX_VALUE - 8
, which is less than half of JavaScript.
To ensure maximum compatibility across languages and encourage paginated APIs, maxItems
SHOULD always be defined by schema authors. maxItems
SHOULD NOT have a value greater than can be represented by a 16-bit signed integer, in other words, 32767
or (2^15) - 1)
.
Note that developers MAY choose to set a smaller value; the value 32767
is a default choice to be used when no better choice is available. However, developers SHOULD design their API for growth. For example, although a paginated API may only support a maximum of 100 results per page today, performance improvements may allow deveopers to improve that to 1,000 results next year. Therefore, maxItems
SHOULD NOT be used to communicate maximum page size.
minItems
SHOULD also be defined. In most situations, its value will be either 0
or 1
.
APIs MUST NOT produce or consume null
values.
null
is a primitive type in JSON. When validating a JSON document against JSON Schema, a property's value can be null
only when it is explicitly allowed by the schema, using the type
keyword (e.g. {"type": "null"}
). Since in an API type
will always need to be defined to some other value such as object
or string
, and these standards prohibit using schema composition keywords such as anyOf
or oneOf
that allow multiple types, if an API produces or consumes null values, it is unlikely that, according to the API's own schemas, this is actually valid data.
In addition, in JSON, a property that doesn't exist or is missing in the object is considered to be undefined; this is conceptually separate from a property that is defined with a value of null
, but many programming languages have difficulty drawing this distinction.
For example, a property my_property
defined as {"type": "null"}
is represented as
{
"my_property": null
}
While a property that is undefined
would not be present in the object:
{ }
In most strongly typed languages, such as Java, there is no concept of an undefined
type, which means that for all undefined fields in a JSON object, a deserializer would return the value of such types as null
when you try to retrieve them. Similarly, some Java-based JSON serializers serialize fields to JSON null
by default, even though it is not possible for the serializer to determine whether the author of the Java object intended for that property to be defined with a value of null
, or simply undefined. In Jackson, for example, this behavior is moderated through use of the JsonInclude
annotation. On the other hand, the org.json library defines an object called NULL to distinguish between null
and undefined
.
Eschewing JSON null completely helps avoid many of these subtle cross-language compatibility traps.
Setting of additionalProperties
to false
in schema objects breaks backward compatibility in those clients that use an API's JSON schemas (defined by its contract) to validate the API requests and responses. For the same reason, the schema author MUST not explicitly set the additionalProperties
to false
.
The API implementation SHOULD instead enforce the conformance of requests and responses to an API contract by hard validating the requests and responses against the defined API contract at run-time.
Resource representations in API MUST reuse the common data type definitions where possible. Following sections provide some details about some of these common types. Please refer to the README
and the schema for more details.
We recommend using address_portable.json
for all requirements related to address. The address_portable.json
is
- backward compatible with hcard address microformats,
- forward compatible with Google open-source address validation metadata (i18n-api) and W3 HTML5.1 autofill fields,
- allows mapping to and from many address normalization services (ANS) such as AddressDoctor.
Please refer to README for Address for more details about the address type, guidance on how to map it to i18n-api's address and W3 HTML5.1's autofill fields.
Money is a standard type to represent amounts. The Common Type money.json
provides common definition of money.
Data-type integrity rules:
- Both
currency_code
andvalue
MUST exist for this type to be valid. - Some currencies such as "JPY" do not have sub-currency, which means the decimal portion of the value should be ".0".
- An amount MUST NOT be negative. For example a $5 bill is never negative. Negative or positive is an internal notion in association with a particular account/transaction and in respect of the type of the transaction performed.
Percentages and interest rates are very common when dealing with money. One of the most common examples is annual percentage rate, or APR. These interest rates SHOULD always be represented according to the following rules:
- The Common Type
percentage.json
MUST be used. This ensures that the rate is represented as a fixed-point decimal.- All validation rules defined in the type MUST be followed.
- The value MUST be represented as a percentage.
- Example: if the interest rate is 19.99%, the value returned by the API MUST be
19.99
.
- Example: if the interest rate is 19.99%, the value returned by the API MUST be
- The field's JSON schema description field SHOULD inform clients how the representation works.
- Example: "The interest rate is represented as a percentage. For example, an interest rate of 19.99% would be serialized as 19.99."
- It is the responsibility of the client to transform this value into a format suitable for display to the end-user. For example, some countries use the comma ( , ) as a decimal separator instead of the period ( . ). Services MUST NOT vary the format of values passed to or from a service based on end-user display concerns.
The following common types MUST be used with regard to global country, currency, language and locale.
country_code
- All APIs and services MUST use the ISO 3166-1 alpha-2 two letter country code standard.
currency_code
- Currency type MUST use the three letter currency code as defined in ISO 4217. For quick reference on currency codes, see http://en.wikipedia.org/wiki/ISO_4217.
language.json
- Language type uses BCP-47 language tag.
locale.json
- Locale type defines the concept of locale, which is composed of
country_code
andlanguage
. Optionally, IANA timezone can be included to further define the locale.
- Locale type defines the concept of locale, which is composed of
province.json
- Province type provides detailed definition of province or state, based on ISO-3166-2 country subdivisions, with room for variant local, international, and abbreviated representations of province names. Useful for logistics, statistics, and building state pull-downs for on-boarding.
When dealing with date and time, all APIs MUST conform to the following guidelines.
-
The date and time string MUST conform to the
date-time
universal format defined in section5.6
of RFC3339. In use cases where you would require only a subset of the fields (e.gfull-date
orfull-time
) from the RFC3339date-time
format, you SHOULD use the Date Time Common Types to express these. -
All APIs MUST only emit UTC time (aka Zulu time or GMT) in the responses.
-
When processing requests, an API SHOULD accept
date-time
or time fields that contain an offset from UTC. For example,2016-09-28T18:30:41.000+05:00
SHOULD be accepted as equivalent to2016-09-28T13:30:41.000Z
. This helps ensure compatibility with third parties who may not be capable of normalizing values to UTC before sending requests. In such cases the offset SHOULD only be used to calculate the equivalent UTC time before it is persisted in the system (because of known platform/language/DB interoperability issues). A UTC offset MUST NOT be used to derive anything else. -
If the business logic requires expressing the timezone of an event, it is RECOMMENDED that you capture the timezone explicitly by using a separate request/response field. You SHOULD NOT use offset to derive the timezone information. The offset alone is insufficient to accurately transform the stored UTC time back to a local time later. The reason is that a UTC offset might be same for many geographical regions and based on the time of the year there may be additional factors such as daylight savings. For example, an offset UTC-05:00 represents Eastern Standard Time during winter, Central Dayight Time during summer, and year-round offset for Panama, Columbia, and Peru.
-
The timezone string MUST be per IANA timezone database (aka Olson database or tzdata or zoneinfo database), for example America/Los_Angeles for Pacific Time, or Europe/Berlin for Central European Time.
-
When expressing floating time values that are not tied to specific time zones such as user's date of birth, expiry date, publication date etc. in requests or responses, an API SHOULD NOT associate it with a timezone. The reason is that a UTC offset changes the meaning of a floating time value. For examples, all countries with timezones west of prime meridian would consider a floating time value to be the previous day.
The following common types MUST be used to express various date-time formats:
date_time.json
SHOULD be used to express an RFC3339date-time
.date_no_time.json
SHOULD be used to expressfull-date
from RFC 3339.time_nodate.json
SHOULD be used to expressfull-time
from RFC3339.date_year_month.json
SHOULD be used to express a floating date that contains only the month and year. For example, card expiry date (2016-09
).time_zone.json
SHOULD be used for expressing timezone of a RFC3339date-time
or afull-time
field.
As per HTTP specifications, the outcome of a request execution could be specifiedusing an integer and a message. The number is known as the status code and the message as the reason phrase. The reason phrase is a human readable message used to clarify the outcome of the response. The HTTP status codes in the 4xx
range indicate client-side errors (validation or logic errors), while those in the 5xx
range indicate server-side errors (usually defect or outage). However, these status codes and human readable reason phrase are not sufficient to convey enough information about an error in a machine-readable manner. To resolve an error, non-human consumers of RESTful APIs need additional help.
Therefore, APIs MUST return a JSON error representation that conforms to the error.json
schema defined in the Common Types repository. It is recommended that the namespace that an API belongs to has an error catalog associated with it. Please refer to Error Catalog for more details.
An error response following error.json
as schema MUST include the following fields:
name
: A human-readable, unique name for the error. It is recommended that this value would be retrieved from the error catalogerror_spec.json#name
before sending the error response.details
: An array that contains individual instance(s) of the error with specifics such as the following. This field is required for client side errors (4xx
).field
: JSON Pointer to the field in error if in body, else name of the path parameter or query parameter.value
: Value of the field in error.issue
: Reason for error. It is recommended that this value would be retrieved from the error catalogerror_spec_issue.json#issue
before sending the error response.location
: The location of the field in the error, eitherquery
,path
, orbody
. If this field is not present, the default value isbody
.
debug_id
: A unique error identifier generated on the server-side and logged for correlation purposes.message
: A human-readable message, describing the error. This message MUST be a description of the problem NOT a suggestion about how to fix it. It is recommended that this value would be retrieved from the error catalogerror_spec.json#message
before sending the error response.links
: HATEOAS links specific to an error scenario. Use these links to provide more information about the error scenario and how to resolve it. You could insert links fromerror_spec.json#suggested_application_actions
and/orerror_spec.json#suggested_user_actions
here as well as other HATEOAS links relevant to the API.
The following fields are optional:
information_link
: (deprecated
) A URI for expanded developer information related to this error; this SHOULD be a link to the publicly available documentation for the type of error. Uselinks
instead.
If you have used some other means to identify the field
in an already released API, you could continue using your existing approach. However, if you plan to migrate to the approach suggested, you would want to bump up the major version of your API and provide migration assistance to your clients as this could be a potential breaking change for them.
The JSON Pointer for the field
SHOULD be a JSON string value.
In validating requests, there are a variety of concerns that should be addressed in the following order:
Request Validation Issue | HTTP status code |
---|---|
Not well-formed JSON. | 400 Bad Request |
Contains validation errors that the client can change. | 400 Bad Request |
Cannot be executed due to factors outside of the request body. The request was well-formed but was unable to be followed due to semantic errors. | 422 Unprocessable Entity |
This section provides some samples to describe usage of error.json
in various scenarios.
The following sample shows a validation error of type VALIDATION_ERROR
in one field. Because this is a client error, a 400 Bad Request
HTTP status code should be returned.
{
"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"
}
The following sample shows a validation error of the same type, VALIDATION_ERROR
, in two fields. Note that details
is an array listing all the instances in the error. Because both these are a client errors, a 400 Bad Request
HTTP status code should be returned.
{
"name": "VALIDATION_ERROR",
"details": [
{
"field": "/credit_card/expire_month",
"issue": "Required field is missing",
"location": "body"
},
{
"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"
}
For heterogenous types of client-side errors shown below, OBJECT_NOT_FOUND_ERROR
and MULTIPLE_CORE_BUNDLES
, an array named errors
is returned. Each error instance is represented as an item in this array. Because these are client validation errors, a 400 Bad Request
HTTP status code should be returned.
"errors": [
{
"name": "OBJECT_NOT_FOUND_ERROR",
"debug_id": "38cdd677a83a4",
"message": "Bundle is not found.",
"information_link": "<link to public doc describing OBJECT_NOT_FOUND_ERROR error>",
"details": [
{
"field": "/bundles/0/bundle_id",
"value": "33333",
"issue": "BUNDLE_NOT_FOUND",
"location": "body"
}
]
},
{
"name": "MULTIPLE_CORE_BUNDLES",
"debug_id": "52cde38284sd3",
"message": "Multiple CORE bundles.",
"information_link": "<link to public doc describing MULTIPLE_CORE_BUNDLES error>",
"details": [
{
"field": "/bundles/5/bundle_id",
"value": "88888",
"issue": "MULTIPLE_CORE_BUNDLES",
"location": "body"
}
]
}
]
In cases where client input is well-formed and valid but the request action may require interaction with APIs or processes outside of this URI, an HTTP status code 422 Unprocessable Entity
should be returned.
{
"name": "BALANCE_ERROR",
"debug_id": "123456789",
"message": "The account balance is too low. Add balance to your account to proceed.",
"information_link": "http://developer.foo.com/apidoc/blah#BALANCE_ERROR"
}
It is important that documentation generation tools and client/server-side binding generation tools recognize error.json
. Following section shows how you could refer error.json
in an API specification confirming to OpenAPI.
"responses": {
"200": {
"description": "Address successfully found and returned.",
"schema": {
"$ref": "address.json"
}
},
"403": {
"description": "Unauthorized request. This error will occur if the SecurityContext header is not provided or does not include a party_id."
},
"404": {
"description": "The requested address does not exist.",
"schema": {
"$ref": "v1/schema/json/draft-04/error.json"
}
},
"default": {
"description": "Unexpected error response.",
"schema": {
"$ref": "v1/schema/json/draft-04/error.json"
}
}
}
The User Guide of an API is a document that is exposed to API consumers. In addition to linking to samples showing successful execution for invocation on various methods exposed by the API, the API developer should also provide links to samples showing error scenarios. It is equally, or perhaps more, important to show the API consumers how an API would propagate errors in a machine-readable form in order to build applications that take necessary actions to handle errors gracefully and in a meaningful manner.
In conclusion, we reiterate the message we started with that non-human consumers of RESTful APIs need more help to take necessary actions to resolve an error in a machine-readable manner. Therefore, a representation of errors following the schema described here MUST be returned by APIs for any HTTP status code that falls into the ranges of 4xx and 5xx.
Error handling guidelines described earlier show how to provide error related details responses at runtime. This section explains how to catalog the errors so they can be easily consumed in service runtime and for generating documentation.
An Error Catalog is a single JSON file that contains a collection of error specifications (or error metadata) for a namespace. Each error specification includes error name, error message, issue details and related links among other things. The error catalog supports multiple locales or languages. For a specific error catalog, there should be exactly one default version, known as the top-level catalog, which could be in English for example. There should be corresponding locale-specific catalogs, one for each additional supported locale, as needed.
Following are some reasons to catalog the errors for an API:
- To externalize hard-coded error message strings from the API implementation: Developers are good at writing code but not necessarily good at writing error messages. Often error messages written by a developer make a lot of assumptions about the context and audience. It is hard to change such error messages if these are embedded in implementation code. For example, to change a message from "Add Card refused due to compliance guidelines" to
Could not add card due to failure to comply with guideline %s
, a service developer has to make the change and also redeploy the service emitting that error. - To localize the error strings: If error related strings such as
message
,issue
,actions
, among other things inerror.json
are externalized, it is easy for the documentation and an internationalization team to modify and localize these without any help from service developers and without requiring redeployment of the services. - To keep service's implementation and the API documentation in sync with regards to errors: API consumers should be able to refer with confidencethe API's documentation for errors generated by the services at runtime. This helps to reduce the cost and the time spent supporting an API and increases
adoptability
of the API.
There are four main JSON schema files for the Error Catalog.
error_catalog.json
defines top-level catalog container.namespace
: API namespacelanguage
: language used to catalog the errors. Default is US English. This value MUST be a BCP-47 language tag as inen
oren-US
.errors
: one or more error catalog items.
error_catalog_item.json
defines an item in error catalog. This schema in its initial version only includes error specificationerror_spec
. In future versions, it would provide a space to establish relationship between an error specification and method specifications that would use the error specification to respond with error.error_spec.json
is where the core of error specification is defined. The specification includes the following properties.name
: A human-readable, unique name for the error. This value MUST be the value set inerror.json#name
before sending the error response.message
: A human-readable message, describing the error. This message MUST be a description of the problem NOT a suggestion about how to fix it. This value MUST be the value set inerror.json#message
before sending the error response. This value could be localized.log_level
: Log level associated with this error. This MUST NOT be streamed out in error responses or exposed in any external documentation.legacy_code
: Legacy error code. Use if and only if the existing and published error metadata uses the code and it must continue to be supported. UtilizeadditionalProperties
oferror.json
to send this code in the error response.http_status_codes
: Applicable HTTP status codes for this error.suggested_application_actions
: Suggest practical actions that the developer of application consuming the API could take in order to resolve the error condition. These MUST be in English.suggested_user_actions
: Suggest practical actions that a user of the application consuming the API could take in order to resolve the error condition. These MUST be in the language usederror_catalog.json#language
.links
: Error context specific HATEOAS links. Corresponds toerror.json#links
.issues
: Issues associated with this error as defined inerror_spec_issue.json
. Each issue corresponds to an item inerror_details.json
.
error_spec_issue.json
defines details related to the error. For example, there could be multiple validation errors triggering400 BAD REQUEST
. Each invalid field MUST be listed in theerror_details.json
while sending the error response.id
: Catalog-unique identifier of the issue. This is required in order to search for theerror_spec_issue
from cached error catalog.issue
: Reason for error. This value MUST be the value set inerror_details.json#issue
. The issue string could have variables. Please use parametrized string following the Java String Formatter syntax. This value chould be localized.
For API services that are implemented in Java, various strings found in the error catalog MUST be formatted using Java's printf-style
inspired format. It's recommended to use Java's format specification for values of message
and issue
fields in the error catalog where applicable.
Service developers are strongly encouraged to use tools, such as Java String Formatter or similar, to interpret the formatted strings as found in the error catalog.
For example, an error_spec
having value Could not add card due to failure to comply with guideline %s
for message
must be interpreted using a formatter as shown below.
com.foo.platform.error.ErrorSpec errorSpec = <find errorSpec from catalog>
com.foo.platform.error.Error error = new Error();
error.setName(errorSpec.getName());
error.setDebugId("debugId-777");
error.setLogLevel(errorSpec.getLogLevel());
String errorMessageString = String.format(errorSpec.getMessage(), "GUIDELINE: XYZ");
error.setMessage(errorMessageString);
List<Detail> details = new ArrayList<>();
Detail detail = new Detail();
String issueString = String.format(errorSpec.getIssues().get(0).getIssue(), ... <variables in issue string>);
detail.setIssue(issueString);
details.add(detail);
error.setDetails(details);
Response response = Response.status(Response.Status.BAD_REQUEST).entity(error).encoding(MediaType.APPLICATION_JSON).build();
throw new WebApplicationException(response);
The above example code is for illustration purposes only.
This section provides some sample error catalogs.
{
"namespace": "payments",
"language": "en-US",
"errors": [{
"error_spec": {
"name": "VALIDATION_ERROR",
"message": "Invalid request - see details",
"log_level": "ERROR",
"http_status_codes": [
400
],
"issues": [{
"id": "InvalidCreditCardType",
"issue": "Value is invalid (must be visa, mastercard, amex, or discover)"
}],
"suggested_application_actions": [
"Provide an acceptable card type and resend the request."
]
}
}, {
"error_spec": {
"name": "PAYEE_ACCOUNT_LOCKED_OR_CLOSED",
"message": "Payee account is locked or closed",
"log_level": "ERROR",
"http_status_codes": [
422
],
"legacy_code": "PAYER_ACCOUNT_LOCKED_OR_CLOSED",
"issues": [{
"id": "PayerAccountLocked",
"issue": "The account receiving this payment is locked or closed and cannot receive payments."
}],
"suggested_user_actions": [
"Contact Customer Service at [email protected]"
]
}
}]
}
{
"namespace": "wallet",
"language": "en-US",
"errors": [{
"error_spec": {
"name": "INVALID_ISSUER_DETAILS",
"message": "Invalid issuer details",
"log_level": "ERROR",
"http_status_codes": [
400
],
"issues": [{
"id": "ISSUER_DATA_NOT_FOUND",
"issue": "Issuer data not found"
}],
"suggested_application_actions": [
"Provide issuer related data and resend the request."
]
}
}, {
"error_spec": {
"name": "INSTRUMENT_BLOCKED",
"message": "Instrument is currently blocked.",
"log_level": "ERROR",
"http_status_codes": [
422
],
"issues": [{
"id": "BankAccountBlocked",
"issue": "Bank account is blocked due max random deposit retries. "
}],
"suggested_user_actions": [
"Contact Customer Service at [email protected]."
]
}
}]
}
{
"namespace": "payment-networks",
"language": "en-US",
"errors": [{
"error_spec": {
"name": "VENDOR_TIMEOUT",
"message": "Transaction timed out while waiting for response from downstream service provided by a 3rd party vendor.",
"log_level": "ERROR",
"http_status_codes": [
504
],
"suggested_application_actions": [
"Retry again later."
]
}
}, {
"error_spec": {
"name": "INTERNAL_TIMEOUT",
"message": "Internal error due to timeout. Request took too long to process. The status of the transaction is unknown.",
"log_level": "ERROR",
"http_status_codes": [
500
],
"suggested_application_actions": [
"Contact Customer Service at [email protected] and provide Correlation-Id and debug_id for diagnosis along with other details."
]
}
}]
}
This section describes how to version APIs. It describes API's lifecycle states, enumerates versioning policy, describes backwards compatiblity related guidelines and describes an End-Of-Life policy.
Following is an example of states of an API's lifecycle.
State | Description |
---|---|
PLANNED | API has been scheduled for development. API release plans have been established. |
BETA | API is operational and is available to selected new subscribers in production for the purposes of testing, validating, and rolling out a new API. |
LIVE | API is operational and is available to new subscribers in production. API version is fully supported. |
DEPRECATED | API is operational and available at runtime to existing subscribers. API version is fully supported, including bug fixes addressed in backwards compatible way. API version is not available to new subscribers. |
RETIRED | API is unpublished from production and no longer available at runtime to any subscribers. The footprint of all deployed applications entering this state must be completely removed from production and stage environments. |
API’s are versioned products and MUST adhere to the following versioning principles.
- API specifications MUST follow the versioning scheme where where the
v
introduces the version, the major is an ordinal starting with1
for the first LIVE release, and minor is an ordinal starting with0
for the first minor release of any major release. - Every time there is an incremental change to an API, whether or not backward compatible, the API specification MUST be versioned. This allows the change to be labeled, documented, reviewed, discussed, published and communicated.
- API endpoints MUST only reflect the major version.
- API specification versions reflect interface changes and MAY be separate from service implementation versioning schemes.
- A minor API version MUST maintain backward compatibility with all previous minor versions, within the same major version.
- A major API version MAY maintain backward compatibility with a previous major version.
For a given functionality set, there MUST be only one API version in the LIVE
state at any given time across all major and minor versions. This ensures that subscribers always understand which versioned API product they should be using. For example, v1.2 RETIRED
, v1.3 DEPRECATED
, or v2.0 LIVE
.
APIs SHOULD be designed in a forward and extensible way to maintain compatibility and avoid duplication of resources, functionality and excessive versioning.
APIs MUST adhere to the following principles to be considered backwards compatible:
- All changes MUST be additive.
- All changes MUST be optional.
- Semantics MUST NOT change.
- Query-parameters and request body parameters MUST be unordered.
- Additional functionality for an existing resource MUST be implemented either:
- As an optional extension, or
- As an operation on a new child resource, or
- By altering a request body, while still accepting all the previous, existing request variations, if an existing operation (e.g., resource creation) cannot be reasonably extended.
URIs
- A resource URI MAY support additional query parameters but they CANNOT be mandatory.
- There MUST NOT be any change in the behavior of the API for request URIs without the newly added query parameters.
- A new parameter with a required constraint SHALL NOT be added to a request.
- The semantics of an existing parameter, entire representation, or resource SHOULD NOT be changed.
- A service MUST recognize a previously valid value for a parameter and SHOULD NOT throw an error when used.
- There MUST NOT be any change in the HTTP status codes returned by the URIs.
- There MUST NOT be any change in the HTTP verbs (e.g.
GET
,POST
,PUT
orPATCH
) supported earlier by the URI. The URI MAY however support a new HTTP verb. - There MUST NOT be any change in the name and type of the request or response headers of an URI. Additional headers MAY be added, provided they’re optional.
APIs only support media type application/json
. The following applies for JSON representation stability.
- An existing property in a JSON object of an API response MUST continue to be returned with same name and JSON type (number, integer, string, array, object).
- If the value of a response field is an array, then the type of the contents in the array MUST NOT change.
- If the value of the response field is an object, then the compatibility policy MUST apply to the JSON object as a whole.
- New properties MAY be added to a representation any time, but it SHOULD NOT alter the meaning of an existing property.
- New properties that are added MUST NOT be mandatory.
- Mandatory fields are guaranteed to be present in the response.
- For primitive types, unless there is a constraint described in the API documentation (e.g. length of the string, possible values for an ENUM type), clients MUST not assume that the values are constrained in any way.
- If the property of an object is a URI, then it MUST have the same stability mentioned as URIs.
- For an API returning HATEOAS links as part of the representation, the values of rel and href MUST remain the same.
- For ENUM types, there MUST NOT be any change in already supported enumerated values or meaning of these values.
The End-of-Life (EOL) policy regulates how API versions move from the LIVE
to the RETIRED
state. It is designed to ensure a consistent and reasonable transition period for API customers who need to migrate from the old to the new API version while enabling a healthy process to retire technical debt.
Minor API Version EOL
Per versioning policy, minor API versions MUST be backwards compatible with preceding minor versions within the same major version. Thus, minor API versions are RETIRED
immediately after a newer minor version of the same major version becomes LIVE
. This change should have no impact on existing subscribers so there is no need to transition through a DEPRECATED
state to facilitate client migration.
Major API Version EOL
Per versioning policy, major API versions MAY be backwards compatible with preceding major versions. As such, the following rules apply when retiring a major API version.
- A major API version MUST NOT be in the
DEPRECATED
state until a replacement service isLIVE
that provides a clear customer migration path for all functionality that will be retained moving forward. This SHOULD include documentation and, as appropriate, migration tools and sample code that provide customers what they need to make a clean migration. - The deprecated API version MUST be in the
DEPRECATED
state for a minimum period of time to give client customers adequate notice to migrate. Deprecation of API versions with external clients SHOULD be considered on a case-by-case basis and may require additional deprecation time and/or constraints to minimize impact to the business. - If a versioned API in
LIVE
orDEPRECATED
state has no clients, it MAY move to theRETIRED
state immediately.
Since a new major API version that results in deprecation of a pre-existing API version is a significant business investment decision, API owners MUST justify the new major version before beginning significant design and development work. API owners SHOULD explore all possible alternatives to introducing a new major API version with the objective of minimizing the impact on customers before deciding to introduce a new version. Justification SHOULD include the following:
Business Case
- Customer value delivered by new version that is not possible with existing version.
- Cost-benefit analysis of deprecated version versus the new version.
- Explanation of alternatives to introducing an new major version and why those were not chosen.
- If a backwards incompatible change is required to address a critical security issue, items 1 and 2 (above) are not required.
API Design
- A domain model of all resources in the new API version and how they compare to the domain model of the previous major API version.
- Description of APIs operations and use cases they implement.
- Definition of service level objectives for performance and availability that are equal or better to the major API version to be deprecated.
Migration Strategy
- Number of existing customers impacted; internal, external, and partners.
- Communication and support plan to inform existing customers of new version, value, and migration path.
This document describes a solution to deprecate parts of an API as the API evolves. It is an extension to the API Versioning Policy.
The term API Element
is used throughout this document to refer to the things that could be deprecated in an API. Examples of an API Element
are: an endpoint, a query parameter, a path parameter, a property within a JSON Object schema, JSON Object schema of a type or a custom HTTP header among other things.
The term old API
is used to indicate existing minor or major version of your API or an existing different API that your API supersedes.
The term new API
is used to indicate a new minor or major version of your API or a new different API that supersedes the old API
.
API definition
is in the form of specification of an interface of a service following the OpenAPI. API's definition could be found in swagger.json
.
When defining your API, you must make a lot of material decisions that have long lasting implications. The objective is to make a long-lived, durable, and reusable API. You are trying to get it "right". Practically speaking, however, you are not going to succeed every time. New requirements come in. Your understanding of the problem changes. What probably looked like a good decision at the time, may now limit your ability to elegantly extend your API to satisfy your new understanding. Lightweight decisions made in the past now seem somewhat heavy as the implications come into focus. Maintaining backward compatibility is a persistent challenge.
One option is to create a new major version of your API. This allows you to leave past decisions behind and start fresh. Unfortunately, it also means that all of your clients now need to migrate to new endpoints for any of the new work to deliver customer value. This is hard. Many clients will not move without good incentives. There is a lot of overhead in managing the customer migration. You also need to support two sets of interfaces for quite some time. The other consideration is that your API product may have multiple endpoints, but the breaking changes that you want to make only affect one. Making your customers migrate their applications for all the API endpoints just so you can “fix” one small part is a pretty heavyweight and expensive change. While pure and simple from a philosophical and engineering point of view, it is often unjustifiable from an ROI standpoint. The goal of this guideline is to find a middle ground that provides a more practical path forward when minor changes are needed, but which is still consistent, in spirit, with the API Versioning Policy.
Here are the requirements for deprecation.
- An API developer should be able to deprecate an
API Element
in a minor version of an API. - An API specification MUST highlight one or more deprecated elements of the API so the API consumers are aware.
- An API server MUST inform client app(s) regarding deprecated elements present in request and/or response at runtime so that tools can recognize this, log warnings and highlight the usage of deprecated elements as needed.
- Deprecated
API Elements
must remain supported for the life of the major version or until customers are no longer using them (the means to determine this are left to the discretion of the API owner since it's their customers who will ultimately be impacted).
The following describes how to address the requirements listed above. The solution involves addressing documentation related requirement using an annotation and using a custom header to address the runtime related requirement.
An optional annotation named x-deprecated
is used to mark an API Element
as deprecated in the definition of the API.
x-deprecated
can be used to deprecate any kind of API Element
. The annotation should be used inline precisely where the API Element
is defined. It is expected that the API tools generating documentation by introspecting the API definition would recognize this annotation and highlight the corresponding API Element
as deprecated. It is also assumed that this annotation can be completely ignored by tools including those generating implementation bindings (POJO). In other words, it is not in scope of this solution that any implementation language specific construct (such as Java annotation @deprecated
) would be generated for the x-deprecated
annotation.
It is expected that the API documentation would highlight deprecated API Elements
annotated by x-deprecated
in the API specification distinctly and at the appropriate granularity.
We have provided specific JSON object types to use for deprecation of specific API Elements
. This section lists schema for these types. The intent in providing schema for specific application of x-annotation
is to make it easy for API developers to annotate the API definition
and for API tool(s) to highlight each deprecated API Element
with appropriate details.
Following are common schema types that are used across new JSON object types to be used for deprecation.
"x-deprecatedValue": {
"type": "string",
"description": "Value of the element that is deprecated. Use to deprecate a particular value in parameter or schema property as applicable."
},
"x-deprecatedSee": {
"type": "string",
"description": "URI (indirect or absolute) or name of to new parameter, resource, method, api_element, as applicable."
},
"x-apiVersion": {
"pattern": "^[1-9][0-9]*[.][0-9]+$",
"minLength": 3,
"maxLength": 8,
"description": "This string should contain the release or version number at which this schema element became deprecated. Version should be in the format '{major}.{minor}' (no leading 'v')."
}
The following schema MUST be used to deprecate resource objects in API definition
. Examples of resource object in swagger.json
are: operation
and paths
.
"x-deprecatedResource": {
"type": "object",
"title": "Schema for a deprecated resource.",
"description": "Schema for deprecating a resource API element. A resource API element could be an operation or paths.",
"properties": {
"see": {
"$ref": "#/definitions/x-deprecatedSee"
},
"since_version": {
"$ref": "#/definitions/x-apiVersion"
}
}
}
The following section provides several examples showing usage of deprecatedResource
for x-deprecated
annotation at resource level.
The following example shows deprecated resource named commercial-entities
in swagger.json
.
"paths": {
"/commercial-entities": {
"x-deprecated": {
"see": "financial-entities",
"since_version": "1.4"
},
...
}
The following example shows a deprecated method PUT /commercial-entities/{merchant_id}/agreements
and encourages to use new method PATCH /commercial-entities/{merchant_id}/agreements
.
"paths": {
"/commercial-entities/{merchant_id}/agreements": {
"put": {
"summary": "Updates the Commercial Entity Agreements Details for a Merchant.",
"operationId": "commercial-entity.agreement.update",
"x-deprecated": {
"see": "patch",
"since_version": "1.4"
},
"parameters": [
{
"name": "merchant_id",
"in": "path",
"description": "The encrypted Merchant's identifier.",
"required": true,
"type": "string"
},
{
"name": "Agreements",
"in": "body",
"description": "An array of AgreementDetails",
"required": true,
"schema": {
"$ref": "./model/agreement_details.json"
}
}
],
...
}
swagger.json
provides a way to define one or more parameter for a method. The type of parameters are: path, query and header. Typically, query and header parameters can be deprecated. The following schema MUST be used while using x-deprecated
annotation for a parameter.
"x-deprecatedParameter": {
"type": "object",
"title": "Schema for a deprecated parameter.",
"description": "Schema for deprecating an API element inline. The API element could be a custom HTTP header or a query param.",
"properties": {
"value": {
"$ref": "#/definitions/x-deprecatedValue"
},
"see": {
"$ref": "#/definitions/x-deprecatedSee"
},
"since_version": {
"$ref": "#/definitions/x-apiVersion"
}
}
}
The following section provides several examples showing usage of deprecatedParameter
for x-deprecated
annotation at parameter level.
The following example shows usage of the x-deprecated
annotation in swagger.json
to indicate deprecation of a query parameter record_date
.
"/commercial-entities/{merchant_id}": {
"get": {
"summary": "Gets a Commercial Entity as denoted by the merchant_id.",
"description": "Gets the Commercial Entity as denoted by the merchant_id.",
"operationId": "commercial-entity.get",
"parameters": [
{
"name": "record_date",
"in": "query",
"description": "The date to use for the query; defaulted to yesterday.",
"required": false,
"type": "string",
"format": "date",
"x-deprecated": {
"since_version": "1.5",
"see": "transaction_date"
}
},
{
"name": "transaction_date",
"in": "query",
"description": "The date to use for the query; defaulted to yesterday.",
"required": false,
"type": "string",
"format": "date"
},
...
The following example shows a deprecated custom HTTP header called CLIENT_INFO
.
"/commercial-entities/{merchant_id}": {
"get": {
"summary": "Gets a Commercial Entity as denoted by the merchant_id.",
"description": "Gets the Commercial Entity as denoted by the merchant_id.",
"operationId": "commercial-entity.get",
"parameters": [
...
{
"name": "CLIENT_INFO",
"in": "header",
"description": "Optional header for all the API's to pass on api caller tracking information. This header helps capture any input from the caller service and pass it along to analytics for tracking .",
"in" : "header",
"x-deprecated": {
"since_version": "1.5"
}
}
...
The following example shows usage of the x-deprecated
annotation in swagger.json
to indicate deprecation of a specific value (y
) for a query parameter named fields
.
"/commercial-entities": {
"get": {
"summary": "Gets Commercial Entities.",
"description": "Gets the Commercial Entities.",
"operationId": "commercial-entity.get",
"parameters": [
{
"name": "fields",
"in": "query",
"description": "Fields to return in response, default is x, possible values are x, y, z.",
"required": false,
"type": "string",
"x-deprecated": {
"since_version": "1.5",
"value": "y"
}
},
{
"name": "transaction_date",
"in": "query",
"description": "The date to use for the query; defaulted to yesterday.",
"required": false,
"type": "string",
"format": "date"
},
...
In order to deprecate a schema of JSON Object itself or one or more properties within the JSON Object schema, we recommend using a schema called deprecatedSchema
for the x-deprecated
annotation.
"x-deprecatedSchema": {
"type": "array",
"description": "Schema for a collection of deprecated items in a schema.",
"items": {
"$ref": "#/definitions/x-deprecatedSchemaProperty"
}
}
"x-deprecatedSchemaProperty": {
"type": "object",
"title": "Schema for a deprecated schema property or schema itself.",
"description": "Schema for deprecating an API element within JSON Object schema. The API element could be an individual property of a schema of a JSON type or an entire schema representing JSON object.",
"required": ["api_element"],
"properties": {
"api_element": {
"type": "string",
"description": "JSON pointer to API element that is deprecated. If the API element is JSON Object schema of a type itself, JSON pointer MUST point to the root of that schema. If the API element is a property of schema, the JSON pointer MUST point to that property."
},
"value": {
"$ref": "#/definitions/x-deprecatedValue"
},
"see": {
"$ref": "#/definitions/x-deprecatedSee"
},
"since_version": {
"$ref": "#/definitions/x-apiVersion"
}
}
}
In order to avoid extending JSON draft-04 schema with keywords
needed to either deprecate the schema itself by using x-annotation
in metadata section of the schema or deprecate individual properties inline, we have chosen a less disruptive route of using x-annotation
next to references for JSON Object in API definition
. Therefore, this annotation SHOULD be used in API definition
where schema is "referred". In case if you are OK with inlining, you should go ahead and annotate individual deprecated properties in schema or schema itself.
As of March 2017, OpenAPI 3.0.0-rc0 has introduced deprecated
flag to apply at operation, parameter and schema field levels. x-annotation
could be used alongside the deprecated
flag to provide additional useful information for the deprecated API Element
.
The following example shows usage of the x-deprecated
annotation in API definitions
to indicate deprecation of a property named address
in response.
"/commercial-entities/{merchant_id}": {
"get": {
"summary": "Gets a Commercial Entity as denoted by the merchant_id.",
"description": "Gets the Commercial Entity as denoted by the merchant_id.",
"operationId": "commercial-entity.get",
"responses": {
"200": {
"description": "The Commercial Entity.",
"schema": {
"$ref": "./model/interaction/commercial-entities/merchant_id/get_response.json",
"x-deprecated": [
{
"api_element": "./model/interaction/commercial-entities/merchant_id/get_response.json#/address",
"see": "./model/interaction/commercial-entities/merchant_id/get_response.json#/global_address",
"since_version": "1.4"
}
]
}
},
"default": {
"description": "Unexpected error",
"schema": {
"$ref": "v1/schema/json/draft-04/error.json"
}
}
}
The following example shows usage of the x-deprecated
annotation in API definitions
to indicate deprecation of an enum FAILED
used by a property named state
.
"/commercial-entities/{merchant_id}": {
"get": {
"summary": "Gets a Commercial Entity as denoted by the specified merchant identifier.",
"description": "Gets the Commercial Entity as denoted by the specified merchant identifier.",
"operationId": "commercial-entity.get",
"responses": {
"200": {
"description": "The Commercial Entity.",
"schema": {
"$ref": "./model/interaction/commercial-entities/merchant_id/get_response.json",
"x-deprecated": [
{
"api_element": "./model/interaction/commercial-entities/merchant_id/get_response.json#/state",
"value": "FAILED",
"since_version": "1.4"
}
]
}
},
"default": {
"description": "Unexpected error",
"schema": {
"$ref": "v1/schema/json/draft-04/error.json"
}
}
}
The API server MUST inform client app(s) of the deprecated API element
(s) present in request and/or response.
It is recommended to use a custom HTTP header named Foo-Deprecated
to convey deprecation related information. The service MUST respond with the Foo-Deprecated
header in the following cases:
- The caller has used one or more deprecated element in request.
- There is one or more deprecated element in the response.
In order to avoid bloating the responses with static information related to deprecation that does not change from response to response on the same end point, we recommend that API developers provide just an empty JSON object as a value as shown below. In the future, we plan to replace this with something that still does not bloat the responses but still provides enough information so that tools could scan responses for deprecation and take appropriate actions such as notifying App developers/administrators.
"Foo-Deprecated": "{}"
Note: Applications consuming this header MUST not take any action based on the value of the header at this time. Instead, we recommend that these applications SHOULD take action based only on the existence of the header in the response.
References
Please refer to Patterns And Use Cases.