a6-recipe-api-versioning
>-
Works with
---
name: a6-recipe-api-versioning
description: >-
license: Apache-2.0
---
# 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 versioning** — `Accept: 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
```bash
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
```bash
# 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.
```bash
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.
```bash
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
```bash
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
```bash
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
```bash
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`.
```bash
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
```yaml
# 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"]
```
```bash
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 upstream** — `proxy-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
```bash
# 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
```More API Design skills
lark-event
larksuite/cli
Lark/Feishu real-time event listening / subscribing / consuming: stream events as NDJSON via `lark-cli event consume <EventKey>` (covers IM messages/reactions/chat changes, Approval status changes, Task updates, VC meeting started/joined/ended, Minutes generated, Whiteboard updated, etc.). Use for Lark bots, real-time message processing, long-running subscribers, streaming webhook/push handlers. Supports `--max-events` / `--timeout` bounded runs and a stderr ready-marker contract — designed for AI agents running as subprocesses.
lark-contact
larksuite/cli
飞书 / Lark 通讯录:按姓名 / 邮箱解析成 open_id,或按 open_id 反查姓名 / 部门 / 邮箱 / 联系方式 / 个人状态 / 签名,以及按关键词搜索当前用户可见的机器人 / 智能体(agent)。当用户提到一个名字要下一步发消息 / 排日程,或拿到 open_id 想查具体信息时使用。不负责部门树遍历、按部门列员工、组织架构图,这类需求走原生 OpenAPI。
lark-openapi-explorer
larksuite/cli
飞书/Lark 原生 OpenAPI 探索:从官方文档库中挖掘未经 CLI 封装的原生 OpenAPI 接口。当用户的需求无法被现有 lark-* skill 或 lark-cli 已注册命令满足,需要查找并调用原生飞书 OpenAPI 时使用。

