A deployed API serves clients that do not all upgrade at once. A field rename, status change, or removed enum value can break a browser bundle still cached by a user, even if the server and newest code deploy together. Document which fields are required, which are optional, and how unknown values are handled. Add a new field before removing the old one, dual-read or dual-write during a defined compatibility window, and measure use of the old shape before contracting it. A version number is useful when behavior changes cannot be made compatible, but it does not excuse undocumented breaking changes within a version. Test both old and new clients against the intermediate server state.
API Evolution and Compatibility Windows
Working case
The inspection API returns reviewer_name, while the new interface expects reviewer.displayName. If the server removes reviewer_name in one release, a cached old script renders an empty owner label. An expand step returns both shapes for a short period and records which client shape is requested. A new client reads the nested field and tolerates an absent value until migration completes. After the old bundle's supported lifetime passes and telemetry shows no use, the server can remove the old field in a planned contract step. The same discipline applies to write payloads and error responses.
Implementation boundary
function inspectionOwnerLabel(apiRecord) {
const modernLabel = apiRecord.reviewer?.displayName;
if (typeof modernLabel === "string" && modernLabel.trim()) return modernLabel;
if (typeof apiRecord.reviewer_name === "string") return apiRecord.reviewer_name;
return "Reviewer unavailable";
}Cost and boundaries
Temporary fields increase payload size and server branching; dual writes can add storage and reconciliation work. A staged change costs more releases than a direct rename, but it prevents failures in cached browser code, mobile clients, and integrations that upgrade later. Compatibility tests should target the real request/response shapes, not one shared type definition that makes both sides wrong in the same way. Set a measurable removal condition and a support window, or the temporary bridge becomes permanent complexity.
Failure trace
The team deploys the new UI and server within minutes, sees green tests, and deletes the old response field. A user who opened the page before deployment keeps the old script in memory and fetches the new response after clicking Next; the page crashes mid-task. Another client sends an old enum value that the new server now rejects. Exercise long-lived browser sessions and old request fixtures before removal. A release is complete only when the compatibility window and exit criteria are explicit.
Verification
- Run the old browser bundle against the expanded server response and complete a full case read.
- Run the new client against the old and intermediate shapes; confirm its fallback is intentional.
- Remove the old field in staging and make a saved old-client contract test fail before production.
Decision note
Choose compatibility by default for additive changes. Introduce an explicit new version only when the behavior cannot be made compatible without misleading clients, and document how both versions will retire.
Common Mistakes
- Do not assume all clients update with the server deploy.
- Do not let a shared mock replace a real compatibility test.
- Do not leave a dual-shape bridge without a removal criterion.
Connected lessons
Integration and Verification; Signed Webhook Delivery and Replay Control; Browser Journey and Fault-Injection Tests; Field and Lab Performance Evidence; Tests across boundaries: assert behavior, not implementation text; Expand-and-Contract Schema Migrations; HTTP caching: validate a changed representation with an ETag; Continuous Integration and Release Gates.
Apply and check
Build Project: webhook contract and browser verification and review Web Development: identity and integration contracts quiz.
Further connections
TypeScript and Runtime API Boundaries.
Further connections
Structured API Errors and Recovery.
Further connections
GraphQL Schema Nullability and Evolution; GraphQL Mutation Errors and Pagination Contracts.
Further connections
API Contract Evolution and Client Migration; Response Shape Evolution and Unknown Values.
