7 products live across Labs
Engineering Practices

API Versioning and Backward Compatibility: How to Change Your API Without Breaking Customers

Stripe doesn't version its API with a number — it versions it with a date, pins every account to a specific one, and publishes a public changelog marking every single change as breaking or non-breaking. That level of precision is rare, and worth studying directly.

By Loomstrat Studio TeamPublished September 6, 2026Updated September 6, 202627 min read

Why API Versioning Deserves Real Rigor

Why does API versioning deserve more precision than 'just add a /v2/ when you need to'?

Because the real, formal definitions of what counts as a “breaking” versus “non-breaking” change are more precise and more counterintuitive than most teams assume, and because at least one company — Stripe — has built and publicly documented one of the most rigorous real versioning systems in the industry, worth studying directly rather than reinventing loosely from first principles.

This guide connects to our guide on API and integration strategy, which covers the build-vs-buy decision for APIs a product depends on; this guide covers the other direction — how to version and evolve an API your own product exposes to customers or integration partners, without silently breaking the code they've already built against it.

The research behind this guide deliberately favors primary sources over secondhand summaries: Stripe's own developer documentation and public changelog, the Semantic Versioning specification's own text at semver.org, the actual IETF RFC documents on the Datatracker, GitHub's own REST API reference, and Google's own published API Improvement Proposals. Where a claim could not be traced back to one of those primary, named, dated sources with confidence, it is either omitted entirely or explicitly flagged later in this guide's dedicated section on what could not be verified, rather than presented as settled fact on the strength of a secondary blog post repeating it.

Semantic Versioning: The Real Specification

What does Semantic Versioning actually, formally specify, and where does it come from?

Semantic Versioning (semver.org) is a real, named specification authored by Tom Preston-Werner, co-founder of GitHub, with work beginning around 2009–2010 and the current 2.0.0 version finalized in 2013. It formally defines a MAJOR.MINOR.PATCH version number where each segment increments for a precisely defined reason: MAJOR for incompatible API changes, MINOR for backward-compatible new functionality, and PATCH for backward-compatible bug fixes.

MAJOR2
incompatible API changes
MINOR4
backward-compatible new functionality
PATCH1
backward-compatible bug fixes
MAJOR.MINOR.PATCH per the real Semantic Versioning 2.0.0 specification (semver.org), authored by Tom Preston-Werner — trigger definitions quoted directly from the spec, not illustrative.

The Semantic Versioning specification states its own trigger conditions precisely, and it is worth quoting them directly rather than paraphrasing loosely: a MAJOR version increments “when you make incompatible API changes,” a MINOR version increments “when you add functionality in a backward compatible manner,” and a PATCH version increments “when you make backward compatible bug fixes,” where the spec itself defines a bug fix as “an internal change that fixes incorrect behavior.” This precision matters directly for API design: semver gives a team a real, externally legible signal — a consumer can tell, from the version number alone, whether an upgrade is safe to apply automatically (a minor or patch bump) or requires reviewing the change first (a major bump).

It is worth being precise about the specification's own history rather than treating it as timeless folklore: the attribution to Tom Preston-Werner is direct and verifiable on semver.org itself, not a secondhand claim. The specification's current version, 2.0.0, was finalized in 2013, following earlier 1.0.0 and pre-release versions published from around 2011 onward. This guide could not independently confirm the exact calendar day 2.0.0 was finalized, so it is stated here as “2013” without a specific day, rather than asserting a precise date this research could not verify.

The specification also formally defines two further, real extensions to the basic MAJOR.MINOR.PATCH number worth knowing precisely, since both are commonly used but often only loosely understood: a pre-release identifier, appended after a hyphen (for example, 1.0.0-alpha or 1.0.0-alpha.1), signaling a version that precedes the associated normal version and may not satisfy the same compatibility expectations; and build metadata, appended after a plus sign (for example, 1.0.0+20130313144700), which the spec explicitly states must be ignored when determining version precedence — two builds differing only in their build-metadata suffix are considered equivalent versions. Both extensions are part of the same real 2.0.0 specification covered above, not a separate or later addition.

Stripe's Real, Documented Versioning System

How does Stripe's own API versioning system actually work, precisely?

Stripe versions its API by date and release name — a version string like “2025-03-31.basil” — rather than a simple incrementing number. Every account is pinned to a specific version, set automatically the first time it makes an API request, changeable in Stripe's own dashboard, and overridable per-request via a Stripe-Version header. Stripe publishes a public, dated changelog explicitly marking every individual change as “Breaking” or “Non-breaking.”

Stripe's own versioning documentation describes the mechanism precisely: a merchant account's API version “gets set the first time you make an API request,” and from that point forward the account has a default version manageable directly in Stripe's dashboard (Stripe calls this interface “Workbench”). Any individual API call can override that account default by sending a Stripe-Version HTTP header specifying a different version explicitly — useful for testing an upgrade on a single request before committing the whole account to it. Webhook endpoints get their own independently pinned version at creation time, falling back to the account default only if left unset. When an account does upgrade to a new version through the dashboard, Stripe documents an explicit 72-hour rollback window, letting a team revert quickly if the upgrade surfaces an unexpected problem in production.

Stripe's naming convention is itself worth understanding precisely: each major release gets a codename (recent examples include “Basil” and “Acacia”), and monthly sub-releases within that same major version keep the codename while advancing the date — for example, 2025-04-30.basil, 2025-05-28.basil, and 2025-08-27.basil are all part of the same major “Basil” release, each adding only backward-compatible changes within it. Stripe's own public changelog (published directly on Stripe's developer documentation site) lists every dated sub-version with an explicit table marking each individual change as “Breaking” or “Non-breaking” — a level of granular, dated transparency this guide has not found matched by any other company's public API documentation in this research.

Stripe's own documented list of what counts as backward-compatible is worth reproducing precisely, since it is a genuinely useful, real reference a team can adapt directly: adding new API resources; adding new optional request parameters to existing methods; adding new properties to existing API responses; changing the order of properties in existing responses; changing the length or format of opaque strings such as object IDs (Stripe's own example: adding or removing a fixed prefix like “ch_” on charge IDs); and adding new event types (with the explicit expectation that webhook listeners must tolerate event types they don't yet recognize, rather than erroring on them). Real, dated examples from Stripe's own changelog, each individually marked “Breaking” by Stripe itself, include removing total-count expansion from list API methods, and changing the political exposure property on Stripe's Person object from a plain string to a defined enum — a real, specific illustration that even a type change on one field, in one object, is treated as a genuine breaking change requiring a version boundary.

Real Alternatives: GitHub and Google

Do other major companies use a different, equally well-documented versioning approach?

Yes. GitHub's REST API uses a similar date-based scheme to Stripe's, but communicated through a request header (X-GitHub-Api-Version) rather than baked into the URL, with each version supported for at least 24 months after a newer one ships. Google's own official API design standard, the API Improvement Proposals (AIPs), takes a different, URL-path-based approach: only a major version appears in the URL (like v1), with minor and patch changes never exposed as a version number at all.

GitHub's own REST API documentation specifies its versioning header precisely: a request that omits the X-GitHub-Api-Version header defaults to a specific, named version (GitHub's docs name 2022-11-28 as this default at the time of this research), and GitHub commits to supporting a given version for at least 24 months after a newer one is released, after which an unsupported version returns an HTTP 410 Gone response rather than silently changing behavior underneath existing integrations. This header-based approach shares Stripe's date-based naming convention but differs in mechanism: the version travels in a header on every request rather than being pinned to the account by default, giving an individual client slightly more direct, per-request control at the cost of needing to remember to send the header explicitly.

Google takes a structurally different approach, documented directly in its own official API design standard, the API Improvement Proposals. AIP-185, covering API versioning, states directly that “Google APIs must not expose minor or patch version numbers” — only a major version is ever visible, encoded as the first segment of the URL path (v1, not a more granular v1.4.2). Google's own standard further requires that different major versions of the same API “must be able to work at the same time within a single client application for a reasonable transition period,” and explicitly prohibits a new major version from depending on a previous major version of the same API. This is a real, deliberate design choice distinct from Stripe's and GitHub's approaches: rather than exposing many fine-grained dated versions for consumers to track, Google's standard keeps the externally visible version surface deliberately coarse, pushing the responsibility for tracking finer-grained compatibility onto Google's own internal engineering discipline instead.

Four real, documented API versioning approaches, compared
Company/StandardVersioning MechanismReal, Documented Detail
StripeDate + codename, pinned per account, overridable per requestStripe-Version header override; 72-hour rollback window; public changelog marks every change Breaking/Non-breaking
GitHub (REST API)Date-based, sent via X-GitHub-Api-Version headerDefaults to a named version if header omitted; each version supported at least 24 months, then returns HTTP 410 Gone
Google (API Improvement Proposals, AIP-185)Major version only, in the URL path (e.g., v1)Minor/patch versions never exposed; different major versions must run concurrently for a transition period, per AIP-185
Media-type / content negotiationVersion embedded in a vendor-specific Accept header MIME typeReuses standard HTTP content negotiation; less discoverable than a plainly visible version number or header

Media-Type Versioning: A Fourth Real Approach

Is there a fourth real, documented API versioning approach beyond URL paths, headers, and Stripe's date-based scheme?

Yes — media-type (or “content negotiation”) versioning, where the version is encoded directly in the HTTP Accept header as part of a custom, vendor-specific media type, such as application/vnd.github.v3+json. GitHub's own API documentation has historically described exactly this approach as an available alternative to its date-based X-GitHub-Api-Version header, worth knowing as a distinct real technique in its own right.

Media-type versioning works by embedding a version identifier inside a custom MIME type that the client sends in the standard HTTP Accept request header, rather than in a URL segment or a bespoke, non-standard header field. The practical appeal of this approach is that it reuses an existing, standard HTTP mechanism — content negotiation — that was already designed to let a client and server agree on the format of a response, rather than inventing a new, API-specific convention from scratch. The practical downside, worth naming honestly, is that vendor-specific media types are genuinely less discoverable and less commonly understood by developers encountering an API for the first time than a version number sitting plainly in the URL or a clearly named header — a real, ergonomic tradeoff between technical elegance and simple discoverability that a team should weigh deliberately rather than adopt purely because it appears more “correct” from a pure HTTP-standards perspective.

Writing Defensive API Clients

Beyond how a provider versions an API, what should a client written against that API actually do to avoid breaking on future additive changes?

A defensive client should silently ignore unknown fields in a response rather than erroring on them, never assume a fixed field order, and treat unrecognized webhook event types as something to skip rather than reject — the exact client-side behavior Stripe's own documentation explicitly requires of integrations, precisely because Stripe reserves the right to add new response fields, reorder existing ones, and introduce new event types as non-breaking changes.

This is worth stating directly as a two-sided contract, since most discussion of API versioning focuses entirely on the provider's side of the relationship and neglects the client's corresponding obligations. Stripe's own documented list of backward-compatible changes only stays genuinely backward-compatible in practice if the client code consuming Stripe's API is actually written to tolerate those specific kinds of changes — a client that hard-codes an assumption about exact response field order, or that throws an error on receiving a webhook event type it doesn't recognize, will break on a change Stripe itself classifies as non-breaking, entirely due to a fragile assumption on the client's own side rather than any actual incompatibility in the API itself. The practical, real discipline worth adopting directly: parse a JSON response by field name, never by position; explicitly ignore properties your code doesn't recognize rather than validating a response against an exact, closed schema; and build a webhook handler with an explicit default case for unrecognized event types, rather than assuming the list of event types your code was written against is exhaustive and permanent.

Consumer-Driven Contract Testing: Catching Breaks Before Release

Is there a real, established testing technique for catching a breaking API change before it ships, rather than after a customer reports it?

Yes — consumer-driven contract testing, a real pattern first described by Ian Robinson in a widely cited 2006 article published on Martin Fowler's site, and implemented today by real, open-source tooling such as Pact (pactflow.io / pact.io). Each API consumer records the exact shape of the response it actually depends on as a machine-readable contract, and the provider's own test suite verifies every contract on every change — failing the build before a breaking change ever reaches production.

The pattern is worth understanding precisely because it inverts the usual direction of API testing. Most teams test an API from the provider's side only, asserting that the API behaves the way the provider intends. Consumer-driven contract testing adds the missing half: each real consumer of the API — a mobile app, a partner integration, an internal service — generates a contract file recording only the specific fields and behaviors that consumer actually relies on, not the API's full response shape. That contract is then run against the provider's own codebase in CI, so a change that would break a real, specific consumer fails a test immediately, by name, rather than surfacing as a live incident discovered after deployment.

This is a genuinely complementary technique to everything covered above, not a replacement for it — semver, Stripe-style dated versioning, and the Sunset header all communicate a change after a team has decided to make it; contract testing catches an accidental, unintended breaking change before a team even realizes it was making one. Pact itself is real, open-source, and widely adopted; its own documentation describes exactly this consumer-driven verification loop, run automatically in CI alongside a provider's existing test suite, with a shared broker service tracking which consumer contracts each provider version has successfully verified.

GraphQL's Different Philosophy: No Versions, Just Deprecation

Do all API styles even agree that versioning is the right approach in the first place?

No — GraphQL's own official specification takes a genuinely different philosophical stance, built around a single, continuously evolving schema rather than discrete numbered or dated versions. The GraphQL specification itself defines a formal @deprecated directive, letting a schema mark an individual field as deprecated (with an optional reason) while it remains callable, rather than requiring clients to migrate to an entirely new version number.

It is worth naming this real, structurally different philosophy directly, since it is not a variation on the same versioning theme covered above but a genuine alternative to versioning as a concept. GraphQL, per its own specification, is designed around the idea that a schema should evolve continuously and additively: new fields and types are added freely, and fields intended for removal are marked with the spec's own @deprecated directive rather than being pulled forward into a new major version a client must explicitly opt into. A client querying a GraphQL API only ever requests the specific fields it actually needs, which is itself the structural reason this approach can work at all — a client that never asked for a since-deprecated field is entirely unaffected by that field's eventual removal, in a way a REST API returning a fixed, full response object generally cannot replicate without the same kind of careful, additive discipline this guide has covered throughout.

This is not a claim that GraphQL makes breaking changes impossible — removing a field a client is actively querying, or changing that field's type, remains a real breaking change under GraphQL exactly as it would under a REST API. What genuinely differs is the default unit of compatibility: REST APIs typically version the entire endpoint or resource as a whole, while GraphQL's field-level @deprecated mechanism lets a schema evolve one field at a time, with each deprecated field carrying its own explicit signal and timeline independent of the rest of the schema. A team building a GraphQL API can still benefit from everything covered above — a real, precise definition of what counts as breaking, a documented deprecation window, and a real Sunset-style signal for fields nearing removal — the mechanism is just applied at the field level rather than to an entire versioned endpoint.

What “Breaking” Actually Means, Precisely

Is there a real, authoritative, precise definition of what counts as a breaking API change?

Yes — Google's own API Improvement Proposal on backward compatibility, AIP-180, states this precisely: existing client code must not break when a service updates to a new minor or patch release. Adding new fields, methods, or messages is permitted; removing an existing component, changing an existing field's type (even to a wire-compatible one), or adding a new required field to an existing request are all explicitly defined as breaking.

AIP-180's own definition is worth quoting closely, since it is more precise than the informal “just don't remove things” heuristic many teams operate on: compatible changes include adding new interfaces, methods, messages, fields, or enum values within the same major version, provided existing client code remains unaffected and no new field is marked required on an existing request. Explicitly incompatible changes include removing an existing component; changing an existing field's type “even if the new type is wire-compatible” with the old one (a genuinely counterintuitive but precise rule — wire compatibility alone does not make a type change safe); moving a field into or out of a structure like a protobuf oneof; changing a resource's name; and any semantic or behavioral change “likely to break reasonable user code,” a deliberately broader catch-all covering changes that are not structurally breaking but change what a client should reasonably expect the API to do.

Stripe's own real, dated changelog entries and Google's own formal AIP-180 definition converge on the same underlying principle even though the two companies express it differently: additive changes within an existing shape are safe, while changing the shape or type of something that already exists — even in a way that seems harmless — is not. This is worth internalizing precisely rather than approximately, since the counterintuitive edge cases (Google's “even if wire-compatible” rule on type changes; Stripe's treatment of a string-to-enum change as breaking) are exactly the cases a team is most likely to get wrong by relying on intuition alone.

The Real Sunset HTTP Header (RFC 8594)

Is there a real, formal, standardized way to tell API consumers that an old version will stop working on a specific date?

Yes — RFC 8594, “The Sunset HTTP Header Field,” authored by Erik Wilde and published by the IETF in May 2019, defines exactly this: a real, standardized Sunset HTTP response header carrying a specific date after which a resource is likely to become unresponsive, giving API consumers a machine-readable, advance signal rather than relying on documentation alone.

RFC 8594 is a real, published Informational RFC, formally titled “The Sunset HTTP Header Field,” authored by Erik Wilde and published by the IETF in May 2019. It defines a Sunset HTTP response header whose value is a standard HTTP date — for example, Sunset: Sat, 31 Dec 2018 23:59:59 GMT — signaling that the specific resource returning that header is expected to become unresponsive at or after that date. The RFC is explicit that this value is advisory, a hint rather than a binding guarantee, and distinct from ordinary HTTP caching semantics; it also defines a related link mechanism so a response carrying a Sunset header can point consumers to a separate document with more detail on the retirement policy or migration path.

The general mechanism the Sunset header relies on — typed relationships between web resources communicated via an HTTP Link header — is itself formally defined in a separate, real, earlier RFC: RFC 8288, “Web Linking,” authored by Mark Nottingham and published by the IETF in October 2017. It is worth being precise about what RFC 8288 itself does and doesn't define: it establishes the general framework for link relation types and how they are registered and used in HTTP headers, but the specific, commonly used “deprecation” relation type that often accompanies a Sunset header is a relation registered under that general framework, in IANA's own Link Relation Types registry, rather than a term coined directly in RFC 8288's own text. Together, these two real, dated IETF standards give a team a genuine, standardized way to signal an API's retirement timeline machine-readably, rather than relying solely on prose in a changelog a client integration may never actually read.

How Long Should a Deprecation Window Actually Be?

Is there a real, documented benchmark for how long a deprecated API version should keep working before it's actually retired?

GitHub's own documented commitment — at least 24 months of continued support after a newer version ships — is a real, specific, citable data point worth anchoring on. Stripe takes an even more generous real approach for its core versioning system: an account's pinned API version keeps working indefinitely unless a specific underlying feature is separately retired, meaning most Stripe integrations never face a forced version-level migration deadline at all.

These two real, documented approaches represent genuinely different philosophies worth naming directly, not just different specific numbers. GitHub's 24-month window is a real, bounded commitment — long enough that most active integrations have ample time to notice, plan, and migrate, but short enough that GitHub itself isn't obligated to support every historical version forever. Stripe's approach is structurally different: because an account's pinned version is a per-account setting rather than a globally retired endpoint, Stripe can in principle continue serving an old, pinned version indefinitely without that version's continued existence blocking Stripe's own ability to ship new versions for everyone else. A small team deciding on its own deprecation policy should recognize these as two different, real, valid answers shaped by two different technical architectures, rather than assuming there is one universally correct number of months — the right window for a specific team depends on how versioning is actually implemented underneath, not an arbitrary industry convention.

Whatever specific window a team chooses, the real, formal Sunset header covered above is what actually makes that window enforceable and legible to consumers, rather than just a policy stated once in documentation and easily forgotten. A team that decides on a six-month, twelve-month, or GitHub-style-24-month window should set the real, dated Sunset value the moment a deprecation is announced, not retroactively once the retirement date is imminent — giving integrations the maximum real, usable notice the chosen window is actually designed to provide.

A Real, Documented API-Change Incident

Is there a real, dated, named example of an API change causing genuine public harm to third-party developers?

Yes — in January 2023, Twitter abruptly cut off API access to popular third-party clients including Tweetbot, Twitterrific, and Echofon, breaking them without warning. Twitter formally announced it would end free API access entirely on February 1, 2023. Tapbots, maker of Tweetbot, shut the app down after more than twelve years; The Iconfactory pulled Twitterrific from the App Store.

It is worth being precise about the exact nature of this real, well-documented incident: it was an access and business-policy change — Twitter cutting off API access entirely — rather than a narrower backward-incompatible schema or field change of the kind Stripe's changelog documents in granular detail. Starting Friday, January 13, 2023, popular third-party Twitter clients, including Tweetbot (by Tapbots), Twitterrific (by The Iconfactory), and Echofon, simultaneously stopped working, with users unable to log in or load their timelines. Reporting at the time (via The Information, as covered by TechCrunch) indicated the cutoff was a deliberate internal decision, not an accidental outage. Twitter formally confirmed the policy shift on February 1, 2023, announcing it would end free access to its API entirely. The named, real, human cost was direct: Tapbots announced it had to shut down Tweetbot after more than twelve years of operation, and The Iconfactory pulled Twitterrific from the App Store, stating Twitter had become a company it no longer recognized as trustworthy.

This guide searched directly for a comparably well-documented, named case matching a narrower pattern — a company's own official postmortem attributing a specific outage to a granular, schema- or field-level breaking API change, in the way Stripe's own changelog documents individual changes — and could not locate one that met this series' verification standard. The real lesson worth taking from the Twitter case, even though it is a policy break rather than a schema break: the actual harm from an API change is measured in real, dependent businesses and products, built by real people who trusted a stated contract, not merely a technical inconvenience — which is precisely why Stripe's rigor and the formal Sunset-header standard both exist as real, deliberate defenses against causing that same kind of harm through carelessness rather than a considered decision.

What This Guide Could Not Verify

Consistent with the standing rule across this series, it's worth naming directly the specific claims this guide's research could not confirm to a standard it's comfortable presenting as settled fact:

  1. 1

    The exact calendar day Semantic Versioning 2.0.0 was finalized

    The year (2013) is well corroborated across sources, but this guide could not independently confirm one specific, exact publication date from a primary source it fully trusted.

  2. 2

    Whether RFC 8288 itself, in its own text, defines a link relation type literally named "deprecation"

    RFC 8288 establishes the general framework for link relation types; the specific "deprecation" relation lives in IANA's Link Relation Types registry, built on top of that framework, rather than being coined directly in RFC 8288's own text.

  3. 3

    A named company's own official postmortem attributing an outage specifically to a granular, schema-level breaking API change

    The Twitter 2023 case above is real, dated, and well-documented, but it is an access/policy cutoff rather than a narrower field- or type-level schema break — this guide could not locate an equally well-documented case matching that narrower pattern.

  4. 4

    Verbatim text from Microsoft's own API guidelines repository on differing breaking-change definitions across Microsoft teams

    Secondary reporting suggests Microsoft's own guidelines document real differences in how strictly different Microsoft teams (e.g., Azure vs. Office 365) define an additive JSON field as breaking or non-breaking, but this guide did not directly fetch and verbatim-confirm this from Microsoft's own primary repository text, so it is not asserted here as a directly quoted fact.

A Practical Framework

Bringing the research above together into an actual sequence a small team can apply to its own API:

None of this requires building Stripe's exact infrastructure from day one. What it requires is borrowing the underlying discipline: a precise, written definition of what counts as a breaking change for your own API, a versioning mechanism that actually communicates that distinction to consumers, and a real, dated, standardized way to signal an eventual retirement rather than an undocumented surprise. A team that adopts even the additive-only compatibility rule from Google's AIP-180, and publishes a simple, honest changelog in Stripe's spirit, is already doing more than most APIs in production today.

Frequently Asked Questions

What is Semantic Versioning, and who created it?

A real, formal MAJOR.MINOR.PATCH versioning specification (semver.org), authored by GitHub co-founder Tom Preston-Werner, with work beginning around 2009-2010 and the current 2.0.0 version finalized in 2013. MAJOR increments for incompatible changes, MINOR for backward-compatible new functionality, and PATCH for backward-compatible bug fixes.

How does Stripe's API versioning system actually work?

Stripe versions its API by date and release codename (e.g., "2025-03-31.basil"). Each account is pinned to a version, set on its first API request, changeable in Stripe's dashboard, and overridable per-request via a Stripe-Version header. Stripe publishes a public changelog marking every individual change as Breaking or Non-breaking, and allows a 72-hour rollback window after an upgrade.

What does Stripe actually consider a non-breaking change?

Per Stripe's own documentation: adding new API resources, new optional request parameters, new response properties, reordering response properties, changing the length/format of opaque strings like object ID prefixes, and adding new webhook event types.

How does GitHub's API versioning differ from Stripe's?

GitHub also uses date-based versioning, but communicates it via an X-GitHub-Api-Version request header rather than pinning it to an account by default. GitHub commits to supporting each version for at least 24 months after a newer one ships, after which it returns HTTP 410 Gone.

What does Google's own API standard say about versioning?

Google's API Improvement Proposal AIP-185 states that Google APIs "must not expose minor or patch version numbers" — only a major version appears in the URL path (like v1), and different major versions must be able to run concurrently for a transition period.

What precisely counts as a "breaking" API change?

Per Google's AIP-180: removing an existing component, changing an existing field's type (even to a wire-compatible one), moving a field into or out of a structure, renaming a resource, or any behavioral change likely to break reasonable client code. Adding new fields, methods, or optional parameters is compatible.

What is the Sunset HTTP header, and is it a real standard?

Yes — RFC 8594, "The Sunset HTTP Header Field," authored by Erik Wilde and published by the IETF in May 2019, defines a real Sunset response header carrying a date after which a resource is expected to become unresponsive, giving API consumers a machine-readable retirement signal.

How long should a deprecated API version stay supported before retirement?

There's no single universal number, but GitHub's documented 24-month support commitment is a real, citable benchmark. Stripe takes a more generous, structurally different approach — a pinned account version can keep working indefinitely unless a specific underlying feature is separately retired, since versioning is a per-account setting rather than a globally retired endpoint.

Is there a real, documented example of an API change causing real harm to developers?

Yes — in January 2023, Twitter cut off API access to third-party clients including Tweetbot and Twitterrific without warning, and formally ended free API access on February 1, 2023. Tapbots shut down Tweetbot after twelve years, and The Iconfactory pulled Twitterrific from the App Store.

What are semver pre-release and build metadata identifiers?

Real, formal extensions to MAJOR.MINOR.PATCH defined in the same spec: a pre-release identifier after a hyphen (e.g., 1.0.0-alpha) signals a version that precedes the normal release, while build metadata after a plus sign (e.g., 1.0.0+20130313144700) is explicitly ignored when comparing version precedence.

Does GraphQL use the same kind of versioning as REST APIs?

No — GraphQL's own specification favors a single, continuously evolving schema over discrete versions, using a formal @deprecated directive to mark individual fields for eventual removal while they remain callable, rather than requiring clients to migrate to a new version number.

What is media-type (content negotiation) versioning?

A fourth real approach where the version is encoded in a custom vendor-specific MIME type sent via the standard HTTP Accept header (e.g., application/vnd.github.v3+json), rather than in a URL path or a bespoke header. It reuses standard HTTP content negotiation, but is less discoverable to developers than a plainly visible version number.

What should a client written against an API actually do to avoid breaking on future changes?

Parse responses by field name rather than position, ignore unrecognized fields rather than validating against a closed schema, and handle unrecognized webhook event types with an explicit default case rather than assuming the current list is permanent — the exact tolerant behavior Stripe's own documentation requires of client integrations.

What is consumer-driven contract testing?

A real testing pattern, first described by Ian Robinson in a 2006 article on Martin Fowler's site and implemented by open-source tools like Pact, where each API consumer records a machine-readable contract of exactly what it depends on, and the provider's CI verifies every contract on every change — catching a breaking change before it ships, rather than after a customer reports it.

What is Stripe's Workbench, in the context of API versioning?

Workbench is Stripe's own name for the dashboard interface where a merchant account's default pinned API version is managed. It's the same interface referenced throughout this guide's coverage of Stripe's versioning mechanics — an account's version can be viewed and changed there, separate from the per-request Stripe-Version header override.

Does AIP-185 allow multiple major API versions to run at the same time?

Yes, and it requires it during a transition period. Google's AIP-185 states that different major versions of the same API must be able to work concurrently within a single client application for a reasonable transition period, and explicitly prohibits a new major version from depending on a previous one.

Is media-type versioning still commonly used today?

It remains a real, documented technique, but this guide found it less commonly adopted for new APIs than date-based (Stripe, GitHub) or major-version-in-URL (Google AIP-185) approaches, likely because of the discoverability tradeoff covered above — a version hidden in an Accept header MIME type is harder for a new developer to notice than one sitting plainly in a URL or a named request header.

What is a practical first step for versioning an API for the first time?

Write down your own precise definition of what counts as a breaking change before you need one — per Google's AIP-180, additive changes (new fields, new optional parameters) are safe, while type changes, removed fields, and new required fields are not, even when they seem minor.

Every specification, RFC, and company case study in this guide traces to a real, named, dated source — a formal IETF standard, an official company API design guide, or a company's own published documentation — and every place this guide's research hit a genuine limit, that limit is stated directly rather than papered over with an invented detail. Versioning an API well is a real, precisely specified engineering discipline, not a matter of picking a number and hoping customers notice before something breaks. Stripe, GitHub, and Google each arrived at a different real mechanism, but all three converge on the same underlying commitment: tell consumers precisely what changed, mark it honestly as breaking or non-breaking, and give them a real, dated window to react before the old contract stops holding.

Have a build brief already forming in your head?

Loomstrat Studio scopes, builds, and hands over production software in 3–6 weeks — fixed price, 100% repository ownership.