How should I version my API?
Elimination matrix
engineeringapiversioningarchitecturebackend
Choosing the wrong API versioning strategy creates long-term maintenance pain, breaks client integrations, and can stall product evolution. This elimination tree narrows down the right approach—URI path versioning, header versioning, query parameter versioning, or a no-versioning backwards-compatibility contract—by working through the characteristics of your API, its consumers, and your team's operational constraints.
Overview
Decision Tree
Start: Do you anticipate introducing breaking changes to this API within the next 12 months?
A: Yes, breaking changes are likely or certain
- Outcome: URI Path Versioning (/v1/, /v2/)
- Outcome: Header Versioning (Accept / Custom Header)
- Outcome: Query Parameter Versioning (?version=2)
B: No, we will maintain strict backwards compatibility
- Outcome: No Versioning / Backwards Compatibility Only
- Outcome: URI Path Versioning (/v1/, /v2/)
Machine-Readable JSON (Canonical Model)
View JSON
{
"_meta": {
"schema": "https://www.drawdecisiontree.com/decision-dag.schema.json",
"source": "https://www.drawdecisiontree.com",
"description": "DrawDecisionTree.com is a free tool for building, sharing, and embedding interactive decision trees. This file is the machine-readable export of a published decision tree. The `dsl` field contains the original source in the Decision DAG DSL; the `dag` schema is documented at the URL in `schema` above.",
"links": {
"interactive": "https://www.drawdecisiontree.com/t/drawdecisiontree/engineering-api-versioning.html",
"embed": "https://www.drawdecisiontree.com/embed/path/drawdecisiontree/engineering-api-versioning",
"dsl_reference": "https://www.drawdecisiontree.com/decision-tree-dsl-reference.html",
"guides": "https://www.drawdecisiontree.com/guides",
"schema_docs": "https://www.drawdecisiontree.com/decision-dag.schema.json",
"author_trees": "https://www.drawdecisiontree.com/trees/drawdecisiontree"
},
"generated_at": "2026-05-29T12:05:39.281Z"
},
"author": {
"handle": "drawdecisiontree",
"first_name": "Andrew",
"last_name": null,
"avatar_url": "1d32d828-b6ca-40ec-bdd7-771fe7b9c36a/avatar-1778531481027.svg",
"display_name": "Andrew"
},
"file": {
"id": "23854095-447f-452c-922c-8578f33dd483",
"name": "How should I version my API?",
"public_slug": "engineering-api-versioning",
"updated_at": "2026-05-12T16:53:43.587978+00:00",
"url": "https://www.drawdecisiontree.com/t/drawdecisiontree/engineering-api-versioning.html",
"json_url": "https://www.drawdecisiontree.com/t/drawdecisiontree/engineering-api-versioning/tree.json",
"dsl_url": "https://www.drawdecisiontree.com/t/drawdecisiontree/engineering-api-versioning/tree.dag"
},
"meta": {
"description": "Choosing the wrong API versioning strategy creates long-term maintenance pain, breaks client integrations, and can stall product evolution. This elimination tree narrows down the right approach—URI path versioning, header versioning, query parameter versioning, or a no-versioning backwards-compatibility contract—by working through the characteristics of your API, its consumers, and your team's operational constraints.",
"mode": "elimination",
"entry": "Q1",
"tags": [
"engineering",
"api",
"versioning",
"architecture",
"backend"
],
"image": "https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=1200&q=80"
},
"questions": [
{
"id": "Q1",
"text": "Do you anticipate introducing breaking changes to this API within the next 12 months?"
},
{
"id": "A",
"text": "Yes, breaking changes are likely or certain [URI_PATH, HEADER, QUERY_PARAM]"
},
{
"id": "B",
"text": "No, we will maintain strict backwards compatibility [NO_VERSION, URI_PATH]"
},
{
"id": "Q2",
"text": "Is this a publicly available API consumed by external third-party developers?"
},
{
"id": "A",
"text": "Yes, public or partner-facing API [URI_PATH, HEADER]"
},
{
"id": "B",
"text": "No, internal API consumed by teams within our organisation [URI_PATH, HEADER, QUERY_PARAM]"
},
{
"id": "Q3",
"text": "Can you control when your clients upgrade to a new API version, or do clients upgrade at their own pace?"
},
{
"id": "A",
"text": "We control client upgrades and can coordinate migrations [HEADER, QUERY_PARAM]"
},
{
"id": "B",
"text": "Clients upgrade independently; version selection must be durable and explicit [URI_PATH, HEADER]"
},
{
"id": "Q4",
"text": "Is this a REST-style API, or are you using GraphQL (or another schema-first protocol)?"
},
{
"id": "A",
"text": "REST or REST-like API [URI_PATH, HEADER, QUERY_PARAM]"
},
{
"id": "B",
"text": "GraphQL, gRPC, or schema-first protocol [NO_VERSION]"
}
],
"outcomes": [
{
"id": "URI_PATH",
"label": "URI Path Versioning (/v1/, /v2/)"
},
{
"id": "HEADER",
"label": "Header Versioning (Accept / Custom Header)"
},
{
"id": "QUERY_PARAM",
"label": "Query Parameter Versioning (?version=2)"
},
{
"id": "NO_VERSION",
"label": "No Versioning / Backwards Compatibility Only"
}
],
"dsl": "dag: How should I version my API?\nversion: 1.0.0\nimage: https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=1200&q=80\ndescription: Choosing the wrong API versioning strategy creates long-term maintenance pain, breaks client integrations, and can stall product evolution. This elimination tree narrows down the right approach—URI path versioning, header versioning, query parameter versioning, or a no-versioning backwards-compatibility contract—by working through the characteristics of your API, its consumers, and your team's operational constraints.\ntags: engineering, api, versioning, architecture, backend\nentry: Q1\nmode: elimination\n\nQ1: Do you anticipate introducing breaking changes to this API within the next 12 months?\n hint: A \"breaking change\" is any change that would cause a correctly written existing client to fail or behave incorrectly: removing or renaming fields, changing data types, altering authentication requirements, or modifying required request parameters. If your API is early-stage and the contract is still being discovered, assume breaking changes will occur. If the API surface is tiny, internal, and stable, a no-versioning contract may be viable.\n A: Yes, breaking changes are likely or certain [URI_PATH, HEADER, QUERY_PARAM]\n B: No, we will maintain strict backwards compatibility [NO_VERSION, URI_PATH]\n\nQ2: Is this a publicly available API consumed by external third-party developers?\n hint: Public APIs face a much harder versioning problem than internal ones: you cannot coordinate a migration with consumers you don't know, and breaking changes can destroy developer trust. Internal APIs benefit from the ability to run multi-version deploys briefly and then coordinate a cut-over with a small number of known teams. GraphQL APIs (regardless of audience) often use a different evolution strategy—see the later question.\n A: Yes, public or partner-facing API [URI_PATH, HEADER]\n B: No, internal API consumed by teams within our organisation [URI_PATH, HEADER, QUERY_PARAM]\n\nQ3: Can you control when your clients upgrade to a new API version, or do clients upgrade at their own pace?\n hint: If you own all clients (e.g., your own mobile apps, your own backend services), you can coordinate upgrades and deprecate old versions on a predictable schedule. If clients upgrade at their own pace—common with public APIs, SDKs, or long-lived embedded devices—you need a versioning scheme that makes version selection explicit and durable. Query parameter versioning is often rejected by purists but works well when clients need to select versions programmatically.\n A: We control client upgrades and can coordinate migrations [HEADER, QUERY_PARAM]\n B: Clients upgrade independently; version selection must be durable and explicit [URI_PATH, HEADER]\n\nQ4: Is this a REST-style API, or are you using GraphQL (or another schema-first protocol)?\n hint: REST APIs benefit from explicit version identifiers in the URI or headers because the resource model changes over time. GraphQL is designed for additive, versionless evolution using schema directives (@deprecated, nullable fields) and query-level flexibility—introducing URI versioning on a GraphQL endpoint is almost always the wrong choice. If you are using gRPC or similar, versioning conventions differ again and may require a separate analysis.\n A: REST or REST-like API [URI_PATH, HEADER, QUERY_PARAM]\n B: GraphQL, gRPC, or schema-first protocol [NO_VERSION]\n\n[URI_PATH]: URI Path Versioning (/v1/, /v2/)\n color: #2563eb\n description: URI path versioning (/v1/resources) is the most visible, most cacheable, and most widely understood versioning strategy—making it the default choice for most public and partner-facing REST APIs. Version the base path, not individual endpoints, and treat each major version as a separate API surface with its own documentation, changelog, and deprecation timeline. Establish a formal deprecation policy before launching v1: specify the minimum notice period (typically 12–18 months for public APIs), communicate via developer portal announcements and response headers (Sunset, Deprecation), and provide a migration guide. Run multiple major versions in parallel only as long as necessary—each live version is a maintenance and security obligation.\n code: ENG_APIv_URI\n\n[HEADER]: Header Versioning (Accept / Custom Header)\n color: #7c3aed\n description: Header-based versioning (via the Accept header, e.g., Accept: application/vnd.myapi.v2+json, or a custom header such as API-Version) keeps URIs clean and is preferred by REST purists who treat the URI as a stable resource identifier. The trade-off is reduced discoverability and cacheability—CDNs and HTTP caches must be configured to vary on the relevant header, and many HTTP debugging tools do not surface custom headers prominently. Document the version header requirement on the very first page of your API reference, provide it in every code sample, and return a clear 400 error with a descriptive message when the header is absent or invalid. Ensure your API gateway and load balancer are configured to route based on the header value before releasing.\n code: ENG_APIv_HEADER\n\n[QUERY_PARAM]: Query Parameter Versioning (?version=2)\n color: #ea580c\n description: Query parameter versioning (?api_version=2 or ?v=2) is the most pragmatic option for internal APIs where simplicity of client implementation matters more than REST purity. It is easy to test in a browser, easy to log, and easy to route at the gateway layer without custom header handling. It is generally inappropriate for public APIs because query parameters are included in server logs and analytics pipelines, version identifiers in query strings feel like second-class citizens to external developers, and the approach is not idiomatic HTTP. For internal use, define a canonical parameter name (agree on it across all your services), document a clear default version for clients that omit the parameter, and automate version deprecation warnings in your API response headers.\n code: ENG_APIv_QUERY\n\n[NO_VERSION]: No Versioning / Backwards Compatibility Only\n color: #16a34a\n description: Committing to a no-versioning, always-backwards-compatible contract is a high discipline but high reward approach—it forces rigorous API design and eliminates client migration overhead entirely. This strategy is most viable for GraphQL APIs (where additive schema evolution is the norm), for truly stable internal utility APIs, or where you have complete control over all consumers and can deploy atomically. The discipline required is strict: use @deprecated annotations or equivalent, never remove or rename fields, only add optional fields, and treat every schema change as a potential client impact. Invest in a robust schema linter and CI gate that detects breaking changes automatically; a human-only review process will eventually let a breaking change through.\n code: ENG_APIv_NONE\n"
}DSL Representation
dag: How should I version my API?
version: 1.0.0
image: https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=1200&q=80
description: Choosing the wrong API versioning strategy creates long-term maintenance pain, breaks client integrations, and can stall product evolution. This elimination tree narrows down the right approach—URI path versioning, header versioning, query parameter versioning, or a no-versioning backwards-compatibility contract—by working through the characteristics of your API, its consumers, and your team's operational constraints.
tags: engineering, api, versioning, architecture, backend
entry: Q1
mode: elimination
Q1: Do you anticipate introducing breaking changes to this API within the next 12 months?
hint: A "breaking change" is any change that would cause a correctly written existing client to fail or behave incorrectly: removing or renaming fields, changing data types, altering authentication requirements, or modifying required request parameters. If your API is early-stage and the contract is still being discovered, assume breaking changes will occur. If the API surface is tiny, internal, and stable, a no-versioning contract may be viable.
A: Yes, breaking changes are likely or certain [URI_PATH, HEADER, QUERY_PARAM]
B: No, we will maintain strict backwards compatibility [NO_VERSION, URI_PATH]
Q2: Is this a publicly available API consumed by external third-party developers?
hint: Public APIs face a much harder versioning problem than internal ones: you cannot coordinate a migration with consumers you don't know, and breaking changes can destroy developer trust. Internal APIs benefit from the ability to run multi-version deploys briefly and then coordinate a cut-over with a small number of known teams. GraphQL APIs (regardless of audience) often use a different evolution strategy—see the later question.
A: Yes, public or partner-facing API [URI_PATH, HEADER]
B: No, internal API consumed by teams within our organisation [URI_PATH, HEADER, QUERY_PARAM]
Q3: Can you control when your clients upgrade to a new API version, or do clients upgrade at their own pace?
hint: If you own all clients (e.g., your own mobile apps, your own backend services), you can coordinate upgrades and deprecate old versions on a predictable schedule. If clients upgrade at their own pace—common with public APIs, SDKs, or long-lived embedded devices—you need a versioning scheme that makes version selection explicit and durable. Query parameter versioning is often rejected by purists but works well when clients need to select versions programmatically.
A: We control client upgrades and can coordinate migrations [HEADER, QUERY_PARAM]
B: Clients upgrade independently; version selection must be durable and explicit [URI_PATH, HEADER]
Q4: Is this a REST-style API, or are you using GraphQL (or another schema-first protocol)?
hint: REST APIs benefit from explicit version identifiers in the URI or headers because the resource model changes over time. GraphQL is designed for additive, versionless evolution using schema directives (@deprecated, nullable fields) and query-level flexibility—introducing URI versioning on a GraphQL endpoint is almost always the wrong choice. If you are using gRPC or similar, versioning conventions differ again and may require a separate analysis.
A: REST or REST-like API [URI_PATH, HEADER, QUERY_PARAM]
B: GraphQL, gRPC, or schema-first protocol [NO_VERSION]
[URI_PATH]: URI Path Versioning (/v1/, /v2/)
color: #2563eb
description: URI path versioning (/v1/resources) is the most visible, most cacheable, and most widely understood versioning strategy—making it the default choice for most public and partner-facing REST APIs. Version the base path, not individual endpoints, and treat each major version as a separate API surface with its own documentation, changelog, and deprecation timeline. Establish a formal deprecation policy before launching v1: specify the minimum notice period (typically 12–18 months for public APIs), communicate via developer portal announcements and response headers (Sunset, Deprecation), and provide a migration guide. Run multiple major versions in parallel only as long as necessary—each live version is a maintenance and security obligation.
code: ENG_APIv_URI
[HEADER]: Header Versioning (Accept / Custom Header)
color: #7c3aed
description: Header-based versioning (via the Accept header, e.g., Accept: application/vnd.myapi.v2+json, or a custom header such as API-Version) keeps URIs clean and is preferred by REST purists who treat the URI as a stable resource identifier. The trade-off is reduced discoverability and cacheability—CDNs and HTTP caches must be configured to vary on the relevant header, and many HTTP debugging tools do not surface custom headers prominently. Document the version header requirement on the very first page of your API reference, provide it in every code sample, and return a clear 400 error with a descriptive message when the header is absent or invalid. Ensure your API gateway and load balancer are configured to route based on the header value before releasing.
code: ENG_APIv_HEADER
[QUERY_PARAM]: Query Parameter Versioning (?version=2)
color: #ea580c
description: Query parameter versioning (?api_version=2 or ?v=2) is the most pragmatic option for internal APIs where simplicity of client implementation matters more than REST purity. It is easy to test in a browser, easy to log, and easy to route at the gateway layer without custom header handling. It is generally inappropriate for public APIs because query parameters are included in server logs and analytics pipelines, version identifiers in query strings feel like second-class citizens to external developers, and the approach is not idiomatic HTTP. For internal use, define a canonical parameter name (agree on it across all your services), document a clear default version for clients that omit the parameter, and automate version deprecation warnings in your API response headers.
code: ENG_APIv_QUERY
[NO_VERSION]: No Versioning / Backwards Compatibility Only
color: #16a34a
description: Committing to a no-versioning, always-backwards-compatible contract is a high discipline but high reward approach—it forces rigorous API design and eliminates client migration overhead entirely. This strategy is most viable for GraphQL APIs (where additive schema evolution is the norm), for truly stable internal utility APIs, or where you have complete control over all consumers and can deploy atomically. The discipline required is strict: use @deprecated annotations or equivalent, never remove or rename fields, only add optional fields, and treat every schema change as a potential client impact. Invest in a robust schema linter and CI gate that detects breaking changes automatically; a human-only review process will eventually let a breaking change through.
code: ENG_APIv_NONE
Machine Access
- Static JSON:
/t/drawdecisiontree/engineering-api-versioning/tree.json - Live JSON (SPA):
/json/drawdecisiontree/engineering-api-versioning - Raw DSL:
/t/drawdecisiontree/engineering-api-versioning/tree.dag - Canonical HTML:
/t/drawdecisiontree/engineering-api-versioning.html
Questions in this decision tree
- Do you anticipate introducing breaking changes to this API within the next 12 months?
- Yes, breaking changes are likely or certain [URI_PATH, HEADER, QUERY_PARAM]
- No, we will maintain strict backwards compatibility [NO_VERSION, URI_PATH]
- Is this a publicly available API consumed by external third-party developers?
- Yes, public or partner-facing API [URI_PATH, HEADER]
- No, internal API consumed by teams within our organisation [URI_PATH, HEADER, QUERY_PARAM]
- Can you control when your clients upgrade to a new API version, or do clients upgrade at their own pace?
- We control client upgrades and can coordinate migrations [HEADER, QUERY_PARAM]
- Clients upgrade independently; version selection must be durable and explicit [URI_PATH, HEADER]
- Is this a REST-style API, or are you using GraphQL (or another schema-first protocol)?
- REST or REST-like API [URI_PATH, HEADER, QUERY_PARAM]
- GraphQL, gRPC, or schema-first protocol [NO_VERSION]
Possible outcomes
- URI Path Versioning (/v1/, /v2/)
- Header Versioning (Accept / Custom Header)
- Query Parameter Versioning (?version=2)
- No Versioning / Backwards Compatibility Only
How to use this decision tree
Click "Open interactive version" to step through the questions. Your answers narrow the tree until a recommended outcome is reached. You can also embed this tree on your own site.
More decision trees by Andrew
Which API design pattern is right for my project?
Determine the right API design style for your integration scenario.
Authentication Method Selection
Authentication is a security-critical, high-friction decision to reverse — migrating users from one auth method to another requires coordinated password resets or credential migration campaigns. This tree eliminates methods that don't match your user type, enterprise requirements, and security posture, giving you a clear shortlist before you write a line of code.
Caching Strategy Selection
Premature or misapplied caching adds complexity — stale data bugs, invalidation logic, and distributed consistency problems — without solving the actual bottleneck. This tree routes you to the caching pattern that matches your data access profile, so you apply the right tool to the right problem rather than defaulting to Redis for everything.
CI/CD Pipeline Tool Selection
Choosing a CI/CD platform is a long-term infrastructure commitment — pipelines accumulate config, custom scripts, and team muscle memory that make switching painful. This tree eliminates tools that don't fit your source control host, infrastructure model, or team scale, leaving only the options genuinely viable for your situation.
Which cloud provider should I use — AWS, Azure, or Google Cloud?
Answer a few questions to identify the most suitable cloud platform for your workload.
Container Orchestration Platform Selection
Container orchestration is foundational infrastructure — the platform you choose shapes how you deploy, scale, network, and operate every service you run. This tree eliminates options that don't match your operational maturity, cloud provider commitment, and workload complexity, so you land on the platform that fits your team today without over-engineering for a scale you haven't reached.