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 with deprecation
We strongly encourage using compatible API extensions and discourage versioning (see {SHOULD} avoid versioning and {MUST} use media type versioning below). The following guidelines for service providers ({SHOULD} prefer compatible extensions) and consumers ({MUST} prepare clients to accept compatible API extensions) 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.
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 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.
-
Consider [250] in case a URL has to change.
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 {RFC-9110}#section-9.3.4[RFC 9110 Section 9.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.
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 of 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 response data:
-
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 {RFC-9110}#section-15[RFC 9110 Section 15]).
-
Follow the redirect when the server returns HTTP status code {301} (Moved Permanently).
-
The OpenAPI specification is not very specific on default extensibility
of objects, and redefines JSON-Schema keywords related to extensibility, like
additionalProperties
. Following our compatibility guidelines, OpenAPI
object definitions are considered open for extension by default as per
Section
5.18 "additionalProperties" of JSON-Schema.
When it comes to OpenAPI, 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 [216].
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.
However, when API versioning is unavoidable, you have to design your multi-version RESTful APIs using media type versioning (see {MUST} not use URL versioning). Media type versioning is less tightly coupled since it supports content negotiation and hence reduces complexity of release management.
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 method only applies to the request and response payload schema, not to URI or method semantics. |
Custom media type format should have the following pattern:
application/x.<custom-media-type>+json;version=<version>
-
<custom-media-type>
is a custom type name, e.g.x.zalando.cart
-
<version>
is a (sequence) number, e.g.2
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
Media type versioning should…
-
Use a custom media type, e.g.
application/x.zalando.cart+json
-
Include media type versions in request and response headers to increase visibility
-
Include
Content-Type
in theVary
header to enable proxy caches to differ between versions
Vary: Content-Type
Note
|
Until an incompatible change is necessary, it is recommended to stay
with the standard application/json media type without versioning.
|
Further reading: API Versioning Has No "Right Way" provides an overview on different versioning approaches to handle breaking changes without being opinionated.
With URL 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 URL versioning, instead we {MUST} use media type versioning
with content negotiation.
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. See [161] for an example.
Maps (see [216]), even though technically objects, are also forbidden as top level data structures, since they don’t support compatible, future extensions.
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 values 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:
delivery_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.
See [240] about enum value naming conventions.
Important: Clients must be prepared for extensions of enums returned with server responses, i.e. must implement a fallback / default behavior to handle unknown new enum values — see {MUST} prepare clients to accept compatible API extensions. API owners must take care to extend enums in a compatible way that does not change the semantics of already existing enum values, for instance, do not split an old enum value into two new enum values.