APIs evolve. New features are added, existing endpoints change, and eventually, breaking changes are unavoidable. API versioning is the practice of managing these changes without breaking existing clients. This article covers the major versioning strategies and when to use each.


The Core Challenge


Once an API is public, changing it is risky. A client might depend on the current response format, and changing it could break the client's application. Versioning gives clients a choice: stay on the old version or upgrade to the new one.


Ideally, changes are backward compatible -- adding fields, relaxing validation, or extending functionality. But sometimes breaking changes are necessary: removing a field, changing a data type, or restructuring an endpoint.


URL Path Versioning


The most common and straightforward approach:



/api/v1/users

/api/v2/users


**Pros:**

  • Extremely simple to implement and understand.
  • Easy to route at the proxy or load balancer level.
  • Visible and explicit -- clients can see the version they are using.
  • Easy to test different versions.

  • **Cons:**

  • URL pollution -- the version is part of the resource identifier, which some argue violates REST principles.
  • Difficult to manage granular versioning (versioning one endpoint without versioning others).
  • Clients tend to hardcode the version in URLs, making upgrades less frequent.

  • **Best for:** Public APIs where simplicity and discoverability are priorities. Used by Twitter, Stripe, and GitHub.


    Query Parameter Versioning


    Include the version as a query parameter:


    
    /api/users?version=1
    
    /api/users?version=2
    
    

    **Pros:**

  • Keeps the URL structure clean.
  • The default (no version) can be the latest version.
  • Easy to implement on the server side.

  • **Cons:**

  • Pollutes query parameters.
  • Easily overlooked by developers.
  • Caching becomes more complex since the same URL with different query parameters may produce different responses.
  • Not suitable for proxy-level routing.

  • **Best for:** Internal APIs or APIs where URL cleanliness is a priority over visibility.


    Header Versioning


    Include the version in an HTTP header:


    
    GET /api/users
    
    Accept: application/vnd.myapi.v1+json
    
    

    Or a custom header:


    
    GET /api/users
    
    X-API-Version: 1
    
    

    **Pros:**

  • Keeps URLs clean and RESTful.
  • The resource is identified by URL, and the representation is negotiated via headers.
  • Content negotiation is a standard HTTP mechanism.

  • **Cons:**

  • Hidden -- developers cannot see the version just by looking at the URL.
  • Harder to test and debug (need to set headers in tools like cURL).
  • More complex routing at the proxy level.
  • Web browsers and simple HTTP clients may not support custom headers well.

  • **Best for:** REST purists who want to follow HTTP semantics strictly. Used by GitHub (in the past) and Twilio.


    Choosing a Strategy


    | Strategy | Visibility | Complexity | RESTful | Caching | Routing |

    |----------|------------|------------|---------|---------|---------|

    | URL Path | High | Low | Moderate | Simple | Simple |

    | Query Param | Medium | Low | Moderate | Complex | Complex |

    | Header | Low | Medium | High | Simple | Complex |


    There is no universally correct choice. Consider your audience:


  • For public APIs with a broad developer audience, URL path versioning is the safest bet. It is simple, visible, and well-understood.
  • For internal APIs, any approach works. Query parameter versioning is the simplest to implement.
  • For RESTful APIs where HTTP semantics matter, header-based versioning aligns with the principles of content negotiation.

  • Semantic Versioning for APIs


    Apply semantic versioning principles to your API:


  • **Major version** (v1, v2): Breaking changes that require client updates.
  • **Minor version** (v1.1): Backward-compatible additions (new endpoints, new optional fields).
  • **Patch version** (v1.1.1): Bug fixes that do not change the API contract.

  • In practice, most APIs only expose the major version and use minor/patch versions internally for documentation purposes.


    Supporting Multiple Versions


    When you support multiple API versions, you need a strategy for maintaining old versions:


    **Deprecation policy.** Announce deprecation well in advance. Give clients at least 6-12 months to migrate. Include deprecation warnings in response headers:


    
    Sunset: Sat, 31 Dec 2026 23:59:59 GMT
    
    Deprecation: true
    
    

    **Minimum version support.** Support a minimum number of active versions (typically 2-3). Drop the oldest when a new one is introduced.


    **Version sunset.** Actually retire old versions. Maintaining versions indefinitely is expensive. Be clear about your timeline and enforce it.


    Implementation Patterns


    **Code branching.** Maintain separate code for each version. Simple but leads to code duplication. Use only for small, stable APIs.


    **Conditional logic.** Single codebase with version checks. More maintainable but can become complex:


    
    if version == 1:
    
        return serialize_v1(data)
    
    elif version == 2:
    
        return serialize_v2(data)
    
    

    **Interceptor/middleware.** Transform responses between versions at the middleware layer. Keeps core logic version-agnostic:


    
    if request_version == 1:
    
        response = transform_to_v1(handler.handle(request))
    
    elif request_version == 2:
    
        response = transform_to_v2(handler.handle(request))
    
    

    **Adapter layer.** Use the adapter pattern to translate between internal models and versioned API responses. The cleanest approach for complex APIs.


    Avoiding Versioning Altogether


    The best versioning strategy is not needing one. Design your API to be extensible from the start:


  • Include optional fields rather than creating new endpoints.
  • Use field selection (GraphQL or query parameter filters).
  • Add new endpoints without modifying existing ones.
  • Return more data than clients need (clients can ignore extra fields).
  • Use wire-format tolerant data structures (maps instead of fixed objects).

  • GraphQL eliminates many versioning concerns by letting clients specify exactly what they need.


    Summary


    API versioning is a practical necessity for evolving APIs. URL path versioning is the most common and practical approach. Support a limited number of versions and communicate deprecation timelines clearly. Design your API to minimize breaking changes through extensibility. When possible, prefer backward-compatible additions over new versions.