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"]
}'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="https://cycles.example.com",
api_key=os.environ["CYCLES_API_KEY"],
tenant="acme",
)Or from environment variables:
export CYCLES_BASE_URL=https://cycles.example.com
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: https://cycles.example.com
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 '{ ... }'Revoking API keys
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.
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 |
Next steps
- 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
