The API versioning decision record: why "we'll cross that bridge when we come to it" is an undocumented versioning strategy

Every API has a versioning strategy. The strategy is encoded in the URL pattern, the response schema, the changelog, and the implicit contract with every consumer — whether or not any of this was decided deliberately. Most teams add /v1/ to their first endpoint because that is what APIs do, without documenting what condition would trigger a /v2/. The trigger condition is the entire decision. Without it, every future breaking change forces the team to re-argue from first principles: do we bump the version or do we call this change backwards-compatible? The team that answers this question six times across six different endpoints with six different conclusions has made six inconsistent decisions that collectively constitute a versioning strategy that no single engineer could describe — and that no consumer was told about.

API versioning looks like implementation because the implementation is visible. The /v1/ prefix is in the URL. The Accept: application/vnd.myapi.v2+json header is in the request. The breaking change is in the changelog. What is not visible is the decision behind any of this: why this versioning mechanism and not another, what counts as a change that requires a version bump versus a change that is backwards-compatible by definition, how long old versions are maintained after new versions are released, and what the team owes consumers who were not part of the original decision.

This invisibility matters because API versioning decisions are among the few architectural decisions whose consequences are borne primarily by parties external to the team that made the decision. A database choice, a caching strategy, a framework selection — these decisions affect the team building the system. An API versioning decision affects every consumer of the API, including consumers who joined after the decision was made, consumers who rely on behavior that the team regards as incidental, and consumers who understood the API contract differently from the team that published it. Writing the versioning ADR is not a documentation exercise. It is the act of making the contract legible — to the consumers, to the team's future members, and to the engineers who will be responsible for the next breaking change.

Why "no versioning" is itself a versioning strategy

The team that decides not to version its API has made a versioning decision with specific implications. "No versioning" is a commitment to one of two operational models: either all API consumers are owned by the same team and coordinated changes are feasible, or breaking changes will be communicated directly to all consumers before they are deployed and consumers will migrate before the change goes live. Both of these are real claims about the team's operational capacity that should be documented, because the moment either assumption turns out to be wrong, the team discovers the implicit constraint at the worst possible time.

The discovery moment is usually the first external consumer. An internal API built under the assumption that all consumers are owned by the team operates in a world where the team can change the API and the consumers simultaneously, deploy them together, and have no window where old consumers are calling a new API. This model works until a consumer is not owned by the team — a partner integration, a mobile client with a slow release cycle, a third-party developer who built something on the API because it was accessible. At that point, the "no versioning" decision, which was appropriate for an internal API, becomes a commitment to backwards compatibility that the team may not have intended to make.

The "we'll add versioning when we need it" decision is not a deferral — it is a claim: that the current consumer landscape does not require versioning, and that the team will recognize when the consumer landscape changes enough to require it. The second half of this claim is the part that is never documented. How many external consumers would trigger the addition of versioning? Is a single partner integration enough? Does a public SDK count? What about a mobile app with a six-month release cycle that cannot be updated instantly when a breaking change is deployed? Without a documented trigger condition, the team relies on consensus recognition that "now" is when versioning becomes necessary — a consensus that is harder to reach under the pressure of a breaking change deadline than it would have been in a quiet session before the API was published.

Three categories of API versioning decisions

The versioning strategy for an API is not a single decision — it is a set of related decisions that are usually made at different times, by different people, and in different contexts. The trouble is that these decisions interact. A team that has chosen URL versioning without documenting what constitutes a breaking change has made a partial versioning decision: they know the mechanism but not the trigger. A team that has a clear breaking change definition but no sunset policy for old versions has made a different partial decision: they know when to bump the version but not how long to maintain the previous one. The complete versioning ADR covers all three categories.

The strategy choice. URL versioning, Accept-header versioning, custom header versioning, query parameter versioning, and no versioning are the primary options. Each has a different set of trade-offs that most teams discuss briefly and then resolve by convention rather than by deliberation. URL versioning (/v1/users, /v2/users) makes versions explicit, easy to route, and easy to test in a browser — but it creates multiple URL namespaces that must be maintained concurrently and makes the version a permanent part of every client's integration. Accept-header versioning (Accept: application/vnd.myapi.v2+json) keeps the URL stable and allows the version to be an optional parameter that defaults to the current version — but it is invisible in browser testing, harder to route at the infrastructure layer, and requires consumers to explicitly request old behavior rather than having it implied by the URL they were given. Query parameter versioning (/users?version=2) is the most practical for rapid iteration but the most easily ignored, and it creates ambiguity about whether the version applies to the endpoint, the response format, or the request contract.

The choice between these mechanisms is usually made once and rarely revisited, because changing the versioning mechanism after the API is published is itself a breaking change. The versioning mechanism is a contract term: clients who integrated with the URL-versioned API have the version baked into every URL they store; clients who integrated with the header-versioned API have the Accept header value baked into every request. A switch from URL versioning to header versioning after the first external consumer has integrated is a migration that requires every existing integration to change. This makes the strategy choice high-stakes in a way that is rarely appreciated at the time it is made — the team is choosing the versioning mechanism under low-pressure conditions, before external consumers exist, without the full weight of the commitment they are making visible.

The breaking change definition. What counts as a breaking change? The answer is not obvious and is not consistent across teams that have never documented it. Removing a field is breaking. Adding a required request field is breaking. Changing a field's type from string to integer is breaking. Narrowing the valid values for a field is breaking. Changing a field's documented semantics while keeping its type and name is more subtly breaking — a field that changes from "total including tax" to "total excluding tax" with no name change will break every consumer that relied on the old semantic, but the breakage will be silent, producing incorrect business calculations rather than request failures. Broadening the valid values for a field, adding a new optional response field, adding a new endpoint, adding a new optional request parameter with a sensible default — these are probably backwards-compatible changes, but "probably" is not a versioning policy.

The breaking change definition is the section of the versioning decision that most directly affects day-to-day engineering. An engineer who needs to remove a deprecated field from an API response, change the type of an order status field from string to enum, or rename a field to match a new domain model needs to answer the question: does this require a new version? Without a documented breaking change definition, the engineer makes this determination based on intuition, asks a colleague who makes a different determination based on different intuition, escalates to a tech lead who makes a third determination, and the result is inconsistent API evolution that surprises consumers who were relying on the original contract.

The deprecation and migration timeline. When a new version is released, how long does the old version live? The answer determines what "backwards compatibility" actually means in practice. A team that publishes a /v2/ endpoint without a documented sunset timeline for /v1/ has committed to maintaining /v1/ indefinitely, or has committed to breaking consumers who have not migrated by an unannounced date. Neither of these is what the team intended, but one of them will be what happened when the sunset decision is eventually made.

The deprecation strategy is also an undocumented decision in most APIs: how do consumers learn that a version is deprecated? A Deprecation header in the response, a changelog post, an email to registered developers, a sunset date in the API documentation? How long does the team commit to maintaining deprecated versions — three months, six months, until a specified percentage of traffic has migrated, or until the two largest consumer teams have confirmed migration? These questions are answerable in advance, and the answers shape what commitments the team makes when it publishes a new version. The deprecation of an API version should produce a record that supersedes the original versioning ADR, documenting what triggered the deprecation, what the sunset timeline is, and what migration support the team is providing — the same way a software dependency deprecated in favor of its successor should produce a superseding record explaining why the successor addresses the original dependency's limitations.

The "just in case v1" problem

The most common API versioning scenario is a team that adds /v1/ to the URL prefix of their first API because it seems like good practice, without deciding what condition would trigger a /v2/. This is a partial versioning decision: the team has chosen URL versioning as the mechanism, but they have not decided the trigger condition, the breaking change definition, or the deprecation policy. The /v1/ prefix commits the team to several things that were not deliberated: maintaining the /v1/ URL namespace for the duration of the API's life, eventually publishing a /v2/ if the API changes significantly, and having a definition of "significant change" that justifies a new version. None of these commitments are documented in the commit that added /v1/ to the router.

The /v1/ commit message usually says something like "add API versioning support" or "initial API structure with versioned endpoints." It does not say: "added URL versioning because it is the most explicit mechanism for the current consumer landscape; versioning is being introduced before any external consumers exist so the version number is currently arbitrary; the trigger condition for /v2/ would be a breaking change to an existing endpoint that cannot be made backwards-compatible, specifically the removal or type change of an existing field or the semantic change of an existing endpoint's behavior." That is the ADR. The commit message is not the ADR.

The practical consequence of the "just in case v1" is that the team has a versioning mechanism with no versioning policy. When the first breaking change arrives — a field removal, a type change, a semantic shift — the team discovers that they have never agreed on what makes a change require a version bump. The discussion that ensues is a retroactive attempt to establish the versioning policy that should have been established when the /v1/ prefix was added. That discussion happens under deadline pressure, with consumers waiting on the breaking change, and it produces a less careful decision than would have been made in the quiet before the first version was published. Writing the versioning ADR before publishing the first versioned endpoint is the discipline that forces this deliberation into the correct time — before any consumer has integrated and before any breaking change is imminent.

The breaking change definition gap

The breaking change definition gap is the most operationally costly undocumented versioning decision. It is not a single decision — it is a missing definition that produces inconsistent decisions every time a potential breaking change is evaluated. The gap manifests as the "is this breaking?" debate that repeats across every API change discussion, absorbs engineering time, produces inconsistent outcomes, and occasionally results in a change being deployed as "backwards-compatible" that breaks a consumer who had a different understanding of the contract.

Consider a concrete example: the team needs to change the user.role field from a free-form string to an enum. The current values in production are "admin", "editor", and "viewer". The new enum will be ADMIN, EDITOR, and VIEWER — uppercase, which is the convention for the new type. Is this breaking? It depends on how consumers are using the field. A consumer that does a case-insensitive comparison is not broken. A consumer that does a case-sensitive string match is broken. A consumer that passes the value back to the API in a subsequent request will send the old lowercase value, which the new endpoint will reject if it validates against the enum. The team debating whether this change is breaking is actually debating three different consumer behaviors without knowing which consumers exhibit which behavior.

The versioning ADR's breaking change definition resolves this debate in advance by establishing what the team's obligation is to consumers. A definition that includes "any change to the type, format, or case convention of an existing field is a breaking change" covers this scenario clearly: the role field change is breaking, requires a new version, and the old behavior must be maintained in /v1/ while /v2/ introduces the enum. A definition that includes "changes that maintain the set of valid values are backwards-compatible even if the representation changes" reaches a different conclusion: the role field change is not breaking as long as the enum values map one-to-one to the old string values, and the change can be deployed without a version bump — but the team must inform all consumers of the case change through the changelog, and the consumer who relied on case-sensitive matching was relying on undocumented behavior.

Neither definition is wrong. Both are coherent versioning policies. The problem is not which policy is chosen but that no policy was documented, leaving every "is this breaking?" decision as a fresh debate. API versioning decisions constrain consuming teams who may not have visibility into the producing team's internal deliberations, and the consuming teams have no way to anticipate changes that the producing team regards as backwards-compatible if the producing team's definition of backwards-compatible is not published.

The consumer landscape as the versioning decision's context

The same versioning strategy that is appropriate for an internal API serving two frontend applications is inappropriate for a public API serving hundreds of third-party developers. The versioning ADR's Context section must include the consumer landscape at the time of the decision, because the consumer landscape is what makes the versioning strategy appropriate or inappropriate. A strategy that was correctly chosen for an internal consumer base does not automatically extend to the consumer base that exists three years later after a public API launch.

The new technical leader who inherits an unversioned API with 200 external consumers faces a different problem from the one the original team solved. The original team built the API for an internal consumer base where coordinated deployments were feasible. The new consumer base acquired over three years includes consumers with slow release cycles, consumers in regulated industries who cannot deploy changes quickly, consumers who have built secondary products on top of the API, and consumers who treat undocumented API behavior as part of the contract. None of these consumers existed when the versioning strategy was chosen, and none of them are represented in the versioning decision that was made for the original consumer landscape. The new technical leader's first question — "why doesn't this API have versioning?" — can only be answered by the ADR that explains what consumer landscape existed when the decision was made.

The consumer landscape also changes in the other direction: an API that was built with public versioning because external consumers were anticipated may have only internal consumers after the public launch failed to attract adoption. An elaborate versioning scheme maintained for three years for consumers who never arrived is overhead that the team is paying for an insurance policy that was never needed. The revisitation condition in the versioning ADR — "re-evaluate this versioning strategy if the consumer landscape changes materially from the anticipated public API adoption" — is what would allow the team to recognize this scenario and simplify the versioning overhead without losing track of why it was added in the first place.

Writing the API versioning ADR

The Nygard ADR format applies to API versioning decisions with several important expansions. The standard sections — Context, Decision, Consequences — are insufficient for a versioning decision because the breaking change definition and the deprecation strategy are not captured by any of these sections as they are typically written. The versioning ADR requires an additional section — "Contract Terms" — that makes explicit what consumers are entitled to rely on, and under what conditions that entitlement can change.

The decision-statement title convention for API versioning ADRs should name the mechanism and the consumer landscape, not just the direction of the decision:

Context. The consumer landscape at the time of the decision: who calls this API, how many consumers are external versus internal, what are the release cycle constraints of the largest consumers, and what the team's operational capacity for coordinated deployments is. This section should also state any anticipated changes to the consumer landscape — if the API is expected to become public in the next twelve months, that changes the appropriate versioning strategy significantly and should be in the record so that the "should we add versioning?" question does not have to be re-argued from scratch when the public launch is planned.

Alternatives Considered. Each versioning mechanism that was evaluated, with the specific reason it was not chosen. For a URL versioning decision, the alternatives might include: Accept-header versioning (evaluated and found to be harder to document for developer experience — the accept header is not visible when consumers browse the API in a browser or test with curl without flags; the development team's existing tooling does not parse it for routing); query parameter versioning (evaluated and rejected because it can be accidentally omitted in client requests without producing an immediate error, leading to silent behavior changes when the default version is bumped); no versioning (evaluated and rejected because two of the three existing partner integrations have monthly release cycles and cannot coordinate deployment timing with the API team, making unannounced breaking changes unacceptable).

Decision. The chosen mechanism, the breaking change definition, and the consumer commitments the decision creates. The breaking change definition section should be explicit enough that an engineer evaluating a specific proposed API change can make the "is this breaking?" determination without convening a meeting: "Breaking changes for this API are defined as: removal of an existing field from any response schema; addition of a required field to any request body; change in the type of any existing field (including changes in string format conventions, e.g., case changes); change in the semantic meaning of any existing field, even where the type remains the same; removal or renaming of any existing endpoint; change in the set of valid values for any enum or string field where the change reduces the set (adding valid values is backwards-compatible). Non-breaking changes are: addition of new optional fields to response schemas; addition of new endpoints; addition of new optional request parameters with documented defaults; expansion of the valid value set for existing enum or string fields."

Consequences. The ongoing obligations the versioning decision creates. URL versioning creates a permanent multi-namespace maintenance obligation: every endpoint that exists in /v1/ must continue to exist in /v1/ until the version is formally deprecated and the sunset date has passed, regardless of what changes are made in /v2/. This means that bug fixes to shared logic must be applied to both versions, security patches must be deployed across all active versions simultaneously, and documentation must be maintained for all active versions. The team that adds a /v2/ without acknowledging these consequences is committing to double the maintenance surface for the duration of the transition period without having budgeted for it.

Deprecation policy. The sunset timeline for old versions, the communication mechanism (response headers, email notification, documentation update, API changelog), the minimum notice period, and what "migration support" means — whether the team commits to providing a migration guide, a compatibility layer, a migration script, or only documentation. A deprecation policy that says "old versions are supported for six months after the new version is released, with a Sunset response header added at the three-month mark and email notification to all registered API consumers at three months and at two weeks before sunset" is a specific, enforceable commitment. A deprecation policy that says "we'll keep the old version around for a while" is not a policy.

Revisitation condition. Named triggers for re-evaluating the versioning strategy: "Re-evaluate this versioning strategy if: the first public developer (not an internal employee or a directly contracted partner) adopts the API, because the consumer landscape assumption that underlies this strategy changes materially at that point; the number of active consumer integrations exceeds 20, because the coordination overhead of the current strategy increases non-linearly with consumer count; a consumer requests a versioning mechanism change for a documented integration requirement (regulatory compliance requiring stable API references, tooling requirements for a specific versioning format); or the team's operational capacity for cross-version maintenance changes such that maintaining multiple active versions is no longer feasible."

The implicit contract problem

API versioning decisions create implicit contracts beyond what is explicitly stated in the API documentation. Consumers observe API behavior over time and develop expectations based on that behavior, even when that behavior was never documented as part of the versioning commitment. A field that has always appeared in the response even when the API documentation marks it as optional becomes, in practice, a field that consumers rely on being present. An error code that has always been returned for a specific error condition becomes, in practice, a machine-readable field that consumers parse even if the API documentation says error messages are informational only. An endpoint that has always responded within 200ms becomes, in practice, a performance contract that consumers' timeout configurations depend on.

None of these implicit contracts are captured in the versioning ADR — they cannot be, because they emerge from the API's runtime behavior rather than its design. But the versioning ADR can acknowledge that implicit contracts exist and establish a policy for how they are handled when they conflict with proposed changes. An explicit statement that "runtime behavior that is not documented in the API specification is not part of the versioning contract and may change without a version bump" is a weaker commitment that gives the team more flexibility at the cost of occasional consumer surprises. An explicit statement that "the team treats consistently observed runtime behavior as part of the effective contract even when not formally documented" is a stronger commitment that gives consumers more stability at the cost of the team's flexibility to change observed behavior without a version bump. Either policy is defensible; the absence of a policy is what produces inconsistency.

The API versioning decision is one of the highest-stakes decisions a founding team makes in the first year, precisely because it is so easy to defer. The decision costs nothing to make before the first external consumer, and it costs significant coordination effort to make after the first external consumer has integrated. The founding team that writes a versioning ADR before publishing the first external endpoint is making a two-hour investment that will prevent a twenty-hour retroactive versioning debate when the first breaking change arrives. The founding team that skips the ADR is not saving two hours — it is deferring them to a higher-pressure moment with more stakeholders and less flexibility.

Finding API versioning deliberations in AI chat

API versioning discussions appear in AI chat history at two predictable and structurally distinct points. The first is the API design session that precedes the first external endpoint: a developer asking "should we add /v1/ to our API?", "how should we handle breaking changes?", or "what's the difference between URL versioning and header versioning?" This conversation contains the alternatives evaluation — the specific trade-offs the team considered between versioning mechanisms — and it represents the deliberation that produced the eventual versioning strategy, even if the strategy was documented only as a URL prefix in a router configuration file.

The second is the breaking change deliberation that arrives when the first real breaking change is needed: "we need to remove the user.legacy_plan field, is that breaking?", "can we rename this field to something more accurate?", "what do we owe existing consumers if we change how pagination works?" This conversation is the implicit breaking change definition session — the team is producing a definition through deliberation even if they are not aware that is what they are doing. The outcome of this session establishes precedent for every future breaking change evaluation, and that precedent exists only in the AI chat session and in the eventual (often inconsistent) implementation.

The WhyChose extractor identifies API design sessions through the API design vocabulary: "endpoint," "breaking change," "backwards compatible," "versioning," "consumer," "deprecate," "v1," "v2," "API contract." Versioning-specific sessions are identifiable through the combination of mechanism evaluation language ("URL versioning vs. header versioning," "should we put the version in the URL") and consumer concern language ("what if clients are relying on this," "how much notice do we need to give"). The breaking change definition sessions are identifiable through the evaluation pattern: a specific proposed change, analysis of which consumer behaviors it would break, and a conclusion about whether the change requires a version bump.

The extraction value for API versioning sessions is higher than for most architectural decisions because the AI chat session is often the only place the alternatives evaluation exists. API versioning decisions are typically not in tickets (the ticket says "add API versioning"), not in code comments (the comment says "// v1 API router"), and not in the API documentation (the documentation describes the current version without explaining why the versioning mechanism was chosen). The deliberation content — why URL versioning was chosen over header versioning, what the team considered breaking at the time the strategy was established — is in the AI chat session or it does not exist at all.

The versioning decision's downstream cascade

An API versioning decision is among the few architectural decisions that create obligations for teams other than the one that made the decision. The consuming teams who integrate with the API are bound by the versioning strategy they were given without being party to the deliberation that produced it. A consuming team that integrates with a URL-versioned API has baked /v1/ into every URL they store, every bookmark their users have saved, every integration they have built on top of the API. When the producing team releases /v2/ and schedules /v1/ for sunset, the consuming team's migration cost is determined by the deprecation policy in the producing team's versioning ADR — a document they were probably not given and may not know exists.

This is why API versioning decisions are among the most important decisions to share across team boundaries at the time they are made rather than when they become relevant. A consuming team that knows the producing team's breaking change definition can design their integration to avoid relying on behavior that is not part of the versioning contract. A consuming team that is given the deprecation policy before they integrate knows what migration commitment they are implicitly accepting when they choose to integrate with /v1/. A consuming team that is given neither must rely on the hope that the producing team's policy is reasonable and consistent — which it may not be, particularly if the policy was never documented and has been applied inconsistently across multiple breaking change evaluations.

The API versioning decision is sometimes coupled with an API gateway or API management platform decision — the choice to use Kong, AWS API Gateway, or a custom routing layer. The API gateway makes URL versioning and header versioning mechanically different: some gateways route on URL prefix natively, others require custom routing rules for Accept header evaluation, and others support neither and require version routing in the application layer. An API versioning ADR that does not acknowledge the gateway constraint — or the absence of a gateway — is incomplete, because a future migration from URL versioning to header versioning requires not just changing client code and documentation but changing the routing infrastructure. The constraint is often invisible at the time the versioning decision is made because the gateway is not yet a factor — and it becomes visible at the worst possible moment, when a versioning strategy change is being evaluated and the gateway is discovered to make the alternative mechanism impractical.

What the honest versioning record enables

The API versioning decision record — the one that names the consumer landscape, the breaking change definition, the deprecation policy, and the revisitation condition — enables three things that the URL prefix and the API documentation alone do not.

It enables consistent breaking change evaluations. An engineer evaluating whether a proposed API change requires a version bump can check the breaking change definition in the ADR and make the determination without convening a meeting. The definition makes explicit what the team's obligation is to consumers, which prevents the definition from being re-negotiated under the pressure of each individual change. Over time, consistent breaking change evaluations produce consistent API evolution, which is what allows consumers to trust the versioning contract — they learn that the team's "backwards-compatible" claims are reliable because they can see that the same definition has been applied consistently.

It enables deprecation decisions that consumers can plan for. A team with a documented six-month sunset policy can begin the deprecation of an old version with confidence that consumers have been given the notice they were promised. Consumers who received the versioning ADR at integration time know the deprecation policy they accepted when they chose to integrate, and they can plan their migration accordingly. The team that has not documented its deprecation policy makes every sunset decision in an environment of consumer uncertainty — consumers who do not know the policy cannot plan for migration, and the team must negotiate a sunset timeline with each consumer individually rather than applying a policy that all consumers accepted at integration time.

And it enables the honest conversation about consumer commitments that API teams often avoid. Documenting the versioning decision honestly — naming the consumer landscape that makes the chosen strategy appropriate, the breaking change definition that the team is committing to enforce, and the deprecation policy that sets the terms of the commitment — is the act of making the API contract explicit rather than implicit. The team that publishes a versioning ADR alongside its API documentation is telling consumers: here is what you can rely on, here is how we will tell you when things are changing, and here is how long we will maintain old behavior after a new version is released. That transparency is what allows consumers to make informed integration decisions — which reduces the cost of breaking changes for both the consuming team and the producing team, because consumers who understood the contract are less surprised by the terms when the producing team needs to exercise them.

Further reading