Athena

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:

  1. Create the replacement key.
  2. Deploy it everywhere.
  3. Confirm requests are succeeding and last_used_at advances for the new key.
  4. Disable the old key with PATCH /admin/api-keys/{id}.
  5. 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());