Skip to main content

API Version

Manage the life cycle of the API, handling multiple versions.


Introduction

API versioning is crucial for managing changes, updates, and improvements to APIs over time. Traefik Hub provides robust support for effectively managing multiple API versions while maintaining backward compatibility and supporting existing clients.

The APIVersion Object

The APIVersion object represents a specific version of an API within Traefik Hub. It allows API owners to effectively manage the lifecycle of their APIs.

When creating a new APIVersion object, you must reference it in its parent API's versions field.

You can expose an APIVersion using the same mechanism as exposing an API, either through an Ingress or IngressRoute annotation: hub.traefik.io/api-version.

warning

An API and its APIVersions must be part of the same namespace.

Example

Let's start with an existing API exposed through an Ingress.

apiVersion: hub.traefik.io/v1alpha1
kind: API
metadata:
name: flight-api
namespace: apps
spec:
openApiSpec:
path: /openapi.json

Now, see an example of how you can add two versions to this API, each attached to different Ingresses with the routing mechanism of your choice. Then, the API object only needs to reference its supported versions. Note that the previous API object now references its versions.

Remove the openApiSpec section and reference API Versions
apiVersion: hub.traefik.io/v1alpha1
kind: API
metadata:
name: flight-api
namespace: apps
spec:
# Remove the openApiSpec section
versions: # Reference the versions (assumed to be in the same namespace)
- name: flight-api-v1
- name: flight-api-v2

Title & Description

The APIVersion object supports title and description fields. These fields are shown in the API portal and they override the corresponding values in the API version’s OpenAPI specification.

APIVersion resource with title and description fields defined
apiVersion: hub.traefik.io/v1alpha1
kind: APIVersion
metadata:
name: hello-api-v1
namespace: apps
spec:
title: "Hello API v1.0.0"
description: "A simple API version for greeting users."
release: v1.0.0
openApiSpec:
path: /openapi.yaml

Operation Filtering

Operation filtering allows you to restrict user groups to specific OpenAPI operations.

Here's how it works:

  1. Define OperationSets: OperationSets are named collections of OpenAPI operations. For example, you might create an operationSet called "admin" that includes operations to delete customers and retrieve customer statistics, and another called "support" that includes operations to retrieve a list of all customers.
  2. Configure APIAccess: Once you've defined your operationSets, you can reference them in your APIAccess configurations. For instance, you might grant access to the "admin" operationSet to members of the "admin" group and access to the "support" operationSet to members of the "support" group.

OperationSets

An operationSet is composed of:

  • A name, which will be referenced in the APIAccess.
  • A matcher to select the API operation(s) to be included in the operationSet.

Here are examples of how to define operationSets using different matchers:

Path
# Example of versioning an API using `path` for URI path.
apiVersion: hub.traefik.io/v1alpha1
kind: APIVersion
metadata:
name: my-flights-api-v2
namespace: apps
spec:
release: v2.0.0
title: "An awesome title for this release, like a cheese name"
openApiSpec:
path: /api/v2/openapi.json
operationSets:
- name: flight-operations
matchers:
- path: /flights
note
  • Only one of the following path settings is allowed: path, pathPrefix, or pathRegex.
  • You can use the methods option without specifying a path setting (path, pathPrefix, or pathRegex).
  • You can combine a path setting with the methods option.
  • The pathRegex must match both the OpenAPI specification and the request. For example, the request to /flights/:id shouldn't be expressed by /flights/[0-9]+ but by /flights/.+ to satisfy both /flights/{flightID} in the OAS specification and /flights/2 in the actual user request.
Be aware of the subtle configuration difference

Please note the subtle difference between configuring one matcher with multiple options and configuring multiple matchers. The examples below illustrate the difference in the syntax.

One matcher with two options, both of the options must be aligned
operationSets:
- name: read-pets
matchers:
- pathPrefix: /pets
methods:
- GET

Without a Defined HTTP Method

The following example shows an API CRD using an operation matcher with pathPrefix, without any HTTP method defined. This would allow all HTTP methods matching the /flights path prefix. For example, GET /flights, GET /flights/{id}, POST /flights, PUT /flights/{id}.

Without a Defined HTTP Method
# Example of versioning an API using `pathPrefix` for URI path.
apiVersion: hub.traefik.io/v1alpha1
kind: APIVersion
metadata:
name: my-flights-api-v2
namespace: apps
spec:
release: v2.0.0
title: "An awesome title for this release, like a cheese name"
openApiSpec:
path: /api/v2/openapi.json
operationSets:
- name: flight-operations
matchers:
- pathPrefix: /flights

Every Matcher Option Must Align

The next example shows an API CRD using one matcher with two options. One doing path prefix filtering and one doing method filtering.

Both options must be fulfilled.

Setting two options, one with pathPrefix and the other with methods (act as a logical AND).

This allows GET for all operations matching the /flights path prefix. For example, GET /flights, GET /flights/{id}, GET /flights/findByStatus.

Every Matcher Option Must Align
apiVersion: hub.traefik.io/v1alpha1
kind: APIVersion
metadata:
name: my-flights-api-v2
namespace: apps
spec:
release: v2.0.0
title: "An awesome title for this release, like a cheese name"
openApiSpec:
path: /api/v2/openapi.json
operationSets:
- name: read-flights
matchers:
- pathPrefix: /flights
methods:
- GET

One of the Two Matchers Must Match

The next example shows an API CRD using a combination of two matchers. One doing path prefix filtering and the other doing method filtering.

One of the matchers must fulfil.

Setting two matchers, one with pathPrefix and the other with methods (act as a logical OR).

This allows all operations matching the /flights path prefix. This allows GET operations on all endpoints of the API For example, POST /flights, GET /airports/{id}.

One of the Two Matchers Must Match
apiVersion: hub.traefik.io/v1alpha1
kind: APIVersion
metadata:
name: my-flights-api-v2
namespace: apps
spec:
release: v2.0.0
title: "An awesome title for this release, like a cheese name"
openApiSpec:
path: /api/v2/openapi.json
operationSets:
- name: read-flights
matchers:
- pathPrefix: /flights
- methods:
- GET

validateRequestMethodAndPath

Like the API resource, an APIVersion can enforce only the paths and methods that are explicitly defined in its OpenAPI specification by using validateRequestMethodAndPath.

Enforcing Only Documented Endpoints in an APIVersion
apiVersion: hub.traefik.io/v1alpha1
kind: APIVersion
metadata:
name: flight-api-v2
namespace: apps
spec:
release: v2.0.0
openApiSpec:
path: /openapi.json
validateRequestMethodAndPath: true
info

If validateRequestMethodAndPath is true, requests for any undefined path or method result in 404 Not Found status code. If it is not defined or is false, then the global static setting applies. The global setting is false by default.

Global Static Configuration

Similar to an API, the APIVersion respects the global configuration only if validateRequestMethodAndPath isn’t explicitly set. However, Any per-APIVersion setting overrides the global default.

hub:
apiManagement:
openApi:
validateRequestMethodAndPath: true

CORS

If an API version must be accessed from a different domain than the API Portal or client application, you can enable CORS through the cors field of an APIVersion resource.

info

When the cors field is absent, no CORS headers are applied.

apiVersion: hub.traefik.io/v1alpha1
kind: APIVersion
metadata:
name: my-api-version
namespace: default
spec:
release: v1.0.0
openApiSpec:
path: /openapi.yaml
cors:
allowHeadersList:
- "*"
allowOriginsList:
- "*"
allowMethodsList:
- "*"
exposeHeadersList:
- "*"
addVaryHeader: true
allowCredentials: true
allowOriginListRegex:
- ".*"
maxAge: 86400

Advanced API Versioning Examples

Traefik Hub provides the most flexible API versioning on the market. By leveraging Traefik’s intelligent routing, you can define custom rules that route requests to different APIVersion objects. This enables versioning based on host, URI path, HTTP method, query parameters, headers, and more—even in combination with logical operators ( AND (&&) and OR (||)).

Below are some examples to illustrate how you might route requests to different versions of your API based on various criteria. Each example references a separate APIVersion resource through annotations like hub.traefik.io/api-version.

Path-Based Versioning

The most common approach is to embed the version in the path (e.g., /v1 or /v2).

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: flight-ingress-v2
namespace: apps
annotations:
hub.traefik.io/api-version: flight-api-v2
spec:
rules:
- http:
paths:
- path: /flight/v2
pathType: Prefix
backend:
service:
name: flight-service
port:
number: 8080

Outcome: Requests to http://<HOST>/flight/v2/... will map to the flight-api-v2 resource.

Host-Based Versioning

You can also route by host, so that v1.api.example.com goes to one version and v2.api.example.com goes to another.

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: flight-ir-v2
namespace: apps
annotations:
hub.traefik.io/api-version: flight-api-v2
spec:
entryPoints:
- web
routes:
- match: Host(`v2.api.example.com`)
kind: Rule
services:
- name: flight-service
port: 8080

Outcome: Any request matching Host: v2.api.example.com will be routed to flight-api-v2. Meanwhile, you could have another IngressRoute for v1.api.example.com that references flight-api-v1.

Header-Based Versioning

Sometimes, clients specify the API version via a custom HTTP header. For example, X-API-Version: v2.

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: flight-ir-header
namespace: apps
annotations:
hub.traefik.io/api-version: flight-api-v2
spec:
routes:
- match: Headers(`X-API-Version`, `v2`)
kind: Rule
services:
- name: flight-service
port: 8080

Outcome: Here, any request carrying X-API-Version: v2 in its header is routed to flight-api-v2. Another route could handle X-API-Version: v1.

Content Negotiation / Media Type Versioning

You can also version your API by examining the Accept header for specific media types. In many cases, clients include a version within the media type itself. For example, a request might specify:

Accept: application/vnd.example+json;version=1.0

or

Accept: application/vnd.example.v1+json

Below is an example of an IngressRoute that routes requests to flight-api-v3 if the Accept header matches one of these media types.

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: flight-ir-content-neg
namespace: apps
annotations:
hub.traefik.io/api-version: flight-api-v3
spec:
routes:
- match: Headers(`Accept`, `application/vnd.example+json;version=1.0`) || Headers(`Accept`, `application/vnd.example.v1+json`)
kind: Rule
services:
- name: flight-service
port: 8080

Outcome: Here, the route applies if the Accept header matches either application/vnd.example+json;version=1.0 or application/vnd.example.v1+json. The version is part of the media type, so version information is specified in the request header. You can adjust these rules to include additional media types or other version identifiers.

Query Parameter Versioning

You can also route based on query parameters, for example ?version=v2.

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: flight-ir-query
namespace: apps
annotations:
hub.traefik.io/api-version: flight-api-v2
spec:
routes:
- match: Query(`version`, `v2`)
kind: Rule
services:
- name: flight-service
port: 8080

Outcome: Here, any request to, for instance, /flight?version=v2 goes to flight-api-v2.

Combining Rules & Logical Operators

One of the most powerful aspects of Traefik Hub’s routing is that you can combine matchers with && (logical AND) or || (logical OR). For example, you might require both a host and a header to match:

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: flight-ir-combined
namespace: apps
annotations:
hub.traefik.io/api-version: flight-api-v2
spec:
routes:
- match: Host(`api.example.com`) && Headers(`X-API-Version`, `v2`)
kind: Rule
services:
- name: flight-service
port: 8080

Outcome: Here, the route applies only if the Host is api.example.com and the request includes X-API-Version: v2. You can adjust or expand these conditions to match content type. TLS information, or any other rule Traefik Hub supports.

Complex Multi-Condition Versioning

You can layer multiple rules together to target a single API version. For instance, the example below routes requests to flight-api-v3 only if all of these conditions are true:

  • The host is secure.api.example.com
  • The X-Client-Feature header value includes the word “beta” (regex match)
  • The request uses either POST or PUT, or the query parameter ?feature=beta is present
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: flight-ir-complex
namespace: apps
annotations:
hub.traefik.io/api-version: flight-api-v3
spec:
entryPoints:
- websecure
routes:
- match: Host(`secure.api.example.com`) && HeadersRegex(`X-Client-Feature`,`.*beta.*`) && (Method(`POST`,`PUT`) || Query(`feature`, `beta`))
kind: Rule
services:
- name: flight-service
port: 8080
Internal Version Tracking (Semver)

Although the release field in APIVersion accepts semver-like values (v1.0.0, v2.0.0, etc.), the versioning logic is entirely up to your routing rules. The semver field is for internal tracking and clarity. How requests are routed to a given APIVersion object depends on the Ingress or IngressRoute annotations and the routing criteria you specify.

With Traefik Hub's approach to API versioning, you have the flexibility to implement about any versioning strategy. Whether you need path-based, host-based, or even a custom scheme with specific headers, Traefik Hub can handle it all.