Skip to main content

a6-recipe-api-versioning

Overview

API versioning allows you to evolve your API without breaking existing clients. APISIX supports multiple versioning strategies through routing rules, header matching, and traffic splitting — all configurable via the a6 CLI.

Strategies covered:

  1. URI path versioning/v1/users, /v2/users
  2. Header-based versioningAccept: application/vnd.api.v2+json
  3. Query parameter versioning?version=2
  4. Gradual migration — weighted traffic split between versions
  5. Version deprecation — redirect old versions to new

When to Use

  • Introducing breaking changes to an existing API
  • Running multiple API versions simultaneously
  • Gradually migrating clients from v1 to v2
  • Deprecating old API versions with user-friendly redirects

Approach A: URI Path Versioning

The most common pattern. Each version has its own URI prefix, and proxy-rewrite strips the version prefix before forwarding to the backend.

1. Create versioned upstreams

a6 upstream create -f - <<'EOF'
{
"id": "api-v1",
"type": "roundrobin",
"nodes": { "api-v1-backend:8080": 1 }
}
EOF

a6 upstream create -f - <<'EOF'
{
"id": "api-v2",
"type": "roundrobin",
"nodes": { "api-v2-backend:8080": 1 }
}
EOF

2. Create versioned routes with URI rewriting

# v1: /v1/users/123 → /users/123 on api-v1 backend
a6 route create -f - <<'EOF'
{
"id": "route-v1",
"uri": "/v1/*",
"upstream_id": "api-v1",
"plugins": {
"proxy-rewrite": {
"regex_uri": ["^/v1/(.*)", "/$1"]
}
}
}
EOF

# v2: /v2/users/123 → /users/123 on api-v2 backend
a6 route create -f - <<'EOF'
{
"id": "route-v2",
"uri": "/v2/*",
"upstream_id": "api-v2",
"plugins": {
"proxy-rewrite": {
"regex_uri": ["^/v2/(.*)", "/$1"]
}
}
}
EOF

Clients call /v1/users or /v2/users, and the backend always sees /users.

Approach B: Header-Based Versioning

Route based on the Accept header using traffic-split with vars matching. A single URI serves multiple versions.

a6 route create -f - <<'EOF'
{
"uri": "/api/*",
"plugins": {
"traffic-split": {
"rules": [
{
"match": [
{ "vars": [["http_accept", "~~", "application/vnd\\.api\\.v2\\+json"]] }
],
"weighted_upstreams": [
{
"upstream": {
"type": "roundrobin",
"nodes": { "api-v2-backend:8080": 1 }
},
"weight": 1
}
]
}
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": { "api-v1-backend:8080": 1 }
}
}
EOF
  • Accept: application/vnd.api.v2+json → v2 backend
  • Any other Accept value → v1 backend (default upstream)
  • ~~ is the regex match operator in APISIX vars expressions

Approach C: Query Parameter Versioning

Route based on ?version=2 query parameter.

a6 route create -f - <<'EOF'
{
"uri": "/api/*",
"plugins": {
"traffic-split": {
"rules": [
{
"match": [
{ "vars": [["arg_version", "==", "2"]] }
],
"weighted_upstreams": [
{
"upstream": {
"type": "roundrobin",
"nodes": { "api-v2-backend:8080": 1 }
},
"weight": 1
}
]
}
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": { "api-v1-backend:8080": 1 }
}
}
EOF
  • /api/users?version=2 → v2 backend
  • /api/users or /api/users?version=1 → v1 backend

Gradual Version Migration

Use weighted traffic splitting to gradually shift traffic from v1 to v2.

Start: 90% v1, 10% v2

a6 route create -f - <<'EOF'
{
"id": "api-migration",
"uri": "/api/*",
"plugins": {
"traffic-split": {
"rules": [
{
"weighted_upstreams": [
{
"upstream": {
"type": "roundrobin",
"nodes": { "api-v2-backend:8080": 1 }
},
"weight": 1
},
{ "weight": 9 }
]
}
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": { "api-v1-backend:8080": 1 }
}
}
EOF

Shift to 50/50

a6 route update api-migration -f - <<'EOF'
{
"plugins": {
"traffic-split": {
"rules": [
{
"weighted_upstreams": [
{
"upstream": {
"type": "roundrobin",
"nodes": { "api-v2-backend:8080": 1 }
},
"weight": 1
},
{ "weight": 1 }
]
}
]
}
}
}
EOF

Complete: 100% v2

a6 route update api-migration -f - <<'EOF'
{
"upstream": {
"type": "roundrobin",
"nodes": { "api-v2-backend:8080": 1 }
},
"plugins": {}
}
EOF

Version Deprecation with Redirect

When sunsetting v1, redirect clients to v2 with a 301 Moved Permanently.

a6 route update route-v1 -f - <<'EOF'
{
"uri": "/v1/*",
"plugins": {
"redirect": {
"regex_uri": ["^/v1/(.*)", "/v2/$1"],
"ret_code": 301
}
}
}
EOF

Clients calling /v1/users receive:

HTTP/1.1 301 Moved Permanently
Location: /v2/users

Declarative Versioning Config

# apisix-versioning.yaml
upstreams:
- id: api-v1
type: roundrobin
nodes:
"api-v1-backend:8080": 1
- id: api-v2
type: roundrobin
nodes:
"api-v2-backend:8080": 1

routes:
- id: route-v1
uri: "/v1/*"
upstream_id: api-v1
plugins:
proxy-rewrite:
regex_uri: ["^/v1/(.*)", "/$1"]
- id: route-v2
uri: "/v2/*"
upstream_id: api-v2
plugins:
proxy-rewrite:
regex_uri: ["^/v2/(.*)", "/$1"]
a6 config diff -f apisix-versioning.yaml
a6 config sync -f apisix-versioning.yaml

Gotchas

  • regex_uri is an array of two strings["pattern", "replacement"], not an object. The pattern is a Lua regex (PCRE-compatible).
  • traffic-split weight semantics — a weighted_upstreams entry without an upstream field means "use the route's default upstream". Weight 9 + weight 1 = 90%/10%.
  • ~~ operator — regex match in vars expressions. Must double-escape backslashes in JSON: "application/vnd\\\\.api\\\\.v2\\\\+json".
  • Order matters — traffic-split rules are evaluated top-down. First matching rule wins.
  • URI rewrite happens before upstreamproxy-rewrite changes the URI that the backend sees, not the URI used for route matching.
  • redirect plugin is terminal — when redirect is active, the request never reaches an upstream. Remove the upstream_id to avoid confusion.

Verification

# Test URI path versioning
curl http://localhost:9080/v1/users # → v1 backend
curl http://localhost:9080/v2/users # → v2 backend

# Test header-based versioning
curl -H "Accept: application/vnd.api.v2+json" http://localhost:9080/api/users # → v2
curl http://localhost:9080/api/users # → v1 (default)

# Test query parameter versioning
curl "http://localhost:9080/api/users?version=2" # → v2
curl http://localhost:9080/api/users # → v1

# Test redirect (deprecation)
curl -v http://localhost:9080/v1/users # → 301 to /v2/users

This page is generated from a6-recipe-api-versioning/SKILL.md in the api7/a6 repository. Browse all skills on the AI Agent Skills page.

API7.ai Logo

The digital world is connected by APIs,
API7.ai exists to make APIs more efficient, reliable, and secure.

Sign up for API7 newsletter

Product

API7 Gateway

SOC2 Type IIISO 27001HIPAAGDPRRed Herring

Copyright © APISEVEN PTE. LTD 2019 – 2026. Apache, Apache APISIX, APISIX, and associated open source project names are trademarks of the Apache Software Foundation