What makes an OpenAPI change breaking
An OpenAPI change is breaking when a client built against the old contract can fail against the new one, even though the client's code hasn't changed. That client then sees a rejected request, a missing field, or a value it can't parse.
Ask of every edit whether each correct existing client would still work. The answer often depends on direction, because the same edit can be safe in a request and breaking in a response. The request vs response compatibility guide explains that rule, and the lists below cover the changes to look for.
Changes that break existing clients
Each of these edits breaks at least some clients that worked before:
- Removing an endpoint or an HTTP method. Calls to DELETE /orders/{id} start returning 404 or 405.
- Removing or renaming a response field. Renaming customer_id to customerId is a removal plus an addition, so code that reads customer_id gets nothing.
- Adding a required request parameter or body field, or making an optional one required. Clients that don't send it get a 400 or 422.
- Narrowing an enum that clients send. Dropping express from the allowed shipping methods rejects every request that uses it.
- Changing or tightening a type. Changing an integer to a string breaks clients in either direction. Adding format: uuid, lowering maxLength or adding a pattern can reject request values that used to pass.
- Changing nullability. A response field that may now be null breaks clients that expect a value, and a request field that stops accepting null rejects clients that send it.
- Adding a security requirement to an operation that was public, or requiring a new OAuth scope.
- Renaming a parameter or moving it, for example from the query string to a header, or changing the media type of a response.
Changes that usually don't break clients
Additive changes are usually safe because existing clients never touch the new parts. The exceptions are clients that reject unknown response fields, and generated SDKs that fail on a new response enum value.
These changes are normally safe:
- Adding a new endpoint, or a new method on an existing path
- Adding an optional request parameter or body field
- Adding a response field, for clients that ignore fields they don't recognize
- Loosening request validation, such as accepting a longer string or a new enum value
- Editing descriptions, summaries and examples
- Reordering keys, or moving a schema into components and referencing it with $ref, when the resolved schema is unchanged
Compare resolved schemas instead of text
A line-by-line diff of the YAML is a poor guide. Reordering keys or moving a schema into components produces a large text diff that changes nothing. A single tightened keyword in a shared schema, by contrast, can affect every operation that references it.
A semantic diff parses both documents, resolves references, and compares operations, parameters and schemas one by one, with separate rules for requests and responses. Run it on every pull request that touches the spec, so a breaking change becomes a decision someone makes instead of a surprise.
Compare deployed behavior too. A contract can stay the same while the implementation starts returning a string where the spec promises a number, and only a comparison of real responses catches that.
How to ship a breaking change deliberately
A breaking change is sometimes the right call. Make it on purpose, with a path for existing clients:
- Add before you remove. Introduce the new field or endpoint alongside the old one and populate both until clients have moved.
- Mark what is going away with deprecated: true on the operation, parameter or schema property, and name its replacement in the description.
- Announce it at runtime with the Deprecation header (RFC 9745) and the Sunset header (RFC 8594) on affected responses.
- Put incompatible changes in a new major version, such as /v2/, and keep the old version running for the period you published.
- Remove deprecated operations only after their traffic has dropped and the remaining callers have been contacted.