A small API change can create a large integration failure. Replacing an integer with a string may stop a response parser immediately. Changing what an existing status means can be harder to detect: requests still succeed, but the consumer makes the wrong business decision.
Classifying a change requires looking at what existing consumers send, what they receive, and what they are entitled to assume.
This chapter develops that assessment through a fictional order API, including changes that are clearly breaking, changes that preserve compatibility, and additions whose safety depends on the contract.
An API contract describes the inputs, outputs, and behavior consumers can rely on. A breaking change makes a previously supported interaction fail or invalidates an expectation that contract establishes. A non-breaking change preserves those interactions and expectations without requiring existing consumers to change.
Here, compatibility means backward compatibility: an existing client continues to work with an updated service. It does not mean that a new client can send newly introduced features to an old service. That is a separate compatibility direction.
Assume the order API has independently deployed clients, documented request and response formats, and stated rules for how the interface can expand. Classifications in this chapter apply to those supported clients and rules. There is no universal rule that every addition is safe for every API style or client implementation.
The following diagram shows the interaction that must survive a service update:
A successful network exchange is only part of the test. A client that reads the response successfully but now ships an unpaid order has not remained compatible.
Distinguish contract compatibility from observed client impact. A client might rely on an undocumented identifier pattern even though the contract tells it to treat identifiers as opaque strings. Changing that pattern within the documented bounds may preserve the contract while still disrupting that client. Record both conclusions. Even if the client relies on unsupported behavior, you still need to account for the disruption.
The server accepts requests, while the client accepts responses. That difference explains why the same-looking schema edit can have opposite effects depending on its direction.
Suppose an order-creation request includes quantity, documented as an integer from 1 through 20. Lowering the maximum to 10 rejects previously valid requests, so it is breaking. Raising it to 30 can preserve existing requests, provided their behavior remains unchanged.
Now consider a response field with a documented maximum of 20. Returning 30 expands what a consumer must handle. A client may have a valid constraint based on the original maximum. The server accepting more input and the client receiving more possible output are different changes.
The diagram summarizes this structural test. It does not replace a review of meaning, permissions, or side effects:
The following comparisons assume existing field meanings stay unchanged:
These are starting points, not automatic approvals. If the server later returns a larger request quantity in an order response, the input expansion also creates an output change. Review every place the value can travel, including reads by other clients and emitted events.
Assume an authorized support integration retrieves an order at https://api.store.example/orders/ord_4821. The following is an illustrative HTTP/1.1 exchange over HTTPS. The bearer token is a placeholder.
The response body is the single line in the example, without a trailing newline. For this API, totalAmountMinor is an integer in the currency's minor unit; the example represents USD 129.00. Assume all four fields are required, non-null response fields. The contract also explicitly tells clients to ignore unrecognized response properties.
Removing currency breaks a consumer that needs it to interpret the amount. Renaming totalAmountMinor to amount also breaks the existing lookup unless the API still supports the old field. A cleaner name does not compensate for a missing value.
Changing totalAmountMinor from an integer to a string can break a typed response parser. Keeping it an integer but changing its unit to whole dollars is also breaking: 12900 would have a different financial meaning. Data type compatibility does not establish semantic compatibility, which means preserving what the data and operations mean.
Suppose the API adds an optional estimatedDeliveryDate property. Under the stated rule that clients ignore unrecognized response properties, the addition can be non-breaking if the API preserves existing fields and behavior. A strict parser that rejects extra properties changes the assessment if a client the provider supports uses that parser.
Calling the property optional means it may be absent. It does not automatically mean it may contain null, an empty string, or any date format the service chooses. Each of those possibilities needs its own definition.
Likewise, an existing required field becoming optional is a weakening of the response guarantee. A server cannot justify omitting currency by saying that the new schema permits omission; the old consumer relies on the earlier promise.
An enumeration, or enum, is a set of named values such as pending, shipped, and cancelled. Suppose the original contract lists exactly those three fulfillment states and defines shipped as all physical items having left the warehouse.
Introducing partially_shipped into existing responses expands the set. A consumer with an exhaustive branch for the three original states may reject the new value or leave the order in an undefined workflow. Adding a value is therefore breaking for this closed enumeration.
An extensible enumeration has a different contract: the contract tells clients to expect new values and explains how to handle unknown ones. In that environment, adding a value may be compatible. The fallback must still make business sense. Treating every unknown fulfillment state as shipped would be unsafe because it assumes completion without evidence.
Adding an unused endpoint usually does not alter old interactions. Adding a value to a field that existing clients read immediately does. Both are additions, but their reach is different.
The animation below shows how schema changes can break existing clients.
Some of the most consequential changes leave every field name and type intact. Consumers also rely on defaults, result coverage, state transitions, and when work is complete.
Suppose GET /orders promises all matching orders unless the caller supplies a limit. A new default that returns only 50 changes the meaning of the same request. An export client may silently omit the remaining orders even if the response still contains an orders array.
The edge case matters: a test account with 12 orders will not expose the problem. An account with 51 matching orders will. Compatibility review must exercise data conditions under which the new behavior differs.
The same reasoning applies to sort order when the contract promises it. Changing oldest-first to newest-first can alter processing order or invalidate a consumer's checkpoint logic. If the API explicitly promised no particular order, consumers cannot expect a guaranteed sequence. Still investigate clients you know depend on a specific order.
Assume an order-cancellation operation currently returns 200 OK with an order whose state is already cancelled, and the contract says cancellation is complete at that point. Replacing that result with 202 Accepted and an operation identifier requires a different workflow.
HTTP's 202 indicates acceptance for processing before processing has completed. It does not promise eventual success. The original completion guarantee in this example comes from the API's operation contract, not from 200 alone.
An old client cannot safely treat the new response as completed cancellation. It may need to wait for an outcome before issuing a refund or allowing another action. Both status codes belong to the success class, but the interactions are not interchangeable.
Other changes can alter side effects without changing the response. Enabling customer notifications by default, charging immediately instead of reserving funds, or making cancellation delete an audit record can all invalidate existing workflows. Review what an operation does as well as what it returns.
Reducing a documented upload limit from 10 MB to 2 MB breaks clients that submit files within the original range. Shortening a promised retry-deduplication period can make a formerly safe retry create a duplicate operation.
A slower response is not automatically a contract break whenever latency varies. However, removing a documented latency guarantee or exceeding a supported client's required timeout may make an integration unusable. Record the precise commitment and the measured client effect instead of treating every performance change as either harmless or breaking.
Failure behavior is part of the contract because consumers use it to decide whether to correct input, request permission, retry, or stop.
Suppose an order-creation endpoint accepts customerReference values up to 100 characters and returns a validation error beyond that limit. Reducing the maximum to 40 breaks a previously valid 60-character request. It remains a breaking change even if the new error is clear and well structured.
For existing invalid inputs, machine-readable error behavior matters too. If the documented error code is INVALID_QUANTITY, renaming it to BAD_INPUT can break a client's field-specific correction flow. Rephrasing a human-readable message may be compatible if the contract explicitly makes the stable code, rather than the prose, the basis for automated handling.
Permission changes need the same care. Suppose a partner's existing permission authorizes order reads. Requiring a new permission can make the unchanged request receive 403 Forbidden, meaning the server understands the request but refuses to fulfill it. If the previous contract legitimately promised access, that requirement changes the supported interaction.
Contrast that with fixing a defect that exposed another customer's order despite a documented ownership restriction. The fix restores the intended authorization contract, although callers exploiting or depending on the defect will observe a change. Security remediation may be necessary in either case; necessity and compatibility are separate judgments.
Similarly, an implementation accepting a quantity of zero despite a documented minimum of one is a different case from changing the documented minimum from one to two. Describe whether the change corrects a violation or revises the promise. Then assess actual dependencies rather than using “bug fix” as a blanket claim of compatibility.
Wire compatibility means an existing client can still exchange and interpret messages with the service. Source compatibility concerns whether consumer code still builds and uses the supported programming interface after a library or generated client update.
An SDK can introduce a source break while HTTP behavior remains unchanged. For example, renaming its getOrder method to fetchOrder forces application code to change even if both methods would send the same request. Conversely, leaving every SDK method untouched does not prevent a server-side semantic break.
Consider this assessment as three related checks:
The diagram explains why one passing check cannot establish compatibility for the entire release. A provider distributing an SDK should state whether a review covers the service, the SDK, or both.
For this JSON-over-HTTP API, client parser settings influence which additions clients can read. Other API styles have different serialization and tooling rules. Apply the compatibility rules of the actual protocol and supported tooling rather than transferring a JSON field rule to every interface.
A useful review explains the existing promise, the proposed difference, and a concrete consumer interaction the change would affect. “Non-breaking because it is additive” is not enough evidence.
For the order API, a short classification record might look like this:
Validate the reasoning against representative interactions. Exercise old requests with omitted optional inputs, supported boundary values, empty collections, failure cases, and relevant permissions. For a response addition, run the old supported client against the new payload. For a meaning change, verify the consumer's business decision, not just successful parsing.
A schema comparison can identify removed fields or newly required inputs. It cannot, by itself, detect that shipped has acquired a new meaning or that cancellation now sends an email. Consumer checks provide additional evidence, but a passing sample does not prove that every external integration is safe.
When evidence is missing, record the uncertainty. “Compatibility not established because the supported SDK's enum handling is unknown” is a useful review result. It identifies what the reviewer must check before claiming the change is non-breaking.
There may still be good reasons to make a breaking change. Calling a change breaking identifies its impact. It does not decide how to release it or prevent necessary product and security changes. Changing a version label does not alter whether an existing consumer can use the new contract unchanged.
A non-breaking change preserves supported requests, interpretable responses, and the behavior existing consumers rely on. Removing guarantees, restricting previously valid inputs, adding values to a closed response enum, and changing defaults or meanings can break integrations even when the payload looks familiar.
Assess requests and responses in their respective directions, include errors and permissions, and distinguish service compatibility from SDK compatibility. Classify changes using explicit contract assumptions and concrete consumer behavior, and record uncertainty wherever the evidence is incomplete.