relogiosoft-api-governance-kit

API Versioning

Category: Design Tags: versioning, breaking-change, tolerant-reader, content-negotiation, deprecation, sunset, backward-compatibility


Summary of Rules


Key Concepts

Resource

An entity (or collection of entities) whose identity is given by a URI, and whose schema or representation is given by a media type. The version of the entity is given by an ETag or Last-Modified timestamp.

Breaking Change

A breaking change to a representation is one that results in a new representation that clients cannot process.

Not all changes are breaking. A change is only breaking if clients cannot tolerate it. Clients that are not tolerant readers are at risk of breaking on non-breaking changes.

Tolerant Reader

Tolerant Readers extract only what is needed from a message and ignore the rest. Rather than implementing a strict validation scheme, they make every attempt to continue with message processing when potential schema violations are detected. Exceptions are only thrown when the message structure prevents the reader from continuing, or the content clearly violates business rules. Tolerant Readers ignore new message items, the absence of optional items, and unexpected data values as long as this information does not provide critical input to the service logic.

Daigneau, Rob. Service Design Patterns


Breaking vs Non-Breaking Changes

Change Tolerant (non-breaking) Intolerant (breaking)
Adding a property ✅ Tolerant  
Adding a value to an enum on a request ✅ Tolerant  
Removing a value from an enum on a response ✅ Tolerant  
Removing a property   ❌ Breaking
Changing a property name   ❌ Breaking
Changing a property type   ❌ Breaking
Removing a value from an enum on a request   ❌ Breaking
Adding a value to an enum on a response   ❌ Breaking
Changing a representation’s URI   ❌ Breaking

If a server makes a change that a client is expected to be intolerant to, that change MUST be considered a breaking change.


Versioning Strategies

Preferred: Content-Type Negotiation

Version the media type, not the URI. The resource identity (URI) remains stable.

Client sends:

GET /customers/123 HTTP/1.1
Accept: application/json;v=2

Server responds:

HTTP/1.1 200 OK
Content-Type: application/json;v=2
Vary: Accept

Rules:

Advantages:

Disadvantages:

Alternative: URI Versioning

Modify the URI when making a breaking change that results in a genuinely new resource.

https://api.example.com/v2/customers

Advantages:

Disadvantages:

URI versioning SHOULD only be used when the breaking change represents a genuinely new resource, not merely a changed representation.

Alternative: Custom Header Versioning

Pass the version in a custom request header:

GET /customers/123 HTTP/1.1
x-version: 2

Advantages:

Disadvantages:

Alternative: Redirect Versioning

Redirect clients from an unversioned URI to a versioned one:

GET /customers HTTP/1.1
→ 301 Moved Permanently
   Location: https://api.example.com/v2/customers

Disadvantages:


Documenting Multiple Versions in OpenAPI

Use separate media type entries under the content property:

responses:
  '200':
    content:
      application/json;v=1:
        schema:
          $ref: '#/components/schemas/CustomerV1'
      application/json;v=2:
        schema:
          $ref: '#/components/schemas/CustomerV2'

Deprecation Process

When a resource or version must be deprecated, implement as many of the following as practically possible.

Step 1: Set Deprecation Headers

All responses from the deprecated endpoint MUST include:

Warning: 299 - "This resource is deprecated and will be removed on 2025-12-31."
Sunset: Tue, 31 Dec 2025 23:59:59 GMT
Link: </v2/customers>; rel="successor-version"
Link: <https://developer.example.com/api/deprecation-policy>; rel="sunset"

Client guidance: Clients SHOULD inspect Warning and Sunset headers and log them to aid discovery of deprecated usage.

Step 2: Communicate

API maintainers MUST make a reasonable effort to communicate deprecation through available channels:

Step 3: After End-of-Life

Once the sunset period has expired:

Situation Response
Replacement resource exists MUST return 301 Moved Permanently with Location header pointing to new endpoint
No replacement exists MUST return 410 Gone

Note: Keeping the empty endpoint in code is required for this to work, which has a maintenance cost. However, it significantly aids discovery of replacements for API consumers.

Shortened Sunset Period

If all consumers are known (e.g. via consumer-driven contract testing), the sunset period MAY be shortened or eliminated with agreement from all consumers.


Versioning Decision Flow

flowchart TD
    A["Change required to API"] --> B{"Is the change\na breaking change?"}
    B -- No --> C["Apply change directly\n(no versioning needed)"]
    B -- Yes --> D{"Do any clients\ndepend on this representation?"}
    D -- No --> C
    D -- Yes --> E{"Does the change\nrepresent a new resource?"}
    E -- Yes --> F["Create new URI\n(URI versioning MAY be used)"]
    E -- No --> G["Version the media type\napplication/json;v=N"]
    G --> H["Set Warning + Sunset headers\non old version"]
    H --> I{"Sunset period\nexpired?"}
    I -- Yes, replacement exists --> J["Return 301 with\nLocation to new endpoint"]
    I -- Yes, no replacement --> K["Return 410 Gone"]
    I -- No --> L["Continue serving\nboth versions"]