Breaking Changes: How to Avoid Shipping Them and What to Do When You Must
A breaking change is any modification to an API that requires consumers to update their integration to continue functioning. The definition is straightforward. The practice of avoiding breaking changes while evolving an API requires engineering discipline that most teams underestimate until they have shipped a breaking change to a production API and experienced the resulting incident response.
The instinct to avoid defining something as a breaking change is strong. Removing a field that is not documented is tempting to classify as cleanup rather than a breaking change. Changing an error code from 400 to 422 for a validation failure seems like a trivial semantic correction. Adding a required field to a request body seems like a new feature rather than a breaking change. Each of these reclassifications is wrong, and each produces consumers whose integrations fail after the change.
A Taxonomy of Breaking Changes
Breaking changes in REST APIs fall into predictable categories. Endpoint removal — deleting an endpoint — is the most obvious. Field removal from response bodies — removing a field that consumers may be reading — is the most common. Required field addition to request bodies — requiring consumers to send a field they are not currently sending — is frequently mistaken for a non-breaking addition.
Less obvious breaking changes include: changing a field’s type (string to integer, integer to array), changing a field’s semantics while preserving its name (changing a status field to use different values), changing authentication requirements (requiring a new permission scope), changing error response formats (modifying the structure of error objects that consumers parse), and changing pagination behavior (switching from offset to cursor without maintaining offset support during transition).
Any change that could cause a currently functioning consumer integration to fail is a breaking change. The consumer’s code does not know that the field was undocumented or that the error code change was a correction. It depends on the observed behavior, and a change to that behavior breaks the dependency.
The Additive Evolution Pattern
The most sustainable approach to API evolution is restricting modifications to backward-compatible additions. Adding new optional fields to request bodies, adding new fields to response bodies, adding new endpoints, adding new optional query parameters — these changes can be deployed without affecting any consumer who does not explicitly consume the new capability.
The critical discipline is treating response body additions as safe while treating response body modifications as breaking. Consumers that serialize response bodies to typed objects — which is the correct implementation — will fail if a field’s type changes. Consumers that access response fields by name will fail if a field is removed. Adding new fields does not affect either of these patterns and can proceed without a versioning increment.
Deprecation Timelines
When a breaking change is unavoidable, the correct procedure is deprecation: introducing the new behavior alongside the old, communicating the deprecation to consumers through documentation, API response headers, and direct communication, and then removing the deprecated behavior after a defined timeline.
The deprecation timeline must be long enough for consumers to complete the migration. A 30-day deprecation window is aggressive for production integrations at enterprise organizations where change management processes may take weeks. A 12-month deprecation window for a public API gives consumers sufficient time to prioritize and execute the migration. The timeline communicates how seriously the API provider takes the disruption cost that breaking changes impose on consumers.
The Sunset HTTP header — standardized in RFC 8594 — allows API responses to communicate a deprecation date in a machine-readable format. Consumers that parse this header can implement automated deprecation monitoring. Consumers that do not will still see the header in their logs and API monitoring tools. Surfacing deprecations in the API response is more reliable than documentation updates alone.
When You Ship a Breaking Change Accidentally
Accidental breaking changes — changes that were not intended to be breaking — require a different response than planned deprecations. The immediate action is assessing the impact: which consumers have been affected and how severely. The next action is a fix that restores the previous behavior while communicating the incident to affected consumers. The subsequent action is the post-incident review that identifies how the breaking change was not caught before deployment.
Contract testing — validating the API implementation against the specification on every build — catches breaking changes before they are deployed. API linting tools that compare the new specification to the previous version and flag breaking changes provide a second layer of detection. The teams that ship breaking changes accidentally have gaps in their change detection process. The process needs to close before the next change.