API Key Management in Cycles
Every request to the Cycles server requires an API key. This page explains how API keys work, how to create and manage them, and how they relate to tenant isolation.
How API keys work
The Cycles server authenticates requests using the X-Cycles-API-Key header. Each API key is associated with exactly one tenant.
When a request arrives:
- The server extracts the
X-Cycles-API-Keyheader - Validates the key exists and is active
- Derives the effective tenant from the key
- Verifies that
subject.tenantin the request body matches the key's tenant - If any check fails, returns
401 UNAUTHORIZEDor403 FORBIDDEN
This ensures strict tenant isolation — an API key for tenant A cannot create reservations or query balances for tenant B.
Key states
An API key can be in one of three states:
| State | Meaning |
|---|---|
ACTIVE | Key is valid and can be used for requests |
REVOKED | Key has been manually disabled |
EXPIRED | Key has passed its expiration date |
Only ACTIVE keys are accepted. Requests with REVOKED or EXPIRED keys receive 401 UNAUTHORIZED.
Creating API keys
API keys are managed through the Cycles Admin interface:
# Create a new API key for a tenant
curl -X POST http://localhost:7979/v1/admin/api-keys \
-H "Content-Type: application/json" \
-H "X-Admin-API-Key: $ADMIN_API_KEY" \
-d '{
"tenant_id": "acme",
"name": "production-chatbot",
"description": "Production chatbot key",
"permissions": ["reservations:create", "reservations:commit", "reservations:release", "balances:read"]
}'Available permissions (27 total)
Tenant-scoped permissions (used with X-Cycles-API-Key for runtime and tenant operations):
| Permission | Grants | Default |
|---|---|---|
reservations:create | Create new reservations | Yes |
reservations:commit | Commit existing reservations | Yes |
reservations:release | Release existing reservations | Yes |
reservations:extend | Extend reservation TTL | Yes |
reservations:list | List reservations | Yes |
balances:read | Query balance information | Yes |
webhooks:write | Create, update, delete tenant webhooks at /v1/webhooks | No |
webhooks:read | List tenant webhooks and delivery history | No |
events:read | Query tenant event stream at /v1/events | No |
Tenant budget/policy permissions (v0.1.25.6+):
| Permission | Grants | Default? |
|---|---|---|
budgets:read | List and read own tenant budgets | Yes |
budgets:write | Create and fund own tenant budgets | Yes |
policies:read | List and read own tenant policies | Yes |
policies:write | Create and update own tenant policies | Yes |
Admin wildcard permissions (used with X-Cycles-API-Key for admin operations):
| Permission | Grants |
|---|---|
admin:read | Satisfies any *:read permission (budgets:read, policies:read, webhooks:read, events:read, etc.) |
admin:write | Satisfies any *:write permission (budgets:write, policies:write, webhooks:write, etc.). Does NOT grant read access — use both admin:read and admin:write for full access. |
Wildcard behavior (v0.1.25.7+)
admin:write acts as a server-level wildcard — it satisfies any *:write permission requirement. This means pre-v0.1.25.6 keys with admin:write continue to work without migration even after granular permissions were introduced. admin:read does NOT satisfy *:write.
Admin granular permissions (v0.1.25+ — finer-grained alternative to admin wildcards):
| Permission | Grants |
|---|---|
admin:tenants:read | Read tenant details |
admin:tenants:write | Create and update tenants |
admin:budgets:read | List and read budgets |
admin:budgets:write | Create, fund, and update budgets |
admin:policies:read | List and read policies |
admin:policies:write | Create and update policies |
admin:apikeys:read | List API keys |
admin:apikeys:write | Create and revoke API keys |
admin:webhooks:read | List admin webhook subscriptions and deliveries |
admin:webhooks:write | Create, update, delete, test, and replay admin webhooks |
admin:events:read | Query admin event stream |
admin:audit:read | Query audit logs |
Defaults: When no permissions are specified at key creation, the key receives 10 default permissions: the 6 runtime permissions (
reservations:create,reservations:commit,reservations:release,reservations:extend,reservations:list,balances:read) plusbudgets:read,budgets:write,policies:read,policies:write. Webhook, event, and admin permissions must be explicitly requested.
A typical runtime key needs only the 6 defaults. Add budgets:write and budgets:read if tenants manage their own budgets. Add webhooks:write and webhooks:read for webhook subscriptions. Add admin:read/admin:write (or granular equivalents) only if the key is used for cross-tenant admin operations via the admin server (port 7979).
Admin permissions on tenant keys (v0.1.25.7)
admin:read and admin:write are accepted on tenant keys for backward compatibility, but SHOULD NOT be assigned to new tenant keys. Use the specific permissions (budgets:write, policies:read, etc.) instead. The admin key (X-Admin-API-Key) is server-configured and is not provisioned through the API key creation endpoint.
For the full endpoint-to-header-to-permission mapping, see the Architecture Overview — Authentication.
Response:
{
"key_id": "key_abc123...",
"key_secret": "cyc_live_abc123...",
"key_prefix": "cyc_live_abc12",
"tenant_id": "acme",
"permissions": ["reservations:create", "reservations:commit", "reservations:release", "balances:read"],
"created_at": "2026-03-01T00:00:00Z",
"expires_at": "2026-05-30T00:00:00Z"
}Store the API key securely. It is shown only once at creation time.
Using API keys
In the Python client
Configure the key via CyclesConfig:
import os
from runcycles import CyclesConfig
config = CyclesConfig(
base_url="http://localhost:7878",
api_key=os.environ["CYCLES_API_KEY"],
tenant="acme",
)Or from environment variables:
export CYCLES_BASE_URL=http://localhost:7878
export CYCLES_API_KEY=cyc_live_abc123...
export CYCLES_TENANT=acmeconfig = CyclesConfig.from_env()In the Spring Boot Starter
Configure the key in your project's application.yml:
cycles:
api-key: ${CYCLES_API_KEY}
base-url: http://localhost:7878
tenant: acmeUse an environment variable rather than hardcoding the key.
In direct HTTP calls
Pass the key in the X-Cycles-API-Key header:
curl -X POST http://localhost:7878/v1/reservations \
-H "Content-Type: application/json" \
-H "X-Cycles-API-Key: cyc_live_abc123..." \
-d '{ ... }'Listing API keys
GET /v1/admin/api-keys lists keys. By default (tenant-scoped with X-Cycles-API-Key), the server returns keys for the authenticated tenant only. With X-Admin-API-Key you can list across all tenants by omitting the tenant_id query parameter (v0.1.25.22+). Cross-tenant results paginate with a composite (tenant_id, key_id) cursor.
# Cross-tenant — all keys, sorted by last-used
curl -G "http://localhost:7979/v1/admin/api-keys" \
-H "X-Admin-API-Key: $ADMIN_KEY" \
--data-urlencode "sort_by=last_used_at_ms" \
--data-urlencode "sort_dir=desc" \
--data-urlencode "limit=50" | jq .
# Tenant-scoped
curl -G "http://localhost:7979/v1/admin/api-keys?tenant_id=acme" \
-H "X-Admin-API-Key: $ADMIN_KEY" | jq .search (v0.1.25.25+) does a case-insensitive match over key_id, name, and description. See Searching and Sorting Admin List Endpoints for the full parameter vocabulary.
Revoking API keys
Revoke from the dashboard
Key revocation is also a one-click action on the API Keys page in the Cycles Admin Dashboard — typically faster than crafting a curl when responding to a leaked-key incident. Revocation is irreversible; the dashboard surfaces a confirmation step.
Revoke a key to immediately block all requests using it:
curl -X DELETE http://localhost:7979/v1/admin/api-keys/key_abc123 \
-H "X-Admin-API-Key: $ADMIN_API_KEY"Revocation is immediate. Any in-flight requests using the revoked key will fail on their next call to the Cycles server. Active reservations created with the revoked key remain valid until they expire or are committed/released.
Revocation, not deletion
The DELETE endpoint performs a status transition (ACTIVE → REVOKED), not a hard delete. The key record is retained so that audit logs referencing the key remain resolvable. This is consistent with the lifecycle model used across Cycles — see the equivalent notes on tenant closure and budget decommissioning.
Key rotation
To rotate an API key without downtime:
- Create a new key for the same tenant
- Update your application configuration to use the new key
- Deploy the configuration change
- Verify traffic is flowing with the new key
- Revoke the old key
Because both keys are valid during the transition, there is no interruption.
Tenant isolation
API keys are the primary mechanism for tenant isolation in Cycles.
Enforced behaviors:
- A key for tenant A can only create reservations where
subject.tenant = "A"(or where tenant is omitted, in which case the server uses the key's tenant) - A key for tenant A cannot commit, release, or extend reservations owned by tenant B
- A key for tenant A cannot query balances for tenant B
- A key for tenant A cannot list reservations belonging to tenant B
If the tenant is omitted from the Subject, the server automatically sets it to the key's associated tenant. This is the recommended approach — configure the tenant at the key level and let the server enforce it.
Best practices
One key per environment
Use separate API keys for production, staging, and development, even within the same tenant. This makes it easy to revoke a single environment's access without affecting others.
Use environment variables
Never hardcode API keys in source code:
# Good
cycles:
api-key: ${CYCLES_API_KEY}
# Bad
cycles:
api-key: cyc_live_abc123...Minimal key scope
If you operate multiple tenants, issue one key per tenant. Do not share keys across tenants.
Monitor key usage
Track which keys are making requests. If a key is compromised, revoke it immediately and issue a replacement.
Error responses
| Error | HTTP | When |
|---|---|---|
UNAUTHORIZED | 401 | Missing X-Cycles-API-Key header, or key is invalid/revoked/expired |
FORBIDDEN | 403 | Key is valid but subject.tenant does not match the key's tenant |
INSUFFICIENT_PERMISSIONS | 403 | Key is valid but lacks the required permission for the endpoint (e.g., calling a budget endpoint without admin:write) |
Next steps
- Tenant Creation and Management — create and manage the tenants that API keys belong to
- Authentication and Tenancy — deeper dive into the auth model
- Self-Hosting the Cycles Server — deploy your own instance
- Architecture Overview — how authentication fits into the system