{
  "_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"
}