Full Implementations and Admin Recipes
End-to-end examples for bootstrapping rights, creating keys, enabling enforcement, and rolling out lock-in safely.
This page is intentionally operational. Every recipe uses the actual Athena admin and gateway routes so you can test the whole model end to end.
Recipe 1: Create a client-bound query key and use it
1. Define the right
curl -X POST "http://localhost:4052/admin/api-key-rights" \
-H "X-Athena-Admin-Key: $ATHENA_KEY_12" \
-H "Content-Type: application/json" \
-d '{
"name": "gateway.query",
"description": "Run /gateway/query"
}'2. Issue the key
curl -X POST "http://localhost:4052/admin/api-keys" \
-H "X-Athena-Admin-Key: $ATHENA_KEY_12" \
-H "Content-Type: application/json" \
-d '{
"name": "analytics-query-runner",
"description": "Batch analytics jobs",
"client_name": "analytics",
"rights": ["gateway.query"],
"expires_at": "2027-01-01T00:00:00Z"
}'Capture the returned data.api_key immediately.
3. Use the key
curl -X POST "http://localhost:4052/gateway/query" \
-H "X-Athena-Client: analytics" \
-H "X-Athena-Key: $ANALYTICS_QUERY_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "select now() as executed_at"
}'If you change X-Athena-Client to another client, Athena returns
API key client mismatch.
Recipe 2: Roll out a virgin key to a new worker fleet
1. Create the learning key
curl -X POST "http://localhost:4052/admin/api-keys" \
-H "X-Athena-Admin-Key: $ATHENA_KEY_12" \
-H "Content-Type: application/json" \
-d '{
"name": "new-worker-fleet",
"client_name": "analytics",
"rights": ["gateway.query"],
"virgin_mode": true,
"virgin_until_n_requests": 100,
"max_whitelist_ips": 4
}'2. Let the fleet call Athena
curl -X POST "http://localhost:4052/gateway/query" \
-H "X-Athena-Client: analytics" \
-H "X-Athena-Key: $FLEET_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "select 1"
}'Repeat from each expected host until the fleet has exercised the key.
3. Inspect what Athena learned
curl "http://localhost:4052/admin/api-keys/$API_KEY_ID/ip-seen?limit=100" \
-H "X-Athena-Admin-Key: $ATHENA_KEY_12"4. Promote early if you do not want to wait
curl -X POST "http://localhost:4052/admin/api-keys/$API_KEY_ID/virgin/promote" \
-H "X-Athena-Admin-Key: $ATHENA_KEY_12"Recipe 3: Turn on enforcement for one client first
This is useful when you want a gradual rollout.
1. Leave the global flag off
curl "http://localhost:4052/admin/api-key-config" \
-H "X-Athena-Admin-Key: $ATHENA_KEY_12"2. Enable one client override
curl -X PUT "http://localhost:4052/admin/api-key-clients/analytics" \
-H "X-Athena-Admin-Key: $ATHENA_KEY_12" \
-H "Content-Type: application/json" \
-d '{
"enforce_api_keys": true
}'3. Test behavior
curl -X POST "http://localhost:4052/gateway/query" \
-H "X-Athena-Client: analytics" \
-H "Content-Type: application/json" \
-d '{
"query": "select 1"
}'Now the request is rejected for analytics, while other clients still follow
the global setting.
Recipe 4: Add network restrictions after the fact
1. Add a per-key whitelist
curl -X POST "http://localhost:4052/admin/api-keys/$API_KEY_ID/ip-whitelist" \
-H "X-Athena-Admin-Key: $ATHENA_KEY_12" \
-H "Content-Type: application/json" \
-d '{
"addrs": ["203.0.113.10", "203.0.113.11"],
"label": "two runners"
}'2. Add a global blacklist for an abuse source
curl -X POST "http://localhost:4052/admin/ip-global-blacklist" \
-H "X-Athena-Admin-Key: $ATHENA_KEY_12" \
-H "Content-Type: application/json" \
-d '{
"addr": "198.51.100.0/24",
"label": "abuse net"
}'Recipe 5: Rotate a key without downtime
Recommended sequence:
- Create the replacement key.
- Deploy it everywhere.
- Confirm requests are succeeding and
last_used_atadvances for the new key. - Disable the old key with
PATCH /admin/api-keys/{id}. - Delete the old key once no callers still use it.
Quick TypeScript caller example
const response = await fetch("http://localhost:4052/gateway/query", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Athena-Client": "analytics",
"X-Athena-Key": process.env.ATHENA_GATEWAY_KEY!,
},
body: JSON.stringify({
query: "select now() as ts",
}),
});
if (!response.ok) {
throw new Error(await response.text());
}
console.log(await response.json());