How to review API changes without breaking clients
Othman Shareef · August 11, 2026 · 6 min read · The Craft of Code Review
Most API diffs look harmless. A field added here, a validation tightened there, an error message reworded. The code is correct, the tests pass, and a week later a mobile release from three months ago starts crashing on a response it no longer understands. To review API changes well, you have to stop asking whether the code is right and start asking a different question entirely: who consumes this contract, and can any of them break? That is blast-radius review in its purest form, because the blast radius of an API change is every client you cannot redeploy.
Review API changes as contracts, not code
The implementation tells you what the server does now. The contract is what consumers were promised, and the promise includes everything they could observe: field names, types, nullability, ordering guarantees, status codes, error bodies, timeouts, and pagination behavior. The first review pass should ignore the handler logic and enumerate consumers instead: the web app, the mobile apps pinned to old releases, the partner integrations, the internal service three teams over, the cron job nobody remembers. For each one: can it break, and would anyone know before their users do? If the author cannot list the consumers, that is the review finding, and it outranks anything in the code.
Sort every change into additive or breaking
Before reading a line of implementation, classify the contract diff. Additive changes are safe by construction for well-behaved clients: a new endpoint, a new optional request field, a new response field that old clients will ignore. Breaking changes need a migration story:
- Removing or renaming anything. A rename is a removal plus an addition; the removal is the part that breaks.
- Changing a type. Including the sneaky ones: integer to string IDs, timestamps changing format, a scalar becoming an array.
- Tightening validation. Requests that used to succeed now fail. Old clients cannot know the new rules.
- Changing defaults or semantics. Same field, same type, different meaning: the schema validates and the behavior still breaks.
Breaking changes also deserve isolation. Following the logic of small, single-purpose changes, a breaking contract change buried inside a feature PR is the worst of both worlds: too easy to miss in review, too hard to revert alone. Ask for it as its own PR.
The quiet breakers: serialization, nullability, error shapes
The changes that survive review and still break clients are rarely the loud ones. They are the ones that keep the diff small and the schema technically valid. A field that was always present starts being omitted when empty, and a client doing response.items.length throws. A column becomes nullable in the database and the API dutifully starts returning null where a string always lived. A date serializer upgrade adds milliseconds to timestamps and a strict parser somewhere downstream rejects them. Error responses are the most neglected contract of all: clients parse error bodies to decide whether to retry, refresh a token, or show a message, so changing an error shape or swapping a 400 for a 422 is as breaking as renaming a field. When the diff touches a serializer, a nullable annotation, or an error handler, slow down: that is where the incident lives.
Versioning and deprecation are review items
When a change is genuinely breaking, the review question becomes: what is the path for existing clients? Acceptable answers are a new version (path, header, or field-level), or an explicit expand-and-contract migration: ship the new shape alongside the old, migrate consumers, then remove. Either way, removal needs a deprecation sequence, and every step of it is checkable in review: is the old behavior marked deprecated in the spec? Is there a sunset date? Is there telemetry counting calls to the old shape, so removal day is a data decision instead of a hope? A PR that deletes a field with no usage numbers attached should not pass review, no matter how confident the author is that nobody uses it. Somebody always uses it.
Generated clients, specs, and docs ride along
If the API has an OpenAPI spec, a GraphQL schema, or generated client libraries, those artifacts are part of the change and belong in the same PR. Two reasons. First, drift: a spec updated “in a follow-up” is a spec that lies for a sprint. Second, evidence: the regenerated client diff is the best breaking-change detector you have. You do not read generated code line by line; you scan it for deletions, because every deleted method or field in a generated client is a consumer-visible removal. Docs follow the same rule as tests: if behavior changed and the docs did not, the PR is incomplete. Make the contract artifacts a standing item on your review checklist so they stop depending on reviewer memory.
None of this makes API review slower once it becomes habit. It reorders it. Enumerate consumers, classify the change, hunt the quiet breakers, demand a deprecation path, check the artifacts. The handler logic, the part most reviews start and end with, comes last, because it is the only part of an API change the compiler and tests already have covered.