API Breaking Changes: A Complete Checklist for REST and JSON Schema
What counts as a breaking change, why request and response schemas follow different rules, and how to version safely without breaking clients.
Most engineers treat API changes with a binary mindset: "if I change the schema, it might break." The reality is more precise. Backward compatibility is an asymmetric problem — the rules shift depending on whether you are modifying a request (what the client sends) or a response (what the server returns).
Failing to recognise this asymmetry is how most "minor" deployments become emergency rollbacks. Use this api breaking change checklist to audit PRs before they reach production.
The asymmetry problem
The golden rule of API evolution: be conservative in what you send, and liberal in what you accept.
When you change a response schema, you are the producer. Remove a field a client expects, and their parser throws. Clear break.
When you change a request schema, you are the consumer. Remove a field from your required list, and old clients continue sending it — your server ignores it. The client's write operation still succeeds. Understanding this is the first step in avoiding accidental outages.
The complete checklist
Response schema changes (server → client)
| Change | Breaking? | Why |
|---|---|---|
| Remove a field | Yes | Clients expecting the field get `undefined` or null pointer |
| Add an optional field | No | Well-implemented clients ignore unknown fields (Postel's Law) |
Change type (int → string) | Yes | Type-safe languages fail to deserialise |
| Add an enum value | Yes | Clients using exhaustive `switch` hit unhandled cases |
| Remove an enum value | Yes | Clients using that value to represent state lose that state |
| Narrow validation (shorter `maxLength`) | No | Client receives data that still fits |
| Widen validation (longer `maxLength`) | No | Client receives more data; typically handles it fine |
Request schema changes (client → server)
| Change | Breaking? | Why |
|---|---|---|
| Remove a required field | No | Old clients still send it; server ignores the extra field |
| Add a required field | Yes | Existing clients don't send it → `400 Bad Request` |
| Add an optional field | No | Existing clients omit it; server uses default |
| Narrow validation (`max 100` → `max 50`) | Yes | Clients sending `75` worked yesterday; they receive errors today |
| Widen validation (`max 50` → `max 100`) | No | All previously valid requests remain valid |
| Change type | Yes | Server fails to parse the request body |
The optional-to-required trap
The most common junior mistake: moving a field from optional to required in a request schema. This happens during feature hardening when a "nice-to-have" field becomes required for new business logic.
Per Semantic Versioning 2.0.0, any change that breaks the existing contract requires a major version bump. Adding a required request field is the textbook definition.
Before (v1.0.0):
{
"type": "object",
"properties": {
"order_id": { "type": "string" },
"callback_url": { "type": "string", "format": "uri" }
},
"required": ["order_id"]
}After — the breaking change:
{
"type": "object",
"properties": {
"order_id": { "type": "string" },
"callback_url": { "type": "string", "format": "uri" }
},
"required": ["order_id", "callback_url"]
}Every mobile app or third-party integration that hasn't been updated to send callback_url immediately receives 400 Bad Request. To avoid this, either keep the field optional at the schema level and handle the missing case in application code, or release a v2 endpoint.
oneOf and polymorphism
Modern APIs use oneOf, anyOf, or allOf for polymorphic responses. Adding a new variant to a oneOf list in a response is a breaking change for many clients.
If a client uses exhaustive pattern matching (Swift enums, Rust match, TypeScript discriminated unions), they must handle every possible variant. Adding "Type C" to a response that previously returned only "Type A" or "Type B" causes the client's code to fail or hit a catch-all that wasn't designed for real data processing.
The JSON Schema specification notes that oneOf requires exactly one subschema to be valid. Adding to this list changes the shape of the valid data set — a fundamental contract modification.
In request schemas, adding a oneOf variant is safe — the server becomes more permissive and old clients continue sending the variants they know.
Deprecation that actually works
If your api breaking change checklist confirms a breaking change is unavoidable, you need a deprecation cycle. Marking a field @deprecated in documentation is not sufficient.
The Sunset header (RFC 8594)
Use the Sunset header to communicate exactly when a version will be decommissioned. Automated tooling can then flag upcoming outages to client developers.
Sunset: Wed, 31 Dec 2026 23:59:59 GMT
Link: <https://docs.yourapi.com/migration>; rel="deprecation"Monitor before removing
Never remove a field based on assumption. Instrument your logs to track usage of deprecated fields. If you observe traffic on a deprecated endpoint or field, you cannot remove it without a coordinated migration — regardless of what your documentation says the deprecation deadline is.
A maintenance-free way to catch breaking changes before they leave your IDE is the API Breaking Change Checker — paste two JSON Schemas and see exactly which changes are breaking and why.
Related tool
API Breaking Change Checker →Paste two JSON Schemas and instantly see which changes are breaking, which are safe, and why. No signup, no CLI.