10m RESTful API Guidelines based on Zalando RESTful API and Event Scheme Guidelines
10M GmbH
- 10m RESTful API Guidelines based on Zalando RESTful API and Event Scheme Guidelines
- 1. Introduction
- 2. Principles
- 3. General Guidelines
- 4. Meta Information
- 5. Security
- 6. Compatibility
- Must: Don’t Break Backward Compatibility
- Should: Prefer Compatible Extensions
- Must: Prepare Clients To Not Crash On Compatible API Extensions
- Should: Design APIs Conservatively
- Must: Always Return JSON Objects As Top-Level Data Structures To Support Extensibility
- Must: Treat Open API Definitions As Open For Extension By Default
- Should: Used Open-Ended List of Values (x-extensible-enum) Instead of Enumerations
- Should: Avoid Versioning
- Must: Use Media Type Versioning
- Must: Do Not Use URI Versioning
- 7. Deprecation
- 8. JSON Guidelines
- Must: Property names must be ASCII lowerCamelCase):
^[a-z][a-zA-Z0-9]*$
- Should: Define Maps Using
additionalProperties
- Should: Array names should be pluralized
- Must: Boolean property values must not be null
- Should: Null values should have their fields removed
- Should: Empty array values should not be null
- Should: Enumerations should be represented as Strings
- Should: Date property values should conform to RFC 3339
- May: Time durations and intervals could conform to ISO 8601
- May: Standards could be used for Language, Country and Currency
- Must: Property names must be ASCII lowerCamelCase):
- 9. API Naming
- Must/Should: Use Functional Naming Schema
- Must: Use lowercase separate words for Path Segments
- Must: Use lowerCamelCase for Query Parameters
- Should: Prefer Hyphenated-Pascal-Case for HTTP header Fields
- Must: Pluralize Resource Names
- May: Use /api as first Path Segment
- Must: Avoid Trailing Slashes
- May: Use Conventional Query Strings
- 10. Resources
- Must: Avoid Actions — Think About Resources
- Should: Model complete business processes
- Should: Define useful resources
- Must: Keep URLs Verb-Free
- Must: Use Domain-Specific Resource Names
- Must: Identify resources and Sub-Resources via Path Segments
- Should: Use UUIDs
- May: Consider Using (Non-) Nested URLs
- Should: Limit number of Resource types
- Should: Limit number of Sub-Resource Levels
- 11. HTTP Requests
- 12. HTTP Status Codes And Errors
- 13. Performance
- 14. Pagination
- 15. Hypermedia
- 16. Data Formats
- Must: Use JSON to Encode Structured Data
- May: Use non JSON Media Types for Binary Data or Alternative Content Representations
- Should: Prefer standard Media type name
application/json
- Must: Use Standard Date and Time Formats
- May: Use Standards for Country, Language and Currency Codes
- Must: Define Format for Type Number and Integer
- 17. Common Data Types
- 18. Common Headers
- 19. API Operation
- Appendix A: References
- Appendix B: Tooling
- Appendix C: Best Practices
- Appendix D: Changelog
1. Introduction
10M is not a microservice company. But developing client/server archictures with independent frontend and backend teams require stable API architectures. Out estabilished standard for public APIs that provide functionality via RESTful APIs with a JSON payload. Our engineering teams own, deploy and operate these services in their AWS (team) accounts. Our APIs most purely express what our systems do, and are therefore highly valuable business assets.
With this in mind, we’ve adopted "API First" as one of our key engineering principles. Microservices development begins with API definition outside the code and ideally involves ample peer-review feedback to achieve high-quality APIs. API First encompasses a set of quality-related standards and fosters a peer review culture including a lightweight review procedure. We encourage our teams to follow them to ensure that our APIs:
-
are easy to understand and learn
-
are general and abstracted from specific implementation and use cases
-
are robust and easy to use
-
have a common look and feel
-
follow a consistent RESTful style and syntax
-
are consistent with other teams’ APIs and our global architecture
Ideally, all our APIs will look like the same author created them.
Conventions Used in These Guidelines
The requirement level keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" used in this document (case insensitive) are to be interpreted as described in RFC 2119.
In case guidelines are changing, following rules apply:
-
existing APIs don’t have to be changed, but we recommend it
-
clients of existing APIs have to cope with these APIs based on outdated rules
-
new APIs have to respect the current guidelines
Furthermore you should keep in mind that once an API becomes public externally available, it has to be re-reviewed and changed according to current guidelines - for sake of overall consistency.
2. Principles
API Design Principles
Comparing SOA web service interfacing style of SOAP vs. REST, the former tend to be centered around operations that are usually use-case specific and specialized. In contrast, REST is centered around business (data) entities exposed as resources that are identified via URIs and can be manipulated via standardized CRUD-like methods using different representations, and hypermedia. RESTful APIs tend to be less use-case specific and comes with less rigid client / server coupling and are more suitable for an ecosystem of (core) services providing a platform of APIs to build diverse new business services. We apply the RESTful web service principles to all kind of application (micro-) service components, independently from whether they provide functionality via the internet or intranet.
-
We prefer REST-based APIs with JSON payloads
-
We prefer systems to be truly RESTful [1]
An important principle for API design and usage is Postel’s Law, aka The Robustness Principle (see also RFC 1122):
-
Be liberal in what you accept, be conservative in what you send
Readings: Some interesting reads on the RESTful API design style and service architecture:
-
Book: Irresistable APIs: Designing web APIs that developers will love
-
InfoQ eBook: Web APIs: From Start to Finish
-
Lessons-learned blog: Thoughts on RESTful API Design
-
Fielding Dissertation: Architectural Styles and the Design of Network-Based Software Architectures
API as a Product
10M is no a product company, so we are not selling our APIs. But as our customers are changing their architecture to change into a software driven company APIs are getting more important for a wider scope.
Our projects provide their functionality via (public) APIs; hence, the design of our APIs should be based on the API as a Product principle:
-
Treat your API as product and act like a product owner
-
Put yourself into the place of your customers; be an advocate for their needs
-
Emphasize simplicity, comprehensibility, and usability of APIs to make them irresistible for client engineers
-
Actively improve and maintain API consistency over the long term
-
Make use of customer feedback and provide service level support
Embracing 'API as a Product' facilitates a service ecosystem which can be evolved more easily, and used to experiment quickly with new business ideas by recombining core capabilities. It makes the difference between agile, innovative product service business built on a platform of APIs and ordinary enterprise integration business where APIs are provided as "appendix" of existing products to support system integration and optimised for local server-side realization.
Understand the concrete use cases of your customers and carefully check the trade-offs of your API design variants with a product mindset. Avoid short-term implementation optimizations at the expense of unnecessary client side obligations, and have a high attention on API quality and client developer experience.
API as a Product is closely related to our API First principle (see next chapter) which is more focused on how we engineer high quality APIs.
API First
API First requires two aspects:
-
define APIs first, before coding its implementation, using a standard specification language
-
get early review feedback from peers and client developers
By defining APIs outside the code, we want to facilitate early review feedback and also a development discipline that focus service interface design on…
-
profound understanding of the domain and required functionality
-
generalized business entities / resources, i.e. avoidance of use case specific APIs
-
clear separation of WHAT vs. HOW concerns, i.e. abstraction from implementation aspects — APIs should be stable even if we replace complete service implementation including its underlying technology stack
Moreover, API definitions with standardized specification format also facilitate…
-
single source of truth for the API specification; it is a crucial part of a contract between service provider and client users
-
infrastructure tooling for API discovery, API GUIs, API documents, automated quality checks
Elements of API First are also this API Guidelines and a standardized API review process as to get early review feedback from peers and client developers. Peer review is important for us to get high quality APIs, to enable architectural and design alignment and to supported development of client applications decoupled from service provider engineering life cycle.
It is important to learn, that API First is not in conflict with the agile development principles that we love. Service applications should evolve incrementally — and so its APIs. Of course, our API specification will and should evolve iteratively in different cycles; however, each starting with draft status and early team and peer review feedback. API may change and profit from implementation concerns and automated testing feedback. API evolution during development life cycle may include breaking changes for not yet productive features and as long as we have aligned the changes with the clients. Hence, API First does not mean that you must have 100% domain and requirement understanding and can never produce code before you have defined the complete API and get it confirmed by peer review. On the other hand, API First obviously is in conflict with the bad practice of publishing API definition and asking for peer review after the service integration or even the service productive operation has started. It is crucial to request and get early feedback — as early as possible, but not before the API changes are comprehensive with focus to the next evolution step and have a certain quality (including API Guideline compliance), already confirmed via team internal reviews.
3. General Guidelines
The titles are marked with the corresponding labels: Must:, Should:, May:.
Must: Follow API First Principle
You must follow the API First Principle, more specifically:
-
You must define APIs first, before coding its implementation, using OpenAPI as specification language
-
You must design your APIs consistently with this guidelines; use our API Linter Service [internal link] for automated rule checks.
-
You must call for early review feedback from peers and client developers, and apply our lightweight API review process [internal link] for all component external APIs, i.e. all apis with
x-api-audience =/= component-internal
(see API Audience).
Must: Provide API Specification using OpenAPI
We use the OpenAPI specification (aka Swagger specification) as standard to define API specifications files. API designers have to provide the API specification files using JSON to improve readability. Please, use OpenAPI 3.0 even if there might be still missing tooling support which is in beta.
The API specification files should be subject to version control using a source code management system - best together with the implementing sources.
Should: Provide API User Manual
In addition to the API Specification, it is good practice to provide an API user manual to improve client developer experience, especially of engineers that are less experienced in using this API. A helpful API user manual typically describes the following API aspects:
-
API scope, purpose, and use cases
-
concrete examples of API usage
-
edge cases, error situation details, and repair hints
-
architecture context and major dependencies - including figures and sequence flows
The user manual must be published online, e.g. via our documentation hosting platform service, or specific team web servers. Please do not forget to include a link to the
API user manual into the API specification using the #/externalDocs/url
property.
The manual is generated to build/generated-resources/main/src/index.html
Must: Write APIs in U.S. English with SI unit system
APIs must be written in plain english but must follow the metric system. All units must use a unit based on the SI Unit System if applyable.
4. Meta Information
Must: Contain API Meta Information
API specifications must contain the following OpenAPI meta information to allow for API management:
-
#/info/title
as (unique) identifying, functional descriptive name of the API -
#/info/version
to distinguish API specifications versions following semantic rules -
#/info/description
containing a proper description of the API -
#/info/contact/{name,url,email}
containing the responsible team
Following OpenAPI extension properties may be provided in addition:
-
#/info/x-api-id
unique identifier of the API (see rule 215) -
#/info/x-audience
intended target audience of the API (see rule 219)
Must: Use Semantic Versioning
OpenAPI allows to specify the API specification version in
#/info/version
. To share a common semantic of version information we
expect API designers to comply to
Semantic Versioning 2.0 rules 1
to 8
and 11
restricted to the format
<MAJOR>.<MINOR>.<PATCH> for versions as follows:
-
Increment the MAJOR version when you make incompatible API changes after having aligned this changes with consumers,
-
Increment the MINOR version when you add new functionality in a backwards-compatible manner, and
-
Optionally increment the PATCH version when you make backwards-compatible bug fixes or editorial changes not affecting the functionality.
Additional Notes:
-
Pre-release versions (rule 9) and build metadata (rule 10) must not be used in API version information.
-
While patch versions are useful for fixing typos etc, API designers are free to decide whether they increment it or not.
-
API designers should consider to use API version
0.y.z
(rule 4) for initial API design.
Example:
swagger: '2.0'
info:
title: Parcel Service API
description: API for <...>
version: 1.3.7
<...>
Must: Provide API Identifiers
Each API specification must be provisioned with a globally unique and immutable API identifier.
The API identifier is defined in the info
-block of the OpenAPI
specification and must conform to the following definition:
/info/x-api-id:
type: string
format: urn
pattern: ^[a-z0-9][a-z0-9-:.]{6,62}[a-z0-9]$
description: |
Mandatory globally unique and immutable API identifier. The API
id allows to track the evolution and history of an API specification
as a sequence of versions.
API specifications will evolve and any aspect of an OpenAPI specification may change. We require API identifiers because we want to support API clients and providers with API lifecycle management features, like change trackability and history or automated backward compatibility checks. The immutable API identifier allows the identification of all API specification versions of an API evolution. By using API semantic version information or API publishing date as order criteria you get the version or publication history as a sequence of API specifications.
Note: While it is nice to use human readable API identifiers based on self-managed URNs, it is recommend to stick to UUIDs to relief API designers from any urge of changing the API identifier while evolving the API. Example:
swagger: '2.0'
info:
x-api-id: d0184f38-b98d-11e7-9c56-68f728c1ba70
title: Parcel Service API
description: API for <...>
version: 1.5.8
<...>
Must: Provide API Audience
Each API must be classified with respect to the intended target audience supposed to consume the API, to facilitate differentiated standards on APIs for discoverability, changeability, quality of design and documentation, as well as permission granting. We differentiate the following API audience groups with clear organisational and legal boundaries:
- component-internal
-
This is often referred to as a team internal API or a product internal API. The API consumers with this audience are restricted to applications of the same functional component which typically represents a specific product with clear functional scope and ownership. All services of a functional component / product are owned by a specific dedicated owner and engineering team(s). Typical examples of component-internal APIs are APIs being used by internal helper and worker services or that support service operation.
- business-unit-internal
-
The API consumers with this audience are restricted to applications of a specific product portfolio owned by the same business unit.
- company-internal
-
The API consumers with this audience are restricted to applications owned by the business units of the same the company (e.g. the API is not published by the customer as part of a service platform. etc.)
- external-partner
-
The API consumers with this audience are restricted to applications of business partners of the company owning the API and the company itself.
- external-public
-
APIs with this audience can be accessed by anyone with Internet access.
Note: a smaller audience group is intentionally included in the wider group and thus does not need to be declared additionally.
The API audience is provided as API meta information in the info
-block of
the Open API specification and must conform to the following specification:
#/info/x-audience:
type: string
x-extensible-enum:
- component-internal
- business-unit-internal
- company-internal
- external-partner
- external-public
description: |
Intended target audience of the API. Relevant for standards around
quality of design and documentation, reviews, discoverability,
changeability, and permission granting.
Note: Exactly one audience per API specification is allowed. For this reason a smaller audience group is intentionally included in the wider group and thus does not need to be declared additionally. If parts of your API have a different target audience, we recommend to split API specifications along the target audience — even if this creates redundancies (rationale).
Example:
swagger: '2.0'
info:
x-audience: company-internal
title: Parcel Helper Service API
description: API for <...>
version: 1.2.4
<...>
5. Security
Must: Secure Endpoints with JWT
Every API endpoint needs to be secured using JWT. The token is sent in the HTTP Authorization Header with the preceding word Bearer.
Authorization: Bearer <JWT>
6. Compatibility
Must: Don’t Break Backward Compatibility
Change APIs, but keep all consumers running. Consumers usually have independent release lifecycles, focus on stability, and avoid changes that do not provide additional value. APIs are contracts between service providers and service consumers that cannot be broken via unilateral decisions.
There are two techniques to change APIs without breaking them:
-
follow rules for compatible extensions
-
introduce new API versions and still support older versions
We strongly encourage using compatible API extensions and discourage versioning (see rules 113,114 below). The following guidelines for service providers (rule 107) and consumers (rule 108) enable us (having Postel’s Law in mind) to make compatible changes without versioning.
Note: There is a difference between incompatible and breaking changes. Incompatible changes are changes that are not covered by the compatibility rules below. Breaking changes are incompatible changes deployed into operation, and thereby breaking running API consumers. Usually, incompatible changes are breaking changes when deployed into operation. However, in specific controlled situations it is possible to deploy incompatible changes in a non-breaking way, if no API consumer is using the affected API aspects (see also Deprecation guidelines).
Hint: Please note that the compatibility guarantees are for the "on the wire" format. Binary or source compatibility of code generated from an API definition is not covered by these rules. If client implementations update their generation process to a new version of the API definition, it has to be expected that code changes are necessary.
Should: Prefer Compatible Extensions
API designers should apply the following rules to evolve RESTful APIs for services in a backward-compatible way:
-
Add only optional, never mandatory fields.
-
Never change the semantic of fields (e.g. changing the semantic from customer-number to customer-id, as both are different unique customer keys)
-
Input fields may have (complex) constraints being validated via server-side business logic. Never change the validation logic to be more restrictive and make sure that all constraints are clearly defined in description.
-
Enum ranges can be reduced when used as input parameters, only if the server is ready to accept and handle old range values too. Enum range can be reduced when used as output parameters.
-
Enum ranges cannot not be extended when used for output parameters — clients may not be prepared to handle it. However, enum ranges can be extended when used for input parameters.
-
Use
x-extensible-enum
, if range is used for output parameters and likely to be extended with growing functionality. It defines an open list of explicit values and clients must be agnostic to new values. -
Support redirection in case an URL has to change (301 Moved Permanently).
Must: Prepare Clients To Not Crash On Compatible API Extensions
Service clients should apply the robustness principle:
-
Be conservative with API requests and data passed as input, e.g. avoid to exploit definition deficits like passing megabytes for strings with unspecified maximum length.
-
Be tolerant in processing and reading data of API responses, more specifically…
Service clients must be prepared for compatible API extensions of service providers:
-
Be tolerant with unknown fields in the payload (see also Fowler’s "TolerantReader" post), i.e. ignore new fields but do not eliminate them from payload if needed for subsequent PUT requests.
-
Be prepared that
x-extensible-enum
return parameter may deliver new values; either be agnostic or provide default behavior for unknown values. -
Be prepared to handle HTTP status codes not explicitly specified in endpoint definitions. Note also, that status codes are extensible. Default handling is how you would treat the corresponding x00 code (see RFC7231 Section 6).
-
Follow the redirect when the server returns HTTP status 301 Moved Permanently.
Should: Design APIs Conservatively
Designers of service provider APIs should be conservative and accurate in what they accept from clients:
-
Unknown input fields in payload or URL should not be ignored; servers should provide error feedback to clients via an HTTP 400 response code.
-
Be accurate in defining input data constraints (like formats, ranges, lengths etc.) — and check constraints and return dedicated error information in case of violations.
-
Prefer being more specific and restrictive (if compliant to functional requirements), e.g. by defining length range of strings. It may simplify implementation while providing freedom for further evolution as compatible extensions.
Not ignoring unknown input fields is a specific deviation from Postel’s
Law (e.g. see also
The
Robustness Principle Reconsidered) and a strong recommendation. Servers
might want to take different approach but should be aware of the
following problems and be explicit in what is supported:
-
Ignoring unknown input fields is actually not an option for PUT, since it becomes asymmetric with subsequent GET response and HTTP is clear about the PUT "replace" semantics and default roundtrip expectations (see RFC7231 Section 4.3.4). Note, accepting (i.e. not ignoring) unknown input fields and returning it in subsequent GET responses is a different situation and compliant to PUT semantics.
-
Certain client errors cannot be recognized by servers, e.g. attribute name typing errors will be ignored without server error feedback. The server cannot differentiate between the client intentionally providing an additional field versus the client sending a mistakenly named field, when the client’s actual intent was to provide an optional input field.
-
Future extensions of the input data structure might be in conflict with already ignored fields and, hence, will not be compatible, i.e. break clients that already use this field but with different type.
In specific situations, where a (known) input field is not needed anymore, it either can stay in the API definition with "not used anymore" description or can be removed from the API definition as long as the server ignores this specific parameter.
Must: Always Return JSON Objects As Top-Level Data Structures To Support Extensibility
In a response body, you must always return a JSON object (and not e.g. an array) as a top level data structure to support future extensibility. JSON objects support compatible extension by additional attributes. This allows you to easily extend your response and e.g. add pagination later, without breaking backwards compatibility.
Maps, see rule 216, even though technically objects are also forbidden as top level data structures, since they don’t support compatible, future extensions.
Must: Treat Open API Definitions As Open For Extension By Default
The Open API specification is not very specific on default
extensibility of objects, and redefines JSON-Schema keywords related to
extensibility, like additionalProperties
. Following our overall
compatibility guidelines, Open API object definitions are considered
open for extension by default as per
Section
5.18 "additionalProperties" of JSON-Schema.
When it comes to Open API 2.0, this means an additionalProperties
declaration is not required to make an object definition extensible:
-
API clients consuming data must not assume that objects are closed for extension in the absence of an
additionalProperties
declaration and must ignore fields sent by the server they cannot process. This allows API servers to evolve their data formats. -
For API servers receiving unexpected data, the situation is slightly different. Instead of ignoring fields, servers may reject requests whose entities contain undefined fields in order to signal to clients that those fields would not be stored on behalf of the client. API designers must document clearly how unexpected fields are handled for PUT, POST and PATCH requests.
API formats must not declare additionalProperties
to be false, as this
prevents objects being extended in the future.
Note that this guideline concentrates on default extensibility and does
not exclude the use of additionalProperties
with a schema as a value,
which might be appropriate in some circumstances, e.g. see Should: Define Maps Using additionalProperties
.
Should: Used Open-Ended List of Values (x-extensible-enum) Instead of Enumerations
Enumerations are per definition closed sets of values, that are assumed to be complete and not intended for extension. This closed principle of enumerations imposes compatibility issues when an enumeration must be extended. To avoid these issues, we strongly recommend to use an open-ended list of values instead of an enumeration unless:
-
the API has full control of the enumeration values, i.e. the list of values does not depend on any external tool or interface, and
-
the list of value is complete with respect to any thinkable and unthinkable future feature.
To specify an open-ended list of values use the marker
x-extensible-enum
as follows:
deliver_methods:
type: string
x-extensible-enum:
- parcel
- letter
- email
Note: x-extensible-enum
is not JSON Schema conform but will be
ignored by most tools.
Should: Avoid Versioning
When changing your RESTful APIs, do so in a compatible way and avoid generating additional API versions. Multiple versions can significantly complicate understanding, testing, maintaining, evolving, operating and releasing our systems (supplementary reading).
If changing an API can’t be done in a compatible way, then proceed in one of these three ways:
-
create a new resource (variant) in addition to the old resource variant
-
create a new service endpoint — i.e. a new application with a new API (with a new domain name)
-
create a new API version supported in parallel with the old API by the same microservice
As we discourage versioning by all means because of the manifold disadvantages, we strongly recommend to only use the first two approaches.
Must: Use Media Type Versioning
However, when API versioning is unavoidable, you have to design your multi-version RESTful APIs using media type versioning (instead of URI versioning, see below). Media type versioning is less tightly coupled since it supports content negotiation and hence reduces complexity of release management.
Media type versioning: Here, version information and media type are provided together via the HTTP Content-Type header — e.g. application/x.zalando.cart+json;version=2. For incompatible changes, a new media type version for the resource is created. To generate the new representation version, consumer and producer can do content negotiation using the HTTP Content-Type and Accept headers. Note: This versioning only applies to the request and response content schema, not to URI or method semantics.
In this example, a client wants only the new version of the response:
Accept: application/x.zalando.cart+json;version=2
A server responding to this, as well as a client sending a request with content should use the Content-Type header, declaring that one is sending the new version:
Content-Type: application/x.zalando.cart+json;version=2
Using header versioning should:
-
include versions in request and response headers to increase visibility
-
include Content-Type in the Vary header to enable proxy caches to differ between versions
Hint: Until an incompatible change is necessary, it is recommended to stay
with the standard application/json
media type.
Hint: OpenAPI currently doesn’t support content negotiation, though a comment in this issue mentions a workaround (using a fragment identifier that gets stripped off). Another way would be to document just the new version, but let the server accept the old one (with the previous content-type).
Further reading: API Versioning Has No "Right Way" provides an overview on different versioning approaches to handle breaking changes without being opinionated.
Must: Do Not Use URI Versioning
With URI versioning a (major) version number is included in the path, e.g. /v1/customers. The consumer has to wait until the provider has been released and deployed. If the consumer also supports hypermedia links — even in their APIs — to drive workflows (HATEOAS), this quickly becomes complex. So does coordinating version upgrades — especially with hyperlinked service dependencies — when using URL versioning. To avoid this tighter coupling and complexer release management we do not use URI versioning, and go instead with media type versioning and content negotiation (see above).
7. Deprecation
Sometimes it is necessary to phase out an API endpoint (or version), for instance, if a field is no longer supported in the result or a whole business functionality behind an endpoint has to be shut down. There are many other reasons as well. As long as these endpoints are still used by consumers these are breaking changes and not allowed. Deprecation rules have to be applied to make sure that necessary consumer changes are aligned and deprecated endpoints are not used before API changes are deployed.
Must: Obtain Approval of Clients
Before shutting down an API (or version of an API) the producer must make sure, that all clients have given their consent to shut down the endpoint. Producers should help consumers to migrate to a potential new endpoint (i.e. by providing a migration manual). After all clients are migrated, the producer may shut down the deprecated API.
Must: External Partners Must Agree on Deprecation Timespan
If the API is consumed by any external partner, the producer must define a reasonable timespan that the API will be maintained after the producer has announced deprecation. The external partner (client) must agree to this minimum after-deprecation-lifespan before he starts using the API.
Must: Reflect Deprecation in API Definition
API deprecation must be part of the OpenAPI definition. If a method on a
path, a whole path or even a whole API endpoint (multiple paths) should
be deprecated, the producers must set deprecated=true
on each method /
path element that will be deprecated (OpenAPI 2.0 only allows you to
define deprecation on this level). If deprecation should happen on a
more fine grained level (i.e. query parameter, payload etc.), the
producer should set deprecated=true
on the affected method / path
element and add further explanation to the description
section.
If deprecated
is set to true
, the producer must describe what
clients should use instead and when the API will be shut down in the
description
section of the API definition.
Must: Monitor Usage of Deprecated APIs
Owners of APIs used in production must monitor usage of deprecated APIs until the API can be shut down in order to align deprecation and avoid uncontrolled breaking effects. See also the Should: Monitor API Usage.
Should: Add a Warning Header to Responses
During deprecation phase, the producer should add a Warning
header
(see RFC 7234 - Warning
header) field. When adding the Warning
header, the warn-code
must
be 299
and the warn-text
should be in form of "The
path/operation/parameter/… {name} is deprecated and will be removed
by {date}. Please see {link} for details." with a link to a
documentation describing why the API is no longer supported in the
current form and what clients should do about it. Adding the Warning
header is not sufficient to gain client consent to shut down an API.
Should: Add Monitoring for Warning Header
Clients should monitor the Warning
header in HTTP responses to see if
an API will be deprecated in future.
Must: Not Start Using Deprecated APIs
Clients must not start using deprecated parts of an API.
8. JSON Guidelines
These guidelines provides recommendations for defining JSON data at 10M. JSON here refers to RFC 7159 (which updates RFC 4627), the "application/json" media type and custom JSON media types defined for APIs. The guidelines clarifies some specific cases to allow 10M JSON data to have an idiomatic form across teams and services.
The first some of the following guidelines are about property names, the later ones about values.
Must: Property names must be ASCII lowerCamelCase): ^[a-z][a-zA-Z0-9]*$
Property names are restricted to ASCII strings. The first character must be a letter, or an underscore, and subsequent characters can be a letter, or a number.
(It is recommended to use _
at the start of property names only for keywords like _links
.)
Rationale: No established industry standard exists, but many popular Internet companies prefer snake_case: e.g. GitHub, Stack Exchange, Twitter. Others, like Google and Amazon, use both - but not only camelCase. It’s essential to establish a consistent look and feel such that JSON looks as if it came from the same hand.
Should: Define Maps Using additionalProperties
A "map" here is a mapping from string keys to some other type. In JSON this is represented as an object, the key-value pairs being represented by property names and property values. In OpenAPI schema (as well as in JSON schema) they should be represented using additionalProperties with a schema defining the value type. Such an object should normally have no other defined properties.
The map keys don’t count as property names in the sense of rule 118, and can follow whatever format is natural for their domain. Please document this in the description of the map object’s schema.
Here is an example for such a map definition (the translations
property):
definitions:
Message:
description:
A message together with translations in several languages.
type: object
properties:
message_key:
type: string
description: The message key.
translations:
description:
The translations of this message into several languages.
The keys are https://tools.ietf.org/html/bcp47[BCP-47 language tags].
type: object
additionalProperties:
type: string
description:
the translation of this message into the language identified by the key.
An actual JSON object described by this might then look like this:
{ "messageKey": "color",
"translations": {
"de": "Farbe",
"en-US": "color",
"en-GB": "colour",
"eo": "koloro",
"nl": "kleur"
}
}
Should: Array names should be pluralized
To indicate they contain multiple values prefer to pluralize array names. This implies that object names should in turn be singular.
Must: Boolean property values must not be null
Schema based JSON properties that are by design booleans must not be presented as nulls. A boolean is essentially a closed enumeration of two values, true and false. If the content has a meaningful null value, strongly prefer to replace the boolean with enumeration of named values or statuses - for example accepted_terms_and_conditions with true or false can be replaced with terms_and_conditions with values yes, no and unknown.
Should: Null values should have their fields removed
Swagger/OpenAPI, which is in common use, doesn’t support null field values (it does allow omitting that field completely if it is not marked as required). However that doesn’t prevent clients and servers sending and receiving those fields with null values. Also, in some cases null may be a meaningful value - for example, JSON Merge Patch RFC 7382) using null to indicate property deletion.
Should: Empty array values should not be null
Empty array values can unambiguously be represented as the empty
list, []
.
Should: Enumerations should be represented as Strings
Strings are a reasonable target for values that are by design enumerations.
Should: Date property values should conform to RFC 3339
Use the date and time formats defined by RFC 3339:
-
for "date" use strings matching
date-fullyear "-" date-month "-" date-mday
, for example:2015-05-28
-
for "date-time" use strings matching
full-date "T" full-time
, for example2015-05-28T14:07:17Z
Note that the
OpenAPI
format "date-time" corresponds to "date-time" in the RFC) and
2015-05-28
for a date (note that the OpenAPI format "date" corresponds
to "full-date" in the RFC). Both are specific profiles, a subset of the
international standard ISO 8601.
A zone offset may be used (both, in request and responses) — this is
simply defined by the standards. However, we encourage restricting dates
to UTC and without offsets. For example 2015-05-28T14:07:17Z
rather
than 2015-05-28T14:07:17+00:00
. From experience we have learned that
zone offsets are not easy to understand and often not correctly handled.
Note also that zone offsets are different from local times that might be
including daylight saving time. Localization of dates should be done by
the services that provide user interfaces, if required.
When it comes to storage, all dates should be consistently stored in UTC without a zone offset. Localization should be done locally by the services that provide user interfaces, if required.
Sometimes it can seem data is naturally represented using numerical timestamps, but this can introduce interpretation issues with precision - for example whether to represent a timestamp as 1460062925, 1460062925000 or 1460062925.000. Date strings, though more verbose and requiring more effort to parse, avoid this ambiguity.
May: Time durations and intervals could conform to ISO 8601
Schema based JSON properties that are by design durations and intervals could be strings formatted as recommended by ISO 8601 (Appendix A of RFC 3339 contains a grammar for durations).
May: Standards could be used for Language, Country and Currency
-
(It’s "GB", not "UK", even though "UK" has seen some use at Zalando)
-
BCP-47 (based on ISO 639-1) for language variants
9. API Naming
Must/Should: Use Functional Naming Schema
Functional naming is a powerful, yet easy way to align global resources as host, permission, and event names within an the application landscape. It helps to preserve uniqueness of names while giving readers meaningful context information about the addressed component. Besides, the most important aspect is, that it allows to keep APIs stable in the case of technical and organizational changes (Zalando for example maintains an internal naming convention).
To make use of this advantages for APIs with a larger audience we strongly recommended to follow the functional naming schema for hostnames, permission names, and event names in APIs as follows:
Functional Naming |
Audience |
must |
external-public, external-partner |
should |
company-internal, business-unit-internal |
may |
component-internal |
To conduct the functional naming schema, a unique functional-name
is assigned
to each functional component. It is built of the domain name of the functional
group the component is belonging to and a unique a short identifier for the
functional component itself:
<functional-name> ::= <functional-domain>-<functional-component>
<functional-domain> ::= [a-z][a-z0-9]* -- managed functional group of components
<functional-component> ::= [a-z][a-z0-9-]* -- name of owning functional component
Internal Hint: Use the simple functional name registry (internal link) to register your functional name before using it. The registry is a centralized infrastructure service to ensure uniqueness of your functional names (and available domains) and to support hostname DNS resolution.
Please see the following rules for detailed functional naming patterns:
Must: Use lowercase separate words for Path Segments
Example:
/crop-order/{cropOrderId}
This applies to concrete path segments and not the names of path
parameters. For example {cropOrderId}
would be ok as a path
parameter.
Should: Prefer Hyphenated-Pascal-Case for HTTP header Fields
This is for consistency in your documentation (most other headers follow this convention). Avoid camelCase (without hyphens). Exceptions are common abbreviations like "ID."
Examples:
Accept-Encoding
Apply-To-Redirect-Ref
Disposition-Notification-Options
Original-Message-ID
See also: HTTP Headers are case-insensitive (RFC 7230).
See Common Headers sections for more guidance on HTTP headers.
Must: Pluralize Resource Names
Usually, a collection of resource instances is provided (at least API should be ready here). The special case of a resource singleton is a collection with cardinality 1.
May: Use /api as first Path Segment
In most cases, all resources provided by a service are part of the public API, and therefore should be made available under the root "/" base path. If the service should also support non-public, internal APIs — for specific operational support functions, for example — add "/api" as base path to clearly separate public and non-public API resources.
Must: Avoid Trailing Slashes
The trailing slash must not have specific semantics. Resource paths must deliver the same results whether they have the trailing slash or not.
May: Use Conventional Query Strings
If you provide query support for sorting, pagination, filtering functions or other actions, use the following standardized naming conventions:
-
q
— default query parameter (e.g. used by browser tab completion); should have an entity specific alias, like sku -
limit
— to restrict the number of entries. See Pagination section below. Hint: You can use size as an alternate query string. -
cursor
— key-based page start. See Pagination section below. -
offset
— numeric offset page start. See Pagination section below. Hint: In combination with limit, you can use page as an alternative to offset. -
sort
— comma-separated list of fields to sort. To indicate sorting direction, fields may be prefixed with + (ascending) or - (descending, default), e.g. /sales-orders?sort=+id -
fields
— to retrieve a subset of fields. See Support Filtering of Resource Fields below. -
embed
— to expand embedded entities (ie.: inside of an article entity, expand silhouette code into the silhouette object). Implementing "expand" correctly is difficult, so do it with care.
10. Resources
Must: Avoid Actions — Think About Resources
REST is all about your resources, so consider the domain entities that take part in web service interaction, and aim to model your API around these using the standard HTTP methods as operation indicators. For instance, if an application has to lock articles explicitly so that only one user may edit them, create an article lock with PUT or POST instead of using a lock action.
Request:
PUT /article-locks/{article-id}
The added benefit is that you already have a service for browsing and filtering article locks.
Should: Model complete business processes
An API should contain the complete business processes containing all resources representing the process. This enables clients to understand the business process, foster a consistent design of the business process, allow for synergies from description and implementation perspective, and eliminates implicit invisible dependencies between APIs.
In addition, it prevents services from being designed as thin wrappers around databases, which normally tends to shift business logic to the clients.
Should: Define useful resources
As a rule of thumb resources should be defined to cover 90% of all its client’s use cases. A useful resource should contain as much information as necessary, but as little as possible. A great way to support the last 10% is to allow clients to specify their needs for more/less information by supporting filtering and embedding.
Must: Keep URLs Verb-Free
The API describes resources, so the only place where actions should appear is in the HTTP methods. In URLs, use only nouns. Instead of thinking of actions (verbs), it’s often helpful to think about putting a message in a letter box: e.g., instead of having the verb cancel in the url, think of sending a message to cancel an order to the cancellations letter box on the server side.
Must: Use Domain-Specific Resource Names
API resources represent elements of the application’s domain model. Using domain-specific nomenclature for resource names helps developers to understand the functionality and basic semantics of your resources. It also reduces the need for further documentation outside the API definition. For example, "sales-order-items" is superior to "order-items" in that it clearly indicates which business object it represents. Along these lines, "items" is too general.
Must: Identify resources and Sub-Resources via Path Segments
Some API resources may contain or reference sub-resources. Embedded sub-resources, which are not top-level resources, are parts of a higher-level resource and cannot be used outside of its scope. Sub-resources should be referenced by their name and identifier in the path segments.
Composite identifiers must not contain /
as a separator. In order to
improve the consumer experience, you should aim for intuitively
understandable URLs, where each sub-path is a valid reference to a
resource or a set of resources. For example, if
/customers/12ev123bv12v/addresses/DE_100100101
is a valid path of your
API, then /customers/12ev123bv12v/addresses
, /customers/12ev123bv12v
and /customers
must be valid as well in principle.
Basic URL structure:
/{resources}/[resource-id]/{sub-resources}/[sub-resource-id]
/{resources}/[partial-id-1][separator][partial-id-2]
Examples:
/carts/1681e6b88ec1/items
/carts/1681e6b88ec1/items/1
/customers/12ev123bv12v/addresses/DE_100100101
/content/images/9cacb4d8
Should: Use UUIDs
Generating IDs can be a scaling problem in high frequency and near real time use cases. UUIDs solve this problem, as they can be generated without collisions in a distributed, non-coordinated way and without additional server round trips.
May: Consider Using (Non-) Nested URLs
If a sub-resource is only accessible via its parent resource and may not exists without parent resource, consider using a nested URL structure, for instance:
/carts/1681e6b88ec1/cart-items/1
However, if the resource can be accessed directly via its unique id, then the API should expose it as a top-level resource. For example, customer has a collection for sales orders; however, sales orders have globally unique id and some services may choose to access the orders directly, for instance:
/customers/1681e6b88ec1
/sales-orders/5273gh3k525a
Should: Limit number of Resource types
To keep maintenance and service evolution manageable, we should follow "functional segmentation" and "separation of concern" design principles and do not mix different business functionalities in same API definition. In practice this means that the number of resource types exposed via an API should be limited. In this context a resource type is defined as a set of highly related resources such as a collection, its members and any direct sub-resources.
For example, the resources below would be counted as three resource types, one for customers, one for the addresses, and one for the customers' related addresses:
/customers
/customers/{id}
/customers/{id}/preferences
/customers/{id}/addresses
/customers/{id}/addresses/{addr}
/addresses
/addresses/{addr}
Note that:
-
We consider
/customers/{id}/preferences
part of the/customers
resource type because it has a one-to-one relation to the customer without an additional identifier. -
We consider
/customers
and/customers/{id}/addresses
as separate resource types because/customers/{id}/addresses/{addr}
also exists with an additional identifier for the address. -
We consider
/addresses
and/customers/{id}/addresses
as separate resource types because there’s no reliable way to be sure they are the same.
Given this definition, our experience is that well defined APIs involve no more than 4 to 8 resource types. There may be exceptions with more complex business domains that require more resources, but you should first check if you can split them into separate subdomains with distinct APIs.
Nevertheless one API should hold all necessary resources to model complete business processes helping clients to understand these flows.
Should: Limit number of Sub-Resource Levels
There are main resources (with root url paths) and sub-resources (or "nested" resources with non-root urls paths). Use sub-resources if their life cycle is (loosely) coupled to the main resource, i.e. the main resource works as collection resource of the subresource entities. You should use ⇐ 3 sub-resource (nesting) levels — more levels increase API complexity and url path length. (Remember, some popular web browsers do not support URLs of more than 2000 characters)
11. HTTP Requests
Must: Use HTTP Methods Correctly
Be compliant with the standardized HTTP method semantics summarized as follows:
GET
GET requests are used to read either a single or a collection resource.
-
GET requests for individual resources will usually generate a 404 if the resource does not exist
-
GET requests for collection resources may return either 200 (if the collection is empty) or 404 (if the collection is missing)
-
GET requests must NOT have a request body payload
Note: GET requests on collection resources should provide sufficient #137 and Pagination mechanisms.
GET with Body
APIs sometimes face the problem, that they have to provide extensive structured request information with GET, that may conflicts with the size limits of clients, load-balancers, and servers. As we require APIs to be standard conform (body in GET must be ignored on server side), API designers have to check the following two options:
-
GET with URL encoded query parameters: when it is possible to encode the request information in query parameters, respecting the usual size limits of clients, gateways, and servers, this should be the first choice. The request information can either be provided distributed to multiple query parameters or a single structured URL encoded string.
-
POST with body content: when a GET with URL encoded query parameters is not possible, a POST with body content must be used. In this case the endpoint must be documented with the hint
GET with body
to transport the GET semantic of this call.
Note: It is no option to encode the lengthy structured request information in header parameters. From a conceptual point of view, the semantic of an operation should always be expressed by resource name and query parameters, i.e. what goes into the URL. Request headers are reserved for general context information, e.g. FlowIDs. In addition, size limits on query parameters and headers are not reliable and depend on clients, gateways, server, and actual settings. Thus, switching to headers does not solve the original problem.
PUT
PUT requests are used to update (in rare cases to create) entire resources - single or collection resources. The semantic is best described as "please put the enclosed representation at the resource mentioned by the URL, replacing any existing resource.".
-
PUT requests are usually applied to single resources, and not to collection resources, as this would imply replacing the entire collection
-
PUT requests are usually robust against non-existence of resources by implicitly creating before updating
-
on successful PUT requests, the server will replace the entire resource addressed by the URL with the representation passed in the payload (subsequent reads will deliver the same payload)
-
successful PUT requests will usually generate 200 or 204 (if the resource was updated - with or without actual content returned), and 201 (if the resource was created)
Important: It is best practice to prefer POST over PUT for creation of (at least top-level) resources. This leaves the resource ID under control of the service and allows to concentrate on the update semantic using PUT as follows.
Tip: To prevent unnoticed concurrent updates when using PUT, the combination
of ETag
and If-(None-)Match
headers should be considered to signal
the server stricter demands to expose conflicts and prevent lost updates.
The section Optimistic Locking in RESTful APIs also describes some alternatives to this
approach.
Note: In the rare cases where PUT is although used for resource creation, the resource IDs are maintained by the client and passed as a URL path segment. Putting the same resource twice is required to be idempotent and to result in the same single resource instance. If PUT is applied for creating a resource, only URIs should be allowed as resource IDs. If URIs are not available POST should be preferred.
POST
POST requests are idiomatically used to create single resources on a collection resource endpoint, but other semantics on single resources endpoint are equally possible. The semantic for collection endpoints is best described as "please add the enclosed representation to the collection resource identified by the URL".
-
on a successful POST request, the server will create one or multiple new resources and provide their URI/URLs in the response
-
successful POST requests will usually generate 200 (if resources have been updated), 201 (if resources have been created), and 202 (if the request was accepted but has not been finished yet)
The semantic for single resource endpoints is best described as "please execute the given well specified request on the resource identified by the URL".
Generally: POST should be used for scenarios that cannot be covered by the other methods sufficiently. In such cases, make sure to document the fact that POST is used as a workaround (see GET with Body).
Note: Resource IDs with respect to POST requests are created and maintained by server and returned with response payload.
Tip: Posting the same resource twice is by itself not required to be idempotent and may result in multiple resource instances. However, it is best practice to implement POST idempotent, by temporary or permanently storing foreign keys or idempotency keys to identify re-send requests on the server. Here, a foreign key is a natural partner resource ID, e.g. the shopping cart ID is a foreign key candidate for an order, while an idempotency key is an artificial request key that is preserved when re-sending the POST request.
PATCH
PATCH requests are used to update parts of single resources, i.e. where only a specific subset of resource fields should be replaced. The semantic is best described as "please change the resource identified by the URL according to my change request". The semantic of the change request is not defined in the HTTP standard and must be described in the API specification by using suitable media types.
-
PATCH requests are usually applied to single resources as patching entire collection is challenging
-
PATCH requests are usually not robust against non-existence of resource instances
-
on successful PATCH requests, the server will update parts of the resource addressed by the URL as defined by the change request in the payload
-
successful PATCH requests will usually generate 200 or 204 (if resources have been updated with or without updated content returned)
Note: since implementing PATCH correctly is a bit tricky, we strongly suggest to choose one and only one of the following patterns per endpoint, unless forced by a backwards compatible change. In preference order:
-
use PUT with complete objects to update a resource as long as feasible (i.e. do not use PATCH at all).
-
use PATCH with partial objects to only update parts of a resource, whenever possible. (This is basically JSON Merge Patch, a specialized media type
application/merge-patch+json
that is a partial resource representation.) -
use PATCH with JSON Patch, a specialized media type
application/json-patch+json
that includes instructions on how to change the resource. -
use POST (with a proper description of what is happening) instead of PATCH, if the request does not modify the resource in a way defined by the semantics of the media type.
In practice JSON Merge Patch quickly turns out to be too limited, especially when trying to update single objects in large collections (as part of the resource). In this cases JSON Patch can shown its full power while still showing readable patch requests (see also JSON patch vs. merge).
Tip: To prevent unnoticed concurrent updates when using PATCH, the
combination of ETag`and `If-Match
headers should be considered to
signal the server stricter demands to expose conflicts and prevent lost
updates.
DELETE
DELETE requests are used to delete resources. The semantic is best described as "please delete the resource identified by the URL".
-
DELETE requests are usually applied to single resources, not on collection resources, as this would imply deleting the entire collection
-
successful DELETE requests will usually generate 200 (if the deleted resource is returned) or 204 (if no content is returned)
-
failed DELETE requests will usually generate 404 (if the resource cannot be found) or 410 (if the resource was already deleted before)
Important: After deleting a resource with DELETE, a GET request on the resource is expected to either return 404 (not found) or 410 (gone) depending on how the resource is represented after deletion. Under no circumstances the resource must be accessible after this operation on its endpoint.
HEAD
HEAD requests are used to retrieve the header information of single resources and resource collections.
-
HEAD has exactly the same semantics as GET, but returns headers only, no body.
Hint: This is particular useful to efficiently lookup whether large resources
or collection resources have been updated in conjunction with the
ETag
-header.
OPTIONS
OPTIONS requests are used to inspect the available operations (HTTP methods) of a given endpoint.
-
OPTIONS responses usually either return a comma separated list of methods in the
Allow
header or as a structured list of link templates
Note: OPTIONS is rarely implemented, though it could be used to self-describe the full functionality of a resource.
Must: Fulfill Safeness and Idempotency Properties
An operation can be…
-
idempotent, i.e. operation will have the same effect on the server’s state if executed once or multiple times (note: this does not necessarily mean returning the same response or status code)
-
safe, i.e. must not have side effects such as state changes
Method implementations must fulfill the following basic properties:
HTTP method | safe | idempotent |
---|---|---|
OPTIONS |
Yes |
Yes |
HEAD |
Yes |
Yes |
GET |
Yes |
Yes |
PUT |
No |
Yes |
POST |
No |
No |
DELETE |
No |
Yes |
PATCH |
No |
No |
Please see also Best Practices [internal link] for further hints on how to support the different HTTP methods on resources.
Should: Define Collection Format of Query Parameters and Headers
Sometimes, query parameters and headers allow to provide a list of values,
either by providing a comma-separated list (csv
) or by repeating the
parameter multiple times with different values (multi
). The API
specification should explicitly define one type as follows:
Description | OpenAPI 3.0 | OpenAPI 2.0 | Example |
---|---|---|---|
Comma separated values |
|
|
|
Multiple parameters |
|
|
|
When choosing the collection format, take into account the tool support, the escaping of special characters and the maximal URL length.
Must: Document Implicit Filtering
Sometimes certain collection resources or queries will not list all the possible elements they have, but only those for which the current client is authorized to access.
Implicit filtering could be done on:
-
the collection of resources being return on a parent
GET
request -
the fields returned for the resource’s detail
In such cases, the implicit filtering must be in the API Specification (in its description).
Consider caching considerations when implicitely filtering.
Example:
If an employee of the company Foo accesses one of our business-to-business
service and performs a GET /business-partners
, it must, for legal reasons, not display
any other business partner that is not owned or contractually managed by her/his company.
It should never see that we are doing business also with company Bar.
Response as seen from a consumer working at FOO
:
{
"items": [
{ "name": "Foo Performance" },
{ "name": "Foo Sport" },
{ "name": "Foo Signature" }
]
}
Response as seen from a consumer working at BAR
:
{
"items": [
{ "name": "Bar Classics" },
{ "name": "Bar pour Elle" }
]
}
The API Specification should then specify something like this:
/business-partner:
get:
description: >-
Get the list of registered business partner.
Only the business partners to which you have access to are returned.
12. HTTP Status Codes And Errors
Must: Specify Success and Error Responses
APIs should define the functional, business view and abstract from implementation aspects. Success and error responses are a vital part to define how an API is used correctly.
Therefore, you must define all success and service specific error responses in your API specification. Both are part of the interface definition and provide important information for service clients to handle standard as well as exceptional situations.
Hint: In most cases it is not useful to document all technical errors, especially if they are not under control of the service provider. Thus unless a response code conveys application-specific functional semantics or is used in a none standard way that requires additional explanation, multiple error response specifications can be combined using the following pattern:
responses:
...
default:
description: error occurred - see status code and problem object for more information.
schema:
$ref: 'https://zalando.github.io/problem/schema.yaml#/Problem'
API designers should also think about a troubleshooting board as part of the associated online API documentation. It provides information and handling guidance on application-specific errors and is referenced via links from the API specification. This can reduce service support tasks and contribute to service client and provider performance.
Must: Use Standard HTTP Status Codes
You must only use standardized HTTP status codes consistently with their intended semantics. You must not invent new HTTP status codes.
RFC standards define ~60 different HTTP status codes with specific semantics (mainly RFC7231 and RFC-6585) — and there are upcoming new ones, e.g. draft legally-restricted-status. See overview on all error codes on Wikipedia or via https://httpstatuses.com/) also inculding 'unofficial codes', e.g. used by popular web servers like Nginx.
Below we list the most commonly used and best understood HTTP status codes, consistent with their semantic in the RFCs. APIs should only use these to prevent misconceptions that arise from less commonly used HTTP status codes.
Important: As long as your HTTP status code usage is well covered by the semantic defined here, you should not describe it to avoid an overload with common sense information and the risk of inconsistent definitions. Only if the HTTP status code is not in the list below or its usage requires additional information aside the well defined semantic, the API specification must provide a clear description of the HTTP status code in the response.
Success Codes
Code | Meaning | Methods |
---|---|---|
200 |
OK - this is the standard success response |
All |
201 |
Created - Returned on successful entity creation. You are free to return either an empty response or the created resource in conjunction with the Location header. (More details found in the Common Headers.) Always set the Location header. |
POST, PUT |
202 |
Accepted - The request was successful and will be processed asynchronously. |
POST, PUT, DELETE, PATCH |
204 |
No content - There is no response body |
PUT, DELETE, PATCH |
207 |
Multi-Status - The response body contains multiple status informations for different parts of a batch/bulk request. See Must: Use Code 207 for Batch or Bulk Requests. |
POST |
Redirection Codes
Code | Meaning | Methods |
---|---|---|
301 |
Moved Permanently - This and all future requests should be directed to the given URI. |
All |
303 |
See Other - The response to the request can be found under another URI using a GET method. |
PATCH, POST, PUT, DELETE |
304 |
Not Modified - resource has not been modified since the date or version passed via request headers If-Modified-Since or If-None-Match. |
GET |
Client Side Error Codes
Code | Meaning | Methods |
---|---|---|
400 |
Bad request - generic / unknown error. Should also be delivered in case of input payload fails business logic validation. |
All |
401 |
Unauthorized - the users must log in (this often means "Unauthenticated") |
All |
403 |
Forbidden - the user is not authorized to use this resource |
All |
404 |
Not found - the resource is not found |
All |
405 |
Method Not Allowed - the method is not supported, see OPTIONS |
All |
406 |
Not Acceptable - resource can only generate content not acceptable according to the Accept headers sent in the request |
All |
408 |
Request timeout - the server times out waiting for the resource |
All |
409 |
Conflict - request cannot be completed due to conflict, e.g. when two clients try to create the same resource or if there are concurrent, conflicting updates |
POST, PUT, DELETE, PATCH |
410 |
Gone - resource does not exist any longer, e.g. when accessing a resource that has intentionally been deleted |
All |
412 |
Precondition Failed - returned for conditional requests, e.g. If-Match if the condition failed. Used for optimistic locking. |
PUT, DELETE, PATCH |
415 |
Unsupported Media Type - e.g. clients sends request body without content type |
POST, PUT, DELETE, PATCH |
423 |
Locked - Pessimistic locking, e.g. processing states |
PUT, DELETE, PATCH |
428 |
Precondition Required - server requires the request to be conditional (e.g. to make sure that the "lost update problem" is avoided). |
All |
429 |
Too many requests - the client does not consider rate limiting and sent too many requests. See Must: Use Code 429 with Headers for Rate Limits. |
All |
Server Side Error Codes:
Code | Meaning | Methods |
---|---|---|
500 |
Internal Server Error - a generic error indication for an unexpected server execution problem (here, client retry may be sensible) |
All |
501 |
Not Implemented - server cannot fulfill the request (usually implies future availability, e.g. new feature). |
All |
503 |
Service Unavailable - service is (temporarily) not available (e.g. if a required component or downstream service is not available) — client retry may be sensible. If possible, the service should indicate how long the client should wait by setting the 'Retry-After' header. |
All |
Must: Use Most Specific HTTP Status Codes
You must use the most specific HTTP status code when returning information about your request processing status or error situations.
Must: Use Code 207 for Batch or Bulk Requests
Some APIs are required to provide either batch or bulk requests using POST for performance reasons, i.e. for communication and processing efficiency. In this case services may be in need to signal multiple response codes for each part of an batch or bulk request. As HTTP does not provide proper guidance for handling batch/bulk requests and responses, we herewith define the following approach:
-
A batch or bulk request always has to respond with HTTP status code 207, unless it encounters a generic or unexpected failure before looking at individual parts.
-
A batch or bulk response with status code 207 always returns a multi-status object containing sufficient status and/or monitoring information for each part of the batch or bulk request.
-
A batch or bulk request may result in a status code 400/500, only if the service encounters a failure before looking at individual parts or, if an unanticipated failure occurs.
The before rules apply even in the case that processing of all individual part fail or each part is executed asynchronously! They are intended to allow clients to act on batch and bulk responses by inspecting the individual results in a consistent way.
Note: while a batch defines a collection of requests triggering independent processes, a bulk defines a collection of independent resources created or updated together in one request. With respect to response processing this distinction normally does not matter.
Must: Use Code 429 with Headers for Rate Limits
APIs that wish to manage the request rate of clients must use the '429 Too Many Requests' response code if the client exceeded the request rate and therefore the request can’t be fulfilled. Such responses must also contain header information providing further details to the client. There are two approaches a service can take for header information:
-
Return a 'Retry-After' header indicating how long the client ought to wait before making a follow-up request. The Retry-After header can contain a HTTP date value to retry after or the number of seconds to delay. Either is acceptable but APIs should prefer to use a delay in seconds.
-
Return a trio of 'X-RateLimit' headers. These headers (described below) allow a server to express a service level in the form of a number of allowing requests within a given window of time and when the window is reset.
The 'X-RateLimit' headers are:
-
X-RateLimit-Limit
: The maximum number of requests that the client is allowed to make in this window. -
X-RateLimit-Remaining
: The number of requests allowed in the current window. -
X-RateLimit-Reset
: The relative time in seconds when the rate limit window will be reset. Beware that this is different to Github and Twitter’s usage of a header with the same name which is using UTC epoch seconds instead.
The reason to allow both approaches is that APIs can have different needs. Retry-After is often sufficient for general load handling and request throttling scenarios and notably, does not strictly require the concept of a calling entity such as a tenant or named account. In turn this allows resource owners to minimise the amount of state they have to carry with respect to client requests. The 'X-RateLimit' headers are suitable for scenarios where clients are associated with pre-existing account or tenancy structures. 'X-RateLimit' headers are generally returned on every request and not just on a 429, which implies the service implementing the API is carrying sufficient state to track the number of requests made within a given window for each named entity.
Must: Use Problem JSON
RFC 7807 defines a Problem JSON object and
the media type application/problem+json
. Operations should return it (together with
a suitable status code) when any problem occurred during processing and
you can give more details than the status code itself can supply,
whether it be caused by the client or the server (i.e. both for 4xx or
5xx error codes).
The Open API schema definition of the Problem JSON object can be found on github. You can reference it by using:
responses:
503:
description: Service Unavailable
schema:
$ref: 'https://zalando.github.io/problem/schema.yaml#/Problem'
You may define custom problem types as extension of the Problem JSON object if your API need to return specific additional error detail information.
Hint for backward compatibility:
A previous version of this guideline (before the publication of
RFC 7807
and the registration of the media type) told to return
custom variant of the media type application/x.problem+json
.
Servers for APIs defined before this change should pay attention to the
Accept
header sent by the client and set the Content-Type
header of
the problem response correspondingly. Clients of such APIs should accept
both media types.
Must: Do not expose Stack Traces
Stack traces contain implementation details that are not part of an API, and on which clients should never rely. Moreover, stack traces can leak sensitive information that partners and third parties are not allowed to receive and may disclose insights about vulnerabilities to attackers.
13. Performance
Should: Reduce Bandwidth Needs and Improve Responsiveness
APIs should support techniques for reducing bandwidth based on client needs. This holds for APIs that (might) have high payloads and/or are used in high-traffic scenarios like the public Internet and telecommunication networks. Typical examples are APIs used by mobile web app clients with (often) less bandwidth connectivity. (Zalando is a 'Mobile First' company, so be mindful of this point.)
Common techniques include:
-
querying field filters to retrieve a subset of resource attributes (see Should: Support Filtering of Resource Fields below)
-
paginate lists of data items (see Pagination below)
-
ETag
andIf-(None-)Match
headers to avoid re-fetching of unchanged resources (see May: Consider Using ETag Together With If-Match/If-None-Match Header -
pagination for incremental access of larger (result) lists
Each of these items is described in greater detail below.
Should: Use gzip Compression
Compress the payload of your API’s responses with gzip, unless there’s a good reason not to — for example, you are serving so many requests that the time to compress becomes a bottleneck. This helps to transport data faster over the network (fewer bytes) and makes frontends respond faster.
Though gzip compression might be the default choice for server payload, the server should also support payload without compression and its client control via Accept-Encoding request header — see also RFC 7231 Section 5.3.4. The server should indicate used gzip compression via the Content-Encoding header.
Should: Support Filtering of Resource Fields
Depending on your use case and payload size, you can significantly reduce network bandwidth need by supporting filtering of returned entity fields. Here, the client can determine the subset of fields he wants to receive via the fields query parameter — example see Google AppEngine API’s partial response:
Unfiltered
GET http://api.example.org/resources/123 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "cddd5e44-dae0-11e5-8c01-63ed66ab2da5",
"name": "John Doe",
"address": "1600 Pennsylvania Avenue Northwest, Washington, DC, United States",
"birthday": "1984-09-13",
"partner": {
"id": "1fb43648-dae1-11e5-aa01-1fbc3abb1cd0",
"name": "Jane Doe",
"address": "1600 Pennsylvania Avenue Northwest, Washington, DC, United States",
"birthday": "1988-04-07"
}
}
Filtered
GET http://api.example.org/resources/123?fields=(name,partner(name)) HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"name": "John Doe",
"partner": {
"name": "Jane Doe"
}
}
As illustrated by this example, field filtering should be done via request parameter "fields" with value range defined by the following BNF grammar.
<fields> ::= <negation> <fields_expression> | <fields_expression>
<negation> ::= "!"
<fields_expression> ::= "(" <field_set> ")"
<field_set> ::= <qualified_field> | <qualified_field> "," <field_set>
<qualified_field> ::= <field> | <field> <fields_expression>
<field> ::= <DASH_LETTER_DIGIT> | <DASH_LETTER_DIGIT> <field>
<DASH_LETTER_DIGIT> ::= <DASH> | <LETTER> | <DIGIT>
<DASH> ::= "-" | "_"
<LETTER> ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
<DIGIT> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
A fields_expression
as defined by the grammar describes the properties
of an object, i.e. (name)
returns only the name
property of the root
object. (name,partner(name))
returns the name
and partner
properties where partner
itself is also an object and only its name
property is returned.
Hint: OpenAPI doesn’t allow you to formally specify whether depending on a given parameter will return different parts of the specified result schema. Explain this in English in the parameter description.
Should: Allow Optional Embedding of Sub-Resources
Embedding related resources (also know as Resource expansion) is a great way to reduce the number of requests. In cases where clients know upfront that they need some related resources they can instruct the server to prefetch that data eagerly. Whether this is optimized on the server, e.g. a database join, or done in a generic way, e.g. an HTTP proxy that transparently embeds resources, is up to the implementation.
See May: Use Conventional Query Strings for naming, e.g. "embed" for steering of embedded resource expansion. Please use the BNF grammar, as already defined above for filtering, when it comes to an embedding query syntax.
Embedding a sub-resource can possibly look like this where an order resource has its order items as sub-resource (/order/{orderId}/items):
GET /order/123?embed=(items) HTTP/1.1
{
"id": "123",
"_embedded": {
"items": [
{
"position": 1,
"sku": "1234-ABCD-7890",
"price": {
"amount": 71.99,
"currency": "EUR"
}
}
]
}
}
Must: Document Caching, if Supported
If an API is intended to support caching, it must take care to specify
this ability by defining the caching boundaries i.e. time-to-live and
cache constraints, by providing the Cache-Control
and Vary
headers
(Please read RFC-7234 carefully).
Caching has to take many aspects into account, e.g. general cacheability of response information, our guideline to protect endpoints using SSL and OAuth authorization, resource update and invalidation rules, existence of multiple consumer instances. As a consequence, caching is in best case complex, in worst case inefficient. Thus, client side and transparent HTTP caching should be avoided for RESTful APIs unless the API designer has proven to know better.
As default, API providers should set the Cache-Control: no-cache
header.
Note: There is no need to document this default setting, that is attached to each response automatically by most frameworks. However, any use deviating from this default must be sufficiently documented.
14. Pagination
Must: Support Pagination
Access to lists of data items must support pagination for best client side batch processing and iteration experience. This holds true for all lists that are (potentially) larger than just a few hundred entries.
There are two page iteration techniques:
-
Offset/Limit-based pagination: numeric offset identifies the first page entry
-
Cursor-based — aka key-based — pagination: a unique key element identifies the first page entry (see also Facebook’s guide)
The technical conception of pagination should also consider user experience related issues. As mentioned in this article, jumping to a specific page is far less used than navigation via next/previous page links. This favours cursor-based over offset-based pagination.
Should: Prefer Cursor-Based Pagination, Avoid Offset-Based Pagination
Cursor-based pagination is usually better and more efficient when compared to offset-based pagination. Especially when it comes to high-data volumes and / or storage in NoSQL databases.
Before choosing cursor-based pagination, consider the following trade-offs:
-
Usability/framework support:
-
Offset / limit based pagination is more known than cursor-based pagination, so it has more framework support and is easier to use for API clients
-
-
Use case: Jump to a certain page
-
If jumping to a particular page in a range (e.g., 51 of 100) is really a required use case, cursor-based navigation is not feasible
-
-
Variability of data may lead to anomalies in result pages
-
Offset-based pagination may create duplicates or lead to missing entries if rows are inserted or deleted between two subsequent paging requests.
-
When using cursor-based pagination, paging cannot continue when the cursor entry has been deleted while fetching two pages
-
-
Performance considerations - efficient server-side processing using offset-based pagination is hardly feasible for:
-
Higher data list volumes, especially if they do not reside in the database’s main memory
-
Sharded or NoSQL databases
-
-
Cursor-based navigation may not work if you need the total count of results and / or backward iteration support
Further reading:
May: Use Pagination Links Where Applicable
-
API implementing HATEOS may use simplified hypertext controls for pagination within collections.
Those collections should then have an items
attribute holding the
items of the current page. The collection may contain additional
metadata about the collection or the current page (e.g. index
,
page_size
) when necessary.
You should avoid providing a total count in your API unless there’s a clear need to do so. Very often, there are systems and performance implications to supporting full counts, especially as datasets grow and requests become complex queries or filters that drive full scans (e.g., your database might need to look at all candidate items to count them). While this is an implementation detail relative to the API, it’s important to consider your ability to support serving counts over the life of a service.
If the collection consists of links to other resources, the collection name should use IANA registered link relations as names whenever appropriate, but use plural form.
E.g. a service for articles could represent the collection of hyperlinks
to an article’s authors
like that:
{
"self": "https://.../articles/xyz/authors/",
"index": 0,
"page_size": 5,
"items": [
{
"href": "https://...",
"id": "123e4567-e89b-12d3-a456-426655440000",
"name": "Kent Beck"
},
{
"href": "https://...",
"id": "987e2343-e89b-12d3-a456-426655440000",
"name": "Mike Beedle"
},
...
],
"first": "https://...",
"next": "https://...",
"prev": "https://...",
"last": "https://..."
}
15. Hypermedia
Must: Use REST Maturity Level 2
We strive for a good implementation of REST Maturity Level 2 as it enables us to build resource-oriented APIs that make full use of HTTP verbs and status codes. You can see this expressed by many rules throughout these guidelines, e.g.:
Although this is not HATEOAS, it should not prevent you from designing proper link relationships in your APIs as stated in rules below.
May: Use REST Maturity Level 3 - HATEOAS
We do not generally recommend to implement REST Maturity Level 3. HATEOAS comes with additional API complexity without real value in our SOA context where client and server interact via REST APIs and provide complex business functions as part of our e-commerce SaaS platform.
Our major concerns regarding the promised advantages of HATEOAS (see also RESTistential Crisis over Hypermedia APIs, Why I Hate HATEOAS and others for a detailed discussion):
-
We follow the API First principle with APIs explicitly defined outside the code with standard specification language. HATEOAS does not really add value for SOA client engineers in terms of API self-descriptiveness: a client engineer finds necessary links and usage description (depending on resource state) in the API reference definition anyway.
-
Generic HATEOAS clients which need no prior knowledge about APIs and explore API capabilities based on hypermedia information provided, is a theoretical concept that we haven’t seen working in practice and does not fit to our SOA set-up. The OpenAPI description format (and tooling based on OpenAPI) doesn’t provide sufficient support for HATEOAS either.
-
In practice relevant HATEOAS approximations (e.g. following specifications like HAL or JSON API) support API navigation by abstracting from URL endpoint and HTTP method aspects via link types. So, Hypermedia does not prevent clients from required manual changes when domain model changes over time.
-
Hypermedia make sense for humans, less for SOA machine clients. We would expect use cases where it may provide value more likely in the frontend and human facing service domain.
-
Hypermedia does not prevent API clients to implement shortcuts and directly target resources without 'discovering' them.
However, we do not forbid HATEOAS; you could use it, if you checked its limitations and still see clear value for your usage scenario that justifies its additional complexity. If you use HATEOAS please share experience and present your findings in the API Guild [internal link].
Must: Use full, absolute URI
Links to other resource must always use full, absolute URI.
Motivation: Exposing any form of relative URI (no matter if the relative URI uses an absolute or relative path) introduces avoidable client side complexity. It also requires clarity on the base URI, which might not be given when using features like embedding subresources. The primary advantage of non-absolute URI is reduction of the payload size, which is better achievable by following the recommendation to use gzip compression
Must: Use Common Hypertext Controls
When embedding links to other resources into representations you must use the common hypertext control object. It contains at least one attribute:
-
href
: The URI of the resource the hypertext control is linking to. All our API are using HTTP(s) as URI scheme.
In API that contain any hypertext controls, the attribute name href
is
reserved for usage within hypertext controls.
The schema for hypertext controls can be derived from this model:
HttpLink:
description: A base type of objects representing links to resources.
type: object
properties:
href:
description: Any URI that is using http or https protocol
type: string
format: uri
required: [ "href" ]
The name of an attribute holding such a HttpLink
object specifies the
relation between the object that contains the link and the linked
resource. Implementations should use names from the
IANA
Link Relation Registry whenever appropriate. As IANA link relation
names use hyphen-case notation, while this guide enforces snake_case
notation for attribute names, hyphens in IANA names have to be replaced
with underscores (e.g. the IANA link relation type version-history
would become the attribute version_history
)
Specific link objects may extend the basic link type with additional attributes, to give additional information related to the linked resource or the relationship between the source resource and the linked one.
E.g. a service providing "Person" resources could model a person who is
married with some other person with a hypertext control that contains
attributes which describe the other person (id
, name
) but also the
relationship "spouse" between the two persons (since
):
{
"id": "446f9876-e89b-12d3-a456-426655440000",
"name": "Peter Mustermann",
"spouse": {
"href": "https://...",
"since": "1996-12-19",
"id": "123e4567-e89b-12d3-a456-426655440000",
"name": "Linda Mustermann"
}
}
Hypertext controls are allowed anywhere within a JSON model. While this specification would allow HAL, we actually don’t recommend/enforce the usage of HAL anymore as the structural separation of meta-data and data creates more harm than value to the understandability and usability of an API.
Should: Use Simple Hypertext Controls for Pagination and Self-References
Hypertext controls for pagination inside collections and self-references
should use a simple URI value in combination with their corresponding
link
relations (next
, prev
, first
, last
, self
) instead of the
extensible common hypertext control
See Pagination for information how to best represent paginateable collections.
Must: Not Use Link Headers with JSON entities
We don’t allow the use of the
Link
Header defined by
RFC 5988 in conjunction with JSON media types. We prefer links directly
embedded in JSON payloads to the uncommon link header syntax.
16. Data Formats
Must: Use JSON to Encode Structured Data
Use JSON-encoded body payload for transferring structured data. The JSON payload must follow RFC-7159 by having (if possible) a serialized object as the top-level structure, since it would allow for future extension. This also applies for collection resources where one naturally would assume an array. See May: Use Pagination Links Where Applicable for an example.
May: Use non JSON Media Types for Binary Data or Alternative Content Representations
Other media types may be used in following cases:
-
Transferring binary data or data whose structure is not relevant. This is the case if payload structure is not interpreted and consumed by clients as is. Example of such use case is downloading images in formats JPG, PNG, GIF.
-
In addition to JSON version alternative data representations (e.g. in formats PDF, DOC, XML) may be made available through content negotiation.
Should: Prefer standard Media type name application/json
Previously, this guideline allowed the use of custom media types like
application/x.zalando.article+json
. This usage is not recommended
anymore and should be avoided, except where it is necessary for cases of
media type versioning. Instead, just use the standard media type name
application/json
(or application/problem+json
for Must: Use Problem JSON).
Custom media types beginning with x
bring no advantage
compared to the standard media type for JSON, and make automated
processing more difficult. They are also
discouraged by RFC
6838.
Must: Use Standard Date and Time Formats
JSON Payload
Read more about date and time format in Should: Date property values should conform to RFC 3339.
HTTP headers
Http headers including the proprietary headers use the HTTP date format defined in RFC 7231.
May: Use Standards for Country, Language and Currency Codes
Use the following standard formats for country, language and currency codes:
-
ISO 3166-1-alpha2 country codes
-
(It is "GB", not "UK", even though "UK" has seen some use at Zalando)
-
-
-
BCP-47 (based on ISO 639-1) for language variants
-
Must: Define Format for Type Number and Integer
Whenever an API defines a property of type number
or integer
, the
precision must be defined by the format as follows to prevent clients
from guessing the precision incorrectly, and thereby changing the value
unintentionally:
type | format | specified value range |
---|---|---|
integer |
int32 |
integer between -231 and 231-1 |
integer |
int64 |
integer between -263 and 263-1 |
integer |
bigint |
arbitrarily large signed integer number |
number |
float |
IEEE 754-2008/ISO 60559:2011 binary64 decimal number |
number |
double |
IEEE 754-2008/ISO 60559:2011 binary128 decimal number |
number |
decimal |
arbitrarily precise signed decimal number |
The precision must be translated by clients and servers into the most
specific language types. E.g. for the following definitions the most
specific language types in Java will translate to BigDecimal
for
Money.amount
and int
or Integer
for the OrderList.page_size
:
Money:
type: object
properties:
amount:
type: number
description: Amount expressed as a decimal number of major currency units
format: decimal
example: 99.95
...
OrderList:
type: object
properties:
page_size:
type: integer
description: Number of orders in list
format: int32
example: 42
17. Common Data Types
Definitions of data objects that are good candidates for wider usage:
Should: Use a Common Money Object
Use the following common money structure:
Money:
type: object
properties:
amount:
type: number
description: Amount expressed as a decimal number of major currency units
format: decimal
example: 99.95
currency:
type: string
description: 3 letter currency code as defined by ISO-4217
format: iso-4217
example: EUR
required:
- amount
- currency
The decimal values for "amount" describe unit and subunit of the currency in a single value, where the digits before the decimal point are for the major unit and the digits after the decimal point are for the minor unit. Note that some business cases (e.g. transactions in Bitcoin) call for a higher precision, so applications must be prepared to accept values with unlimited precision, unless explicitly stated otherwise in the API specification. Examples for correct representations (in EUR):
-
42.20
or42.2
= 42 Euros, 20 Cent -
0.23
= 23 Cent -
42.0
or42
= 42 Euros -
1024.42
= 1024 Euros, 42 Cent -
1024.4225
= 1024 Euros, 42.25 Cent
Make sure that you don’t convert the "amount" field to float
/
double
types when implementing this interface in a specific language
or when doing calculations. Otherwise, you might lose precision.
Instead, use exact formats like Java’s
BigDecimal
.
See Stack Overflow for more
info.
Some JSON parsers (NodeJS’s, for example) convert numbers to floats by default. After discussing the pros and cons we’ve decided on "decimal" as our amount format. It is not a standard OpenAPI format, but should help us to avoid parsing numbers as float / doubles.
Must: Use common field names and semantics
There exist a variety of field types that are required in multiple places. To achieve consistency across all API implementations, you must use common field names and semantics whenever applicable.
Generic Fields
There are some data fields that come up again and again in API data:
-
id
: the identity of the object. If used, IDs must be opaque strings and not numbers. IDs are unique within some documented context, are stable and don’t change for a given object once assigned, and are never recycled cross entities. -
xyzId
: an attribute within one object holding the identifier of another object must use a name that corresponds to the type of the referenced object or the relationship to the referenced object followed byId
(e.g.customerId
notcustomerNumber
;parentNodeId
for the reference to a parent node from a child node, even if both have the typeNode
) -
createdAt
: when the object was created. If used, this must be adate-time
construct. -
modifiedAt
: when the object was updated. If used, this must be adate-time
construct. -
type
: the kind of thing this object is. If used, the type of this field should be a string. Types allow runtime information on the entity provided that otherwise requires examining the Open API file. -
etag
: the ETag of an embedded sub-resource. It may be used to carry the ETag for subsequent PUT calls (see ETags in result entities).
Example JSON schema:
{
"treeNode": {
"type": "object",
"properties": {
"id": {
"description": " the identifier of this node",
"type": "string"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"modifiedAt": {
"description": "when got this node last updated",
"type": "string",
"format": "date-time"
},
"type": {
"type": "string",
"enum": [
"LEAF",
"NODE"
]
},
"parentNodeId": {
"description": "the identifier of the parent node of this node",
"type": "string"
}
},
"example": {
"id": "123435",
"created": "2017-04-12T23:20:50.52Z",
"modified": "2017-04-12T23:20:50.52Z",
"type": "LEAF",
"parent_node_id": "534321"
}
}
}
These properties are not always strictly necessary, but making them idiomatic allows API client developers to build up a common understanding of Zalando’s resources. There is very little utility for API consumers in having different names or value types for these fields across APIs.
Address Fields
Address structures play a role in different functional and use-case contexts, including country variances. All attributes that relate to address information should follow the naming and semantics defined below.
{
"addressee": {
"description": "a (natural or legal) person that gets addressed",
"type": "object",
"properties": {
"salutation": {
"description": "a salutation and/or title used for personal contacts to some\naddressee; not to be confused with the gender information!\n",
"type": "string",
"example": "Mr"
},
"first_name": {
"description": "given name(s) or first name(s) of a person; may also include the\nmiddle names.\n",
"type": "string",
"example": "Hans Dieter"
},
"last_name": {
"description": "family name(s) or surname(s) of a person\n",
"type": "string",
"example": "Mustermann"
},
"business_name": {
"description": "company name of the business organization. Used when a business is\nthe actual addressee; for personal shipments to office addresses, use\n`care_of` instead.\n",
"type": "string",
"example": "Consulting Services GmbH"
}
},
"required": [
"first_name",
"last_name"
]
},
"address": {
"description": "an address of a location/destination",
"type": "object",
"properties": {
"care_of": {
"description": "(aka c/o) the person that resides at the address, if different from\naddressee. E.g. used when sending a personal parcel to the\noffice /someone else's home where the addressee resides temporarily\n",
"type": "string",
"example": "Consulting Services GmbH"
},
"street": {
"description": "the full street address including house number and street name\n",
"type": "string",
"example": "Schönhauser Allee 103"
},
"additional": {
"description": "further details like building name, suite, apartment number, etc.\n",
"type": "string",
"example": "2. Hinterhof rechts"
},
"city": {
"description": "name of the city / locality\n",
"type": "string",
"example": "Berlin"
},
"zip": {
"description": "zip code or postal code\n",
"type": "string",
"example": 14265
},
"country_code": {
"description": "the country code according to\n[iso-3166-1-alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)\n",
"type": "string",
"example": "DE"
}
},
"required": [
"street",
"city",
"zip",
"country_code"
]
}
}
Grouping and cardinality of fields in specific data types may vary based on the specific use case (e.g. combining addressee and address fields into a single type when modeling an address label vs distinct addressee and address types when modeling users and their addresses).
18. Common Headers
This section describes a handful of headers, which we found raised the most questions in our daily usage, or which are useful in particular circumstances but not widely known.
Must: Use Content Headers Correctly
Content or entity headers are headers with a Content-
prefix. They
describe the content of the body of the message and they can be used in
both, HTTP requests and responses. Commonly used content headers include
but are not limited to:
-
Content-Disposition
can indicate that the representation is supposed to be saved as a file, and the proposed file name. -
Content-Encoding
indicates compression or encryption algorithms applied to the content. -
Content-Length
indicates the length of the content (in bytes). -
Content-Language
indicates that the body is meant for people literate in some human language(s). -
Content-Location
indicates where the body can be found otherwise (May: Use Content-Location Header for more details]). -
Content-Range
is used in responses to range requests to indicate which part of the requested resource representation is delivered with the body. -
Content-Type
indicates the media type of the body content.
May: Use Standardized Headers
Use this list and mention its support in your OpenAPI definition.
May: Use Content-Location Header
The Content-Location header is optional and can be used in successful write operations (PUT, POST or PATCH) or read operations (GET, HEAD) to guide caching and signal a receiver the actual location of the resource transmitted in the response body. This allows clients to identify the resource and to update their local copy when receiving a response with this header.
The Content-Location header can be used to support the following use cases:
-
For reading operations GET and HEAD, a different location than the requested URI can be used to indicate that the returned resource is subject to content negotiations, and that the value provides a more specific identifier of the resource.
-
For writing operations PUT and PATCH, an identical location to the requested URI can be used to explicitly indicate that the returned resource is the current representation of the newly created or updated resource.
-
For writing operations POST and DELETE, a content location can be used to indicate that the body contains a status report resource in response to the requested action, which is available at provided location.
Note: When using the Content-Location header, the Content-Type header has to be set as well. For example:
GET /products/123/images HTTP/1.1
HTTP/1.1 200 OK
Content-Type: image/png
Content-Location: /products/123/images?format=raw
Should: Use Location Header instead of Content-Location Header
As the correct usage of Content-Location with respect to semantics and caching is difficult, we discourage the use of Content-Location. In most cases it is sufficient to direct clients to the resource location by using the Location header instead without hitting the Content-Location specific ambiguities and complexities.
More details in RFC 7231 7.1.2 Location, 3.1.4.2 Content-Location
May: Use the Prefer header to indicate processing preferences
The Prefer
header defined in
RFC7240 allows clients to request
processing behaviors from servers.
RFC7240 pre-defines a number of
preferences and is extensible, to allow others to be defined. Support
for the Prefer header is entirely optional and at the discretion of API
designers, but as an existing Internet Standard, is recommended over
defining proprietary "X-" headers for processing directives.
The Prefer
header can defined like this in an API definition:
Prefer:
name: Prefer
description: |
The RFC7240 Prefer header indicates that particular server
behaviors are preferred by the client but are not required
for successful completion of the request.
# (indicate the preferences supported by the API)
in: header
type: string
required: false
Supporting APIs may return the Preference-Applied
header also defined
in RFC7240 to indicate whether the
preference was applied.
May: Consider Using ETag Together With If-Match/If-None-Match Header
When creating or updating resources it may be necessary to expose
conflicts and to prevent the 'lost update' or 'initially created' problem.
Following RFC 7232 "HTTP: Conditional Requests"
this can be best accomplished by using the
ETag
header together
with the If-Match
or
If-None-Match
conditional header. The
contents of an ETag: <entity-tag>
header is either (a) a hash of the
response body, (b) a hash of the last modified field of the entity, or
(c) a version number or identifier of the entity version.
To expose conflicts between concurrent update operations via PUT, POST,
or PATCH, the If-Match: <entity-tag>
header can be used to force the
server to check whether the version of the updated entity is conforming
to the requested <entity-tag>
. If no matching entity is found, the
operation is supposed a to respond with status code 412 - precondition
failed.
Beside other use cases, the If-None-Match:
header with parameter *
can be used in a similar way to expose conflicts in resource creation.
If any matching entity is found, the operation is supposed a to respond
with status code 412 - precondition failed.
The ETag
, If-Match
, and If-None-Match
headers can be defined as
follows in the API definition:
ETag:
name: ETag
description: |
The RFC7232 ETag header field in a response provides the current entity-
tag for the selected resource. An entity-tag is an opaque identifier for
different versions of a resource over time, regardless whether multiple
versions are valid at the same time. An entity-tag consists of an opaque
quoted string, possibly prefixed by a weakness indicator.
in: header
type: string
required: false
example: W/"xy", "5", "7da7a728-f910-11e6-942a-68f728c1ba70"
IfMatch:
name: If-Match
description: |
The RFC7232 If-Match header field in a request requires the server to
only operate on the resource that matches at least one of the provided
entity-tags. This allows clients express a precondition that prevent
the method from being applied if there have been any changes to the
resource.
in: header
type: string
required: false
example: "5", "7da7a728-f910-11e6-942a-68f728c1ba70"
IfNoneMatch:
name: If-None-Match
description: |
The RFC7232 If-None-Match header field in a request requires the server
to only operate on the resource if it does not match any of the provided
entity-tags. If the provided entity-tag is `*`, it is required that the
resource does not exist at all.
in: header
type: string
required: false
example: "7da7a728-f910-11e6-942a-68f728c1ba70", *
Please also see the section Optimistic Locking in RESTful APIs for a discussion about alternative approaches.
19. API Operation
Must: Publish OpenAPI Specification
All service applications must publish OpenAPI specifications of their external APIs. While this is optional for internal APIs, i.e. APIs marked with the component-internal API audience group, we still recommend to do so to profit from the API management infrastructure.
An API is published by copying its OpenAPI specification into the reserved
/zalando-apis directory of the deployment artifact used to deploy the
provisioning service. The directory must only contain self-contained YAML
files that each describe one API. We prefer this deployment artifact-based method over the
past (now legacy) .well-known/schema-discovery
service endpoint-based
publishing process, that we only support for backward compatibility reasons.
Background: In our dynamic and complex service infrastructure, it is important to provide API client developers a central place with online access to the API specifications of all running applications. As a part of the infrastructure, the API publishing process is used to detect API specifications. The findings are published in the API Portal - the universal hub for all Zalando APIs.
Note: To publish an API, it is still necessary to deploy the artifact successful, as we focus the discovery experience on APIs supported by running services.
Should: Monitor API Usage
Owners of APIs used in production should monitor API service to get information about its using clients. This information, for instance, is useful to identify potential review partner for API changes.
Hint: A preferred way of client detection implementation is by logging of the client-id retrieved from the OAuth token.
Appendix A: References
This section collects links to documents to which we refer, and base our guidelines on.
Publications, specifications and standards
Dissertations
-
Roy Thomas Fielding - Architectural Styles and the Design of Network-Based Software Architectures. This is the text which defines what REST is.
Appendix B: Tooling
This is not a part of the actual guidelines, but might be helpful for following them. Using a tool mentioned here doesn’t automatically ensure you follow the guidelines.
API First Integrations
The following frameworks were specifically designed to support the API First workflow with OpenAPI YAML files (sorted alphabetically):
-
Connexion: OpenAPI First framework for Python on top of Flask
-
Friboo: utility library to write microservices in Clojure with support for Swagger and OAuth
-
Api-First-Hand: API-First Play Bootstrapping Tool for Swagger/OpenAPI specs
-
Swagger Codegen Tooling: plugin for Maven that generates pieces of code from OpenAPI specification
The Swagger/OpenAPI homepage lists more Community-Driven Language Integrations, but most of them do not fit our API First approach.
Support Libraries
These utility libraries support you in implementing various parts of our RESTful API guidelines (sorted alphabetically):
-
Problem: Java library that implements application/problem+json
-
Problems for Spring Web MVC: library for handling Problems in Spring Web MVC
-
Jackson Datatype Money: extension module to properly support datatypes of javax.money
-
JSON fields: framework for limiting fields of JSON objects exposed by Rest APIs
-
Tracer: call tracing and log correlation in distributed systems
-
TWINTIP Spring Integration: API discovery endpoint for Spring Web MVC
Appendix C: Best Practices
The best practices presented in this section are not part of the actual guidelines, but should provide guidance for common challenges we face when implementing RESTful APIs.
Optimistic Locking in RESTful APIs
Introduction
Optimistic locking might be used to avoid concurrent writes on the same entity, which might cause data loss. A client always has to retrieve a copy of an entity first and specifically update this one. If another version has been created in the meantime, the update should fail. In order to make this work, the client has to provide some kind of version reference, which is checked by the service, before the update is executed. Please see the section about usage of the PUT method for a more detailed description on how to update resources via PUT.
A RESTful API usually includes some kind of search endpoint, which will then return a list of result entities. There are several ways to implement optimistic locking in combination with search endpoints which, depending on the approach chosen, might lead to performing additional requests to get the current version of the entity that should be updated.
ETag header with If-Match header
An ETag can only be obtained by performing a GET request on the single entity resource before the update, i.e. when using a search endpoint an additional request is necessary.
Example:
< GET /orders
> HTTP/1.1 200 OK
> {
> "items": [
> { "id": "O0000042" },
> { "id": "O0000043" }
> ]
> }
< GET /orders/BO0000042
> HTTP/1.1 200 OK
> ETag: osjnfkjbnkq3jlnksjnvkjlsbf
> { "id": "BO0000042", ... }
< PUT /orders/O0000042
< If-Match: osjnfkjbnkq3jlnksjnvkjlsbf
< { "id": "O0000042", ... }
> HTTP/1.1 204 No Content
or if there was an update since the GET and the entity's ETag has changed:
> HTTP/1.1 412 Precondition failed
Pros
-
RESTful solution
Cons
-
Many additional requests are necessary to build a meaningful front-end
ETags in result entities
The ETag for every entity is returned as an additional property of that entity. In a response containing multiple entities, every entity will then have a distinct ETag that can be used in subsequent PUT requests.
In this solution, the etag
property should be readonly
and never be expected in the PUT request
payload.
Example:
< GET /orders
> HTTP/1.1 200 OK
> {
> "items": [
> { "id": "O0000042", "etag": "osjnfkjbnkq3jlnksjnvkjlsbf", "foo": 42, "bar": true },
> { "id": "O0000043", "etag": "kjshdfknjqlowjdsljdnfkjbkn", "foo": 24, "bar": false }
> ]
> }
< PUT /orders/O0000042
< If-Match: osjnfkjbnkq3jlnksjnvkjlsbf
< { "id": "O0000042", "foo": 43, "bar": true }
> HTTP/1.1 204 No Content
or if there was an update since the GET and the entity's ETag has changed:
> HTTP/1.1 412 Precondition failed
Pros
-
Perfect optimistic locking
Cons
-
Information that only belongs in the HTTP header is part of the business objects
Version numbers
The entities contain a property with a version number. When an update is performed, this version number is given back to the service as part of the payload. The service performs a check on that version number to make sure it was not incremented since the consumer got the resource and performs the update, incrementing the version number.
Since this operation implies a modification of the resource by the service, a POST operation on the
exact resource (ex: POST /orders/O0000042
) should be used instead of a PUT (see PUT's semantic).
In this solution, the version
property is not readonly
since it is provided at POST time as part of
the payload.
Example:
< GET /orders
> HTTP/1.1 200 OK
> {
> "items": [
> { "id": "O0000042", "version": 1, "foo": 42, "bar": true },
> { "id": "O0000043", "version": 42, "foo": 24, "bar": false }
> ]
> }
< POST /orders/O0000042
< { "id": "O0000042", "version": 1, "foo": 43, "bar": true }
> HTTP/1.1 204 No Content
or if there was an update since the GET and the version number in the database is higher than the one given in the request body:
> HTTP/1.1 409 Conflict
Pros
-
Perfect optimistic locking
Cons
-
Functionality that belongs into the HTTP header becomes part of the business object
-
Using POST instead of PUT for an update logic (not a problem in itself, but may feel unusual for the consumer)
Last-Modified / If-Unmodified-Since
In HTTP 1.0 there was no ETag and the mechanism used for optimistic locking was based on a date. This is still part of the HTTP protocol and can be used. Every response contains a Last-Modified header with a HTTP date. When requesting an update using a PUT request, the client has to provide this value via the header If-Unmodified-Since. The server rejects the request, if the last modified date of the entity is after the given date in the header.
This effectively catches any situations where a change that happened between GET and PUT would be overwritten. In the case of multiple result entities, the Last-Modified header will be set to the latest date of all the entities. This ensures that any change to any of the entities that happens between GET and PUT will be detectable, without locking the rest of the batch as well.
Example:
< GET /orders
> HTTP/1.1 200 OK
> Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
> {
> "items": [
> { "id": "O0000042", ... },
> { "id": "O0000043", ... }
> ]
> }
< PUT /block/O0000042
< If-Unmodified-Since: Wed, 22 Jul 2009 19:15:56 GMT
< { "id": "O0000042", ... }
> HTTP/1.1 204 No Content
or if there was an update since the GET and the entities last modified is later than the given date:
> HTTP/1.1 412 Precondition failed
Pros
-
Well established approach that has been working for a long time
-
No interference with the business objects; the locking is done via HTTP headers only
-
Very easy to implement
-
No additional request needed when updating an entity of a search endpoint result
Cons
-
If a client communicates with two different instances and their clocks are not perfectly in sync, the locking could potentially fail
Conclusion
We suggest to either use the Last-Modified / If-Unmodified-Since approach or ETags in result entities.
Appendix D: Changelog
This change log only contains major changes made after October 2016.
Non-major changes are editorial-only changes or minor changes of existing guidelines, e.g. adding new error code. Major changes are changes that come with additional obligations, or even change an existing guideline obligation. The latter changes are additionally labeled with "Rule Change" here.
To see a list of all changes, please have a look at the commit list in Github.
Rule Changes
-
2018-09-01:
Forked the Zalando API to create a separate version for 10M GmbH -
2018-06-11:
Introduced new naming guidelines for host, permission, and event names. -
2018-01-10:
Moved meta information related aspects into new chapter Meta Information. -
2018-01-09:
Changed publication requirements for API specifications (Must: Publish OpenAPI Specification). -
2017-12-07:
Added best practices section including discussion about optimistic locking approaches. -
2017-11-28:
Changed OAuth flow example from password to client credentials in Security. -
2017-11-22:
Updated description of X-Tenant-ID header field -
2017-08-22:
Migration to Asciidoc -
2017-07-20:
Be more precise on client vs. server obligations for compatible API extensions. -
2017-06-06:
Made money object guideline clearer. -
2017-05-17:
Added guideline on query parameter collection format. -
2017-05-10:
Added the convention of using RFC2119 to describe guideline levels, and replacedbook.could
withbook.may
. -
2017-03-30:
Added rule that permissions on resources in events must correspond to permissions on API resources -
2017-03-30:
Added rule that APIs should be modelled around business processes -
2017-02-28:
Extended information about how to reference sub-resources and the usage of composite identifiers in the Must: Identify resources and Sub-Resources via Path Segments part. -
2017-02-22:
Added guidance for conditional requests with If-Match/If-None-Match -
2017-02-02:
Added guideline for batch and bulk request -
2017-02-01:
Should: Use Location Header instead of Content-Location Header -
2017-01-18:
Removed "Avoid Javascript Keywords" rule -
2017-01-05:
Clarification on the usage of the term "REST/RESTful" -
2016-12-07:
Introduced "API as a Product" principle -
2016-12-06:
New guideline: "Should Only Use UUIDs If Necessary" -
2016-12-04:
Changed OAuth flow example from implicit to password in Security. -
2016-10-13:
Should: Prefer standard Media type nameapplication/json
-
2016-10-10:
Introduced the changelog. From now on all rule changes on API guidelines will be recorded here.