Breaking Changes in APIs: How to Spot Them Before You Ship Them
by Eric Hanson, Backend Developer at Clean Systems Consulting
The change that looked safe
A team adds a new required field to a request body. It is a new feature — why would adding a field break anything? The field is required going forward, so of course existing requests without it should fail.
Except existing clients were not told about this field. Their requests still go out without it. Now they are getting 400 Bad Request on a payload that worked yesterday.
This is the category of breaking change that causes the most incidents: not the obviously destructive changes like removing endpoints or renaming fields, but the ones that look like additions or clarifications.
A taxonomy of breaking changes
Removals (obviously breaking):
- Removing an endpoint
- Removing a field from a response
- Removing a supported HTTP method
- Removing a valid enum value from input
Modifications (obviously breaking):
- Renaming a field
- Changing a field's data type (
string→integer) - Changing a field from optional to required
- Narrowing the accepted value range (
0–1000→0–100)
Additions that are breaking:
- Adding a required field to a request
- Adding a new mandatory query parameter
- Making a previously ignored field now validated and rejected
- Changing authentication requirements on a previously public endpoint
Behavioral changes (subtle):
- Changing the sort order of a response array that clients depend on
- Changing the format of a field value (
2026-04-19→1745654400) - Changing which HTTP status code an error returns (
400→422) - Changing the semantics of a status field without renaming it
The behavioral category is the hardest to catch because nothing in the schema changed — only the runtime behavior did.
How to catch them before shipping
Schema diffing in CI. If you maintain an OpenAPI spec (and you should), run a diff tool on every pull request. Tools like oasdiff (open source, Go) or Optic can classify changes as breaking or non-breaking and block the merge if breaking changes appear on a non-versioned endpoint.
oasdiff breaking api-v1-main.yaml api-v1-branch.yaml --fail-on ERR
oasdiff distinguishes between ERR (breaking) and WARN (potentially breaking). Configure your CI to fail on ERR and require explicit review for WARN.
Consumer-driven contract tests. Tools like Pact let API consumers publish contracts — assertions about what they expect from each endpoint. The provider runs these contracts in CI. If a provider change breaks a consumer contract, the test fails before either party ships.
This is particularly effective for internal microservice APIs where you control both sides. For public APIs, it does not scale, but schema diffing covers most of the same ground.
Changelog review as a merge gate. For teams without CI tooling yet: require that any PR touching API response or request shapes includes an entry in a BREAKING_CHANGES.md file. The act of writing it forces the author to categorize the change explicitly. Code review catches the cases they missed.
The enum problem deserves special attention
Enums are a common source of unexpected breakage in both directions. Adding a new value to a response enum is usually safe — unless the client has a strict parser that rejects unknown values. Many typed languages and strict JSON parsers do exactly this:
type Status = 'active' | 'inactive' | 'suspended';
// If you add 'pending' on the server, this throws at runtime
The safe pattern is to document that clients must treat unknown enum values as unexpected but valid (i.e., handle them without crashing). Include this in your API contract documentation. Consider adding a warning to clients in the Sunset header pattern when you expand enums, even though expanding is not technically breaking by strict definition.
Removing an enum value from input is unambiguously breaking. Any client using that value now gets a validation error. This requires a version bump.
What to do with a breaking change you already need to make
If you have genuinely identified a breaking change that is necessary:
- Introduce the new behavior under a new version or feature flag
- Run both behaviors in parallel for a defined transition period
- Notify existing clients with the
DeprecationandSunsetheaders - Monitor adoption of the new behavior before retiring the old one
The temptation is to treat internal or low-traffic APIs as exempt from this process. Resist it. The number of clients is not the metric that matters — the cost of an unannounced breaking change to even one client who depends on the old behavior is always higher than the cost of following the deprecation process.