Administrative API for the Complete Budget Governance System, aligned with Cycles Protocol v0.1.24.
This service implements a budget governance system built on three integrated pillars:
| Pillar | Plane | Purpose |
|---|---|---|
| Tenant & Budget Management | Configuration | Tenant lifecycle, budget ledgers, policy configuration |
| Authentication & Authorization | Identity | API key validation, permission enforcement, audit logging |
| Runtime Enforcement | Reservation | Budget reservations, commits, balance queries (Cycles Protocol v0.1.24) |
cycles-admin-service/
├── cycles-admin-service-model # Shared domain models, DTOs, enums
├── cycles-admin-service-data # Redis repositories, key service
└── cycles-admin-service-api # REST controllers, auth interceptor, Spring Boot app
- Language: Java 21
- Framework: Spring Boot 3.5.11
- Data Store: Redis (via Jedis 5.2.0)
- API Docs: SpringDoc OpenAPI (Swagger UI)
- Testing: JUnit 5 + TestContainers (Redis)
The fastest way to run the admin server — no Java or Maven required:
# Using pre-built image from GHCR
docker compose -f docker-compose.prod.yml up -dThe server starts at http://localhost:7979. Swagger UI: http://localhost:7979/swagger-ui.html
To run the full stack (Admin Server + Runtime Server + Redis):
docker compose -f docker-compose.full-stack.prod.yml up -dFor the complete deployment walkthrough including tenant setup, API key creation, and budget allocation, see the full stack deployment guide.
- Java 21+
- Maven 3.9+
- Redis 7+ (or Docker for TestContainers)
cd cycles-admin-service
mvn clean installcd cycles-admin-service/cycles-admin-service-api
mvn spring-boot:runThe server starts at http://localhost:7979. Swagger UI is available at /swagger-ui.html.
Integration tests use TestContainers to spin up a Redis instance automatically. Docker must be running.
cd cycles-admin-service
mvn verify -P integration-testsThe API uses two authentication schemes:
| Scheme | Header | Use |
|---|---|---|
| AdminKeyAuth | X-Admin-API-Key |
System administration (tenant/key management, audit) |
| ApiKeyAuth | X-Cycles-API-Key |
Tenant-scoped operations (budgets, reservations, balances) |
API keys use the format cyc_live_{random} (production) or cyc_test_{random} (test), where the random part is 32 cryptographically random characters. Keys are stored as bcrypt hashes; the full secret is only returned once at creation time. Recommended expiry: 90 days.
| Method | Path | Operation | Auth |
|---|---|---|---|
POST |
/v1/admin/tenants |
Create tenant | Admin |
GET |
/v1/admin/tenants |
List tenants | Admin |
GET |
/v1/admin/tenants/{tenant_id} |
Get tenant | Admin |
PATCH |
/v1/admin/tenants/{tenant_id} |
Update tenant | Admin |
POST |
/v1/admin/budgets |
Create budget ledger | ApiKey |
GET |
/v1/admin/budgets |
List budget ledgers | ApiKey |
PATCH |
/v1/admin/budgets?scope={scope}&unit={unit} |
Update budget | ApiKey |
POST |
/v1/admin/budgets/fund?scope={scope}&unit={unit} |
Fund/adjust budget | ApiKey |
POST |
/v1/admin/policies |
Create policy | ApiKey |
GET |
/v1/admin/policies |
List policies | ApiKey |
PATCH |
/v1/admin/policies/{policy_id} |
Update policy | ApiKey |
| Method | Path | Operation | Auth |
|---|---|---|---|
POST |
/v1/admin/api-keys |
Create API key | Admin |
GET |
/v1/admin/api-keys |
List API keys | Admin |
DELETE |
/v1/admin/api-keys/{key_id} |
Revoke API key | Admin |
POST |
/v1/auth/validate |
Validate key & resolve tenant | Admin |
GET |
/v1/admin/audit/logs |
Query audit logs | Admin |
| Method | Path | Operation | Auth |
|---|---|---|---|
POST |
/v1/reservations |
Create budget reservation | ApiKey |
POST |
/v1/reservations/{reservation_id}/commit |
Commit actual spend | ApiKey |
GET |
/v1/balances |
Query budget balances | ApiKey |
Tenants are the top-level isolation boundary. All budgets, keys, and reservations are scoped to a tenant.
- ID format: kebab-case,
^[a-z0-9-]+$, 3-64 chars (e.g.,acme-corp,demo-tenant) - Status lifecycle:
ACTIVE→SUSPENDED↔ACTIVE, or* → CLOSED(irreversible)SUSPENDEDblocks new reservations; existing active reservations can still commit/release
- Supports hierarchical tenants via
parent_tenant_id(enables budget delegation and consolidated billing) - Tenant creation is idempotent — retrying with the same
tenant_idreturns the existing tenant (200) rather than failing
Tenant-level reservation configuration:
| Property | Default | Range | Description |
|---|---|---|---|
default_commit_overage_policy |
ALLOW_IF_AVAILABLE |
— | Default overage policy for all scopes |
default_reservation_ttl_ms |
60000 (60s) |
1s – 24h | Default TTL when not specified per-reservation |
max_reservation_ttl_ms |
3600000 (1h) |
1s – 24h | Maximum allowed TTL; requests exceeding this are capped |
max_reservation_extensions |
10 |
0+ | Max TTL extensions per reservation (prevents zombie reservations) |
reservation_expiry_policy |
AUTO_RELEASE |
— | How expired reservations are handled |
Reservation expiry policies:
| Policy | Behavior |
|---|---|
AUTO_RELEASE |
Expired reservations auto-release after grace period |
MANUAL_CLEANUP |
Require explicit release or cleanup job |
GRACE_ONLY |
Allow commits during grace period, then mark EXPIRED |
A budget ledger tracks finances for a specific (scope, unit) pair. Each ledger maintains:
| Field | Description |
|---|---|
allocated |
Total budget cap |
remaining |
Available for new reservations (can go negative with overdraft) |
reserved |
Locked by active reservations |
spent |
Successfully committed |
debt |
Overdraft amount from ALLOW_WITH_OVERDRAFT commits |
overdraft_limit |
Maximum allowed debt (0 = no overdraft) |
is_over_limit |
true when debt > overdraft_limit |
commit_overage_policy |
Per-ledger overage policy override; inherits from tenant if unset |
Supported units: USD_MICROCENTS, TOKENS, CREDITS, RISK_POINTS
Amount values are int64 integers (not floating-point). All Amount objects carry an explicit unit field to prevent mismatches.
Ledger status: ACTIVE (normal operations) | FROZEN (read-only, no new reservations or commits) | CLOSED (archived)
Budget periods: Ledgers support optional period_start / period_end with a rollover policy:
| Rollover Policy | Behavior |
|---|---|
NONE |
No rollover (default) |
CARRY_FORWARD |
Unused budget carries to next period |
CAP_AT_ALLOCATED |
Remaining is capped at the allocated amount |
Scopes use hierarchical paths: tenant:acme-corp/workspace:eng/agent:summarizer
Scope hierarchy semantics:
- Each scope is independent — no automatic propagation between parent/child
- Parent scopes do NOT automatically aggregate child scope charges
- Hierarchical validation: if
tenant:acme/workspace:enghas a budget, bothtenant:acmeandtenant:acme/workspace:engmust exist - Operations on a scope do NOT affect parent/child scopes unless explicitly specified via multi-scope reservation
Initial state when a budget is created: remaining = allocated, reserved = spent = debt = 0. A budget ledger must exist for a scope before any reservations can be made against it.
Use POST /v1/admin/budgets/fund?scope={scope}&unit={unit} with one of:
| Operation | Effect |
|---|---|
CREDIT |
allocated += amount, remaining += amount |
DEBIT |
allocated -= amount, remaining -= amount (fails if remaining would go negative) |
RESET |
allocated = amount, remaining = amount - reserved - spent - debt |
REPAY_DEBT |
debt -= amount (uses remaining if debt < amount) |
All funding operations support idempotency_key (prevents double-funding; replayed requests return original response) and an optional reason field for the audit trail.
Controls behavior when actual spend exceeds the reserved amount:
| Policy | Behavior |
|---|---|
REJECT |
Fail if actual > reserved |
ALLOW_IF_AVAILABLE |
Charge delta from remaining budget if available |
ALLOW_WITH_OVERDRAFT |
Create debt up to overdraft_limit if budget exhausted |
Policy resolution order (highest priority wins): Reservation > Policy > Budget Ledger > Tenant default
Policies define caps, rate limits, and behavioral rules matched by scope patterns:
tenant:acme-corp— exact matchtenant:acme-corp/*— all descendant scopesagent:*— all agents across all tenants*/agent:summarizer— specific agent across all tenants
Policy status: ACTIVE | DISABLED
Policies support:
- Priority ordering — higher priority policies evaluated first; highest priority wins on conflict
- Effective date windows —
effective_from/effective_untilfor time-bounded policies (trials, temporary restrictions) - Rate limits —
max_reservations_per_minute,max_commits_per_minute - TTL overrides —
default_ttl_ms,max_ttl_ms,max_extensionsper matching scope - Commit overage policy override — overrides both tenant and budget ledger defaults
From Cycles Protocol v0.1.24:
{
"max_tokens": 4096,
"max_steps_remaining": 10,
"tool_allowlist": ["read_file", "search"],
"tool_denylist": ["execute_command"],
"cooldown_ms": 5000
}API keys carry granular permissions:
| Permission | Description |
|---|---|
reservations:create |
Create budget reservations |
reservations:commit |
Commit actual spend |
reservations:release |
Release unused reservations |
reservations:extend |
Extend reservation TTL |
reservations:list |
List reservations |
balances:read |
Query budget balances |
admin:read |
Read admin resources |
admin:write |
Modify admin resources |
Defaults: Tenant keys get [reservations:*, balances:read]. Admin keys get [admin:*, reservations:*, balances:*].
Keys can be further restricted to specific scopes via scope_filter (e.g., ["workspace:eng", "agent:*"]).
Key revocation (DELETE /v1/admin/api-keys/{key_id}) is permanent and cannot be undone. Revoked keys remain in the database for audit trail. Active reservations created with a revoked key can still be committed/released, but no new operations are permitted.
POST /v1/auth/validate performs these checks in order:
- Key exists in database
- Key hash matches
- Status is
ACTIVE(notREVOKEDorEXPIRED) - Current time <
expires_at - Tenant is
ACTIVE(notSUSPENDEDorCLOSED)
On success, returns tenant_id, permissions, and scope_filter. On failure, returns valid: false with a reason (e.g., "REVOKED", "EXPIRED"). Results should be cached with a short TTL (~60s) and invalidated on key revocation.
When POST /v1/reservations is called, the governance layer executes:
- Validate
X-Cycles-API-Keyvia/v1/auth/validate - Derive effective tenant from key
- Validate
Subject.tenantmatches effective tenant (403 if mismatch) - Check tenant status (block if
SUSPENDEDorCLOSED) - Check budget ledger exists for scope
- Apply matching policies (caps, rate limits)
- Execute Cycles reservation logic
- Log operation to audit trail
Commit governance (POST /v1/reservations/{id}/commit): validates key and tenant as above, applies ALLOW_WITH_OVERDRAFT policy (creates debt up to overdraft_limit), and logs to audit trail.
Balance governance (GET /v1/balances): returns full ledger state including debt, overdraft_limit, and is_over_limit; scoped to the effective tenant; supports scope filtering via query params.
GET /v1/admin/audit/logs provides a compliance-ready audit trail (SOC2, GDPR).
Query filters: tenant_id, key_id, operation, status (HTTP code), from/to (datetime range)
Retention recommendation: 90 days hot storage, 1 year cold storage.
All list endpoints use cursor-based pagination:
| Parameter | Description |
|---|---|
cursor |
Opaque cursor from previous response's next_cursor |
limit |
Page size (default: 50, max: 200) |
Responses include next_cursor and has_more fields.
All errors return a standard ErrorResponse:
{
"error": "BUDGET_EXCEEDED",
"message": "Remaining budget insufficient for reservation",
"request_id": "req_abc123",
"details": {}
}Cycles v0.1.24 error codes:
INVALID_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, BUDGET_EXCEEDED, RESERVATION_EXPIRED, RESERVATION_FINALIZED, IDEMPOTENCY_MISMATCH, UNIT_MISMATCH, OVERDRAFT_LIMIT_EXCEEDED, DEBT_OUTSTANDING, INTERNAL_ERROR
Governance error codes:
TENANT_NOT_FOUND, TENANT_SUSPENDED, TENANT_CLOSED, BUDGET_NOT_FOUND, BUDGET_FROZEN, POLICY_VIOLATION, INSUFFICIENT_PERMISSIONS, KEY_REVOKED, KEY_EXPIRED, DUPLICATE_RESOURCE
| Model | Description |
|---|---|
| Single-service | All three pillars in one deployment |
| Split-plane | Separate admin/auth services from runtime enforcement |
| Federated | Multiple runtime enforcement instances, shared admin plane |
The full OpenAPI 3.1.0 specification is in complete-budget-governance-v0.1.24.yaml.
- Cycles Documentation — full docs site
- Deploy the Full Stack — deployment guide with admin server setup
- Tenant Management — tenant creation and lifecycle
- Budget Allocation and Management — budget configuration
- API Key Management — API key lifecycle