skip to content

API Versioning

How to evolve an API without breaking clients: path versions, media-type versions, additive-only changes, and a deprecation policy. Interviewers ask because every long-lived API forces this decision and there is no consensus answer to hide behind.

part ofAPI stylesoverview, primer and where to startread it →
on this pageshow

questions

9

For a JSON REST API, which changes to a request or response are backward-compatible (additive) and which break existing clients? Give concrete examples.

level: juniorimportance: must knowfreq 68%

answer

  1. would an un-redeployed client still work?
  2. add is safe, remove/rename/retype is not
  3. optional param + old-behaviour default
  4. units and meaning break silently
  5. status codes and error shape are contract too

basics

~20 s

Additive: new endpoints, new response fields, new optional request parameters, relaxed validation. Breaking: removing or renaming a field, changing its type, units or format, making a parameter required, tightening validation, changing status codes or the error body shape.

solid answer

~40 s

The test is whether an already-deployed client, unchanged, still works. **Additive:** a new endpoint; a new field in a response; a new optional request parameter whose default reproduces old behaviour; accepting more input than before. **Breaking:** removing or renaming a response field; changing a field's type (`"42"` to `42`, scalar to array); changing units or format (seconds to milliseconds); making an optional parameter required; tightening validation; changing which status code an outcome returns; changing the error body shape; changing defaults such as page size or sort order. **Grey zone:** new enum values, new fields inside array elements — safe for tolerant readers, breaking for clients that validate strictly or branch exhaustively. Semantics count as much as syntax: same field, same type, new meaning is a break, and the worst kind, because it fails silently.

go deeper

for a junior

Recall the two lists with examples and state the un-redeployed-client test.

for a middle

Add the grey zone — enums, nested fields, defaults — and explain why the answer depends on client parsing style.

for a senior

Emphasise silent semantic breaks, request-vs-response asymmetry, and enforcing the taxonomy in CI rather than in review.

for a principal

Frame compatibility as a published policy: define what the organisation guarantees, make it machine-checkable, and accept the modelling cost that guarantee implies.

## The single test A change is **breaking** if a client written against the old contract, and not redeployed, stops working correctly. "Correctly" includes being silently wrong, not only crashing. Every rule below is a corollary. ## Additive changes A new endpoint is invisible to existing callers. A new response field is ignored by parsers that map only the fields they know. A new request parameter is safe **only if it is optional and its default reproduces the old behaviour** — an optional `?include=` that quietly changes the default payload is not additive. Relaxing validation (accepting a longer string, a new currency) is additive in the request direction, because no existing valid call becomes invalid. ## Breaking changes **Removal and renaming.** Deleting `customer_name`, or renaming it to `customerName`, blanks the field in every client that reads it. Renaming is removal plus addition, never a rename on the wire. **Type and representation.** `"42"` to `42`, `42` to `42.0`, a scalar to an array, `null` where the client never saw `null`, an ISO-8601 timestamp to an epoch integer. Strongly typed clients throw; weakly typed ones corrupt data. **Units and semantics.** `timeout` in seconds becoming milliseconds, `amount` in cents becoming decimal currency, `status: "active"` narrowing to exclude trial accounts. Nothing in the schema changes, so no tool catches it, and money or timing goes wrong in production. **Requiredness.** Making a formerly optional request field mandatory rejects every existing caller. Conversely, making a response field that was always present now optional breaks anyone who assumed presence. **Protocol-level contract.** Changing 200 to 202 for the same operation, changing 404 to 200-with-empty-body, restructuring the error envelope, or changing a default page size or sort order all change behaviour clients depend on. ## The grey zone Adding an enum value (`status: "paused"`) or a nested field breaks only clients that reject unknown values or validate responses against a strict schema. Whether you treat these as breaking is a **documented policy decision**: publish the rule ("we may add enum values and object fields at any time; clients must ignore unknown ones") and then adding is additive by contract. ## Practice Evolve by adding: introduce `amount_minor` alongside `amount`, dual-write both, migrate consumers, remove the old one only under a deprecation programme. Treat request and response directions separately — request rules loosen, response rules tighten. Encode all of this in machine-checkable schemas so CI, not review, catches violations.

  • Is adding a field to a response ever breaking?
    Yes, when clients validate responses against a closed schema (`additionalProperties: false`), deserialize with fail-on-unknown-properties enabled, or hash/sign the whole payload. It is also breaking if the field's presence changes meaning — for example adding `discount` that consumers must now subtract. Publish an explicit tolerant-reader rule so additions stay safe by contract.
  • Is changing an HTTP status code from 200 to 201 for a creation endpoint breaking?
    Yes. Clients branch on status codes, and many only special-case exact values or treat anything non-200 as a failure path. Even within the 2xx class the change alters observable behaviour, so it belongs in a new version or behind a deprecation cycle rather than shipping as a fix.

saying these in an interview costs you the question

  • Believing anything that keeps the schema valid is non-breaking, ignoring semantic changes like units
  • Treating a rename as safe because the data is unchanged
  • Adding a required request parameter and calling it additive because the endpoint is unchanged
  • Assuming only response changes can break clients
  • Judging compatibility from the server's tests instead of the clients' actual usage

context

open as a page

Compare the main ways to version an HTTP API — a version in the URI path such as /v1/orders, a query parameter, a custom request header, and media-type versioning via the Accept header. What are the trade-offs?

level: middleimportance: must knowfreq 70%

basics

~20 s

URI path versioning is the most visible, cacheable and easiest to route or test in a browser, but versions the whole API. Header and media-type versioning keep URIs stable and allow per-resource versions, at the cost of discoverability, tooling friction and cache correctness needing Vary.

open as a page

An HTTP API version has been switched off permanently. What status code should its endpoints return — 404, 410 Gone, 301, or something else — and what should the response body contain?

level: juniorimportance: should knowfreq 42%

basics

~20 s

Return 410 Gone: the resource existed and is intentionally, permanently removed. Include a machine-readable error body naming the version, the removal date and the replacement. Use 301/308 redirects only when the new endpoint is genuinely equivalent.

open as a page

What is a tolerant-reader client in the context of consuming a JSON HTTP API, and what concrete parsing rules does it follow so that additive server changes do not break it?

level: middleimportance: should knowfreq 48%

basics

~20 s

A tolerant reader ignores unknown fields, reads only what it needs, does not depend on field order, and handles unknown enum values with a default branch instead of failing. That lets the server add fields and values without redeploying clients.

open as a page

How do the HTTP `Deprecation` and `Sunset` response headers work, what do they carry, and how should a client and a provider each use them?

level: middleimportance: should knowfreq 40%

basics

~20 s

Sunset (RFC 8594) gives an HTTP-date after which the resource becomes unresponsive; Deprecation says it is already or will be deprecated. Pair them with Link rel="sunset" or rel="deprecation" to documentation. Clients should log and alert on them.

open as a page

How do you stop an accidental breaking change to a JSON HTTP API from reaching production? Describe the automated checks you would put in a CI pipeline.

level: seniorimportance: should knowfreq 45%

basics

~20 s

Generate the API schema from code, diff it against the released baseline in CI, and fail the build on breaking diffs. Add consumer-driven contract tests so real client expectations are verified, and back both with production traffic analysis for undocumented usage.

open as a page

You need to retire an old version of a public HTTP API that thousands of integrations still call. Walk through how you would run that deprecation from announcement to shutdown.

level: seniorimportance: should knowfreq 45%

basics

~20 s

Instrument usage per client first, announce with a dated timeline and migration guide, emit Deprecation and Sunset headers, chase the remaining callers by name, run short scheduled brownouts near the end, then shut off and return 410 Gone permanently.

open as a page

Should an HTTP API version the whole surface at once or version resources individually, and how do date-based pinned versions (as used by Stripe, where an account is pinned to a version like 2024-06-20) compare with numbered releases?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Whole-API versions are simple to document and route but force migrations on customers whose endpoints did not change. Per-resource versions avoid that at the cost of a large support matrix. Date-based pinning gives fine-grained, continuous evolution with per-account pinning, at the cost of maintaining a transformation chain in the server.

open as a page

When is introducing a new version of an HTTP API contract the right answer, and what would you do instead? How many versions would you support at once?

level: principalimportance: nice to knowfreq 34%

basics

~20 s

Version only when a change cannot be made additively and the model genuinely must change. Prefer new fields, new endpoints or new representations. Support as few concurrent versions as the customer base tolerates — commonly two — and publish that policy up front.

open as a page