skills/a6-recipe-multi-tenant/SKILL.md
Recipe skill for implementing multi-tenant API gateway patterns using the a6 CLI. Covers tenant isolation via Consumer Groups, host/path/header-based routing, per-tenant rate limiting, context forwarding with proxy-rewrite, and declarative config sync workflows for multi-tenant management.
npx skillsauth add moonming/a6 a6-recipe-multi-tenantInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Multi-tenancy in an API gateway means serving multiple isolated tenants (customers, teams, or business units) through the same gateway instance, each with their own rate limits, authentication, and routing rules.
APISIX achieves multi-tenancy through:
Group consumers by tenant. Each tenant gets shared plugin configuration (rate limits, transformations) applied via the consumer group.
# Free tier — 100 requests/day
a6 consumer-group create -f - <<'EOF'
{
"id": "tenant-free",
"desc": "Free tier tenant",
"plugins": {
"limit-count": {
"count": 100,
"time_window": 86400,
"key_type": "var",
"key": "consumer_name",
"rejected_code": 429,
"rejected_msg": "Free tier quota exceeded"
}
}
}
EOF
# Pro tier — 10000 requests/day
a6 consumer-group create -f - <<'EOF'
{
"id": "tenant-pro",
"desc": "Pro tier tenant",
"plugins": {
"limit-count": {
"count": 10000,
"time_window": 86400,
"key_type": "var",
"key": "consumer_name",
"rejected_code": 429,
"rejected_msg": "Pro tier quota exceeded"
}
}
}
EOF
a6 consumer create -f - <<'EOF'
{
"username": "acme-corp",
"group_id": "tenant-pro",
"plugins": {
"key-auth": { "key": "acme-secret-key" }
}
}
EOF
a6 consumer create -f - <<'EOF'
{
"username": "startup-xyz",
"group_id": "tenant-free",
"plugins": {
"key-auth": { "key": "startup-xyz-key" }
}
}
EOF
a6 route create -f - <<'EOF'
{
"id": "api-v1",
"uri": "/api/v1/*",
"upstream": {
"type": "roundrobin",
"nodes": { "api-backend:8080": 1 }
},
"plugins": {
"key-auth": {}
}
}
EOF
Now acme-corp gets 10,000 req/day and startup-xyz gets 100 req/day,
both through the same route.
Route each tenant to their own backend based on the Host header.
a6 upstream create -f - <<'EOF'
{
"id": "upstream-tenant-a",
"type": "roundrobin",
"nodes": { "tenant-a-backend:8080": 1 }
}
EOF
a6 upstream create -f - <<'EOF'
{
"id": "upstream-tenant-b",
"type": "roundrobin",
"nodes": { "tenant-b-backend:8080": 1 }
}
EOF
a6 route create -f - <<'EOF'
{
"id": "tenant-a-route",
"host": "tenant-a.example.com",
"uri": "/*",
"upstream_id": "upstream-tenant-a",
"plugins": { "key-auth": {} }
}
EOF
a6 route create -f - <<'EOF'
{
"id": "tenant-b-route",
"host": "tenant-b.example.com",
"uri": "/*",
"upstream_id": "upstream-tenant-b",
"plugins": { "key-auth": {} }
}
EOF
Use a custom header (e.g., X-Tenant-ID) to route to different upstreams
via traffic-split.
a6 route create -f - <<'EOF'
{
"uri": "/api/*",
"plugins": {
"key-auth": {},
"traffic-split": {
"rules": [
{
"match": [{ "vars": [["http_x_tenant_id", "==", "tenant-a"]] }],
"weighted_upstreams": [
{ "upstream": { "type": "roundrobin", "nodes": { "tenant-a-backend:8080": 1 } }, "weight": 1 }
]
},
{
"match": [{ "vars": [["http_x_tenant_id", "==", "tenant-b"]] }],
"weighted_upstreams": [
{ "upstream": { "type": "roundrobin", "nodes": { "tenant-b-backend:8080": 1 } }, "weight": 1 }
]
}
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": { "default-backend:8080": 1 }
}
}
EOF
Use proxy-rewrite to inject tenant identity as headers so backends
know which tenant the request belongs to.
a6 route update api-v1 -f - <<'EOF'
{
"plugins": {
"key-auth": {},
"proxy-rewrite": {
"headers": {
"set": {
"X-Consumer-Name": "$consumer_name",
"X-Consumer-Group": "$consumer_group_id"
}
}
}
}
}
EOF
Backend receives X-Consumer-Name: acme-corp and X-Consumer-Group: tenant-pro.
Manage all tenants declaratively with a6 config sync:
# apisix-tenants.yaml
consumer_groups:
- id: tenant-free
desc: "Free tier"
plugins:
limit-count:
count: 100
time_window: 86400
key_type: var
key: consumer_name
- id: tenant-pro
desc: "Pro tier"
plugins:
limit-count:
count: 10000
time_window: 86400
key_type: var
key: consumer_name
consumers:
- username: acme-corp
group_id: tenant-pro
plugins:
key-auth:
key: acme-secret-key
- username: startup-xyz
group_id: tenant-free
plugins:
key-auth:
key: startup-xyz-key
routes:
- id: api-v1
uri: "/api/v1/*"
upstream:
type: roundrobin
nodes:
"api-backend:8080": 1
plugins:
key-auth: {}
proxy-rewrite:
headers:
set:
X-Consumer-Name: "$consumer_name"
X-Consumer-Group: "$consumer_group_id"
# Preview changes
a6 config diff -f apisix-tenants.yaml
# Apply
a6 config sync -f apisix-tenants.yaml
group_id is a string — must match an existing consumer group ID exactly.key_type: "var" with key: "consumer_name" to
enforce per-consumer limits within a group. Without this, the limit applies
globally across all consumers in the group.$consumer_name and $consumer_group_id
are APISIX built-in variables, available only after authentication runs.
Ensure the auth plugin (key-auth, jwt-auth, etc.) has higher priority than
proxy-rewrite.# List consumer groups
a6 consumer-group list
# Verify consumer assignment
a6 consumer get acme-corp --output json | grep group_id
# Test rate limiting for free tier
for i in $(seq 1 101); do
curl -s -o /dev/null -w "%{http_code}\n" \
-H "apikey: startup-xyz-key" http://localhost:9080/api/v1/hello
done
# Request 101 should return 429
# Verify tenant headers reach backend
curl -H "apikey: acme-secret-key" http://localhost:9080/api/v1/headers
# Response should show X-Consumer-Name and X-Consumer-Group headers
tools
Core skill for working with the a6 CLI — the Apache APISIX command-line tool. Provides project conventions, command patterns, architecture overview, and development workflow. Load this skill when working on a6 source code, adding new commands, writing tests, or modifying any a6 component.
tools
Recipe skill for configuring mutual TLS (mTLS) using the a6 CLI. Covers SSL certificate management, upstream mTLS to backend services, client certificate verification, and end-to-end mTLS setup from client through APISIX to upstream.
tools
Recipe skill for configuring upstream health checks using the a6 CLI. Covers active health checks (HTTP probing), passive health checks (response analysis), combining both, configuring healthy/unhealthy thresholds, and monitoring upstream node status.
tools
Recipe skill for implementing GraphQL proxying patterns using the a6 CLI. Covers operation-based routing with built-in GraphQL variables, per-operation rate limiting, REST-to-GraphQL conversion with the degraphql plugin, and security patterns for GraphQL APIs.