Cycles Protocol API
Interactive reference for the Cycles Protocol endpoints. All requests require an API key passed via the X-Api-Key header.
Getting started with the API?
- Deploy the Cycles server or use a running instance
- Create a tenant and API key via the Admin API
- Make your first Reserve call below
PURPOSE (v0):
- Provide a minimal, language-agnostic protocol to enforce deterministic spend exposure for agent runtimes
via concurrency-safe reservations and idempotent commits. - Include optional integration endpoints: /decide (soft landing) and /balances (operator visibility).
NON-GOALS (v0) (NORMATIVE):
- Budget establishment and funding operations are out of scope for v0.
v0 defines the reservation/commit/release enforcement plane and balance reporting only. - v0 provides no API for budget CRUD (create/update/delete), allocation setting, credit/deposit, or debit/withdrawal.
Implementations MAY provide these via an operator/admin plane or a separate API; future versions may standardize them. - A reservation lifecycle is denominated in exactly one unit (single-unit reserve/commit/release).
Multi-unit atomic reservation/settlement is a v1+ concern.
AUTH & TENANCY (NORMATIVE):
- Requests are authenticated via X-Cycles-API-Key.
- Server determines an "effective tenant" from the API key (or other auth context).
- Subject.tenant is a budgeting dimension and MUST be validated against the effective tenant.
If mismatched, server MUST return 403 FORBIDDEN. - Reservation ownership MUST be enforced: every reservation is bound to the effective tenant at creation.
Any subsequent GET/commit/release for a reservation that exists but is owned by a different tenant
MUST return 403 FORBIDDEN. - Balance visibility MUST be tenant-scoped: the server MUST only return balances within the effective tenant.
If a request attempts to query another tenant (e.g., tenant filter mismatches), server MUST return 403 FORBIDDEN.
EVOLUTION CONTRACT:
- This API starts at v0.1.0 with /v1 paths to avoid future client churn.
- v1+ evolution MUST be backward-compatible by default: new fields are additive, existing field meanings MUST NOT change.
- Breaking changes (e.g., new required fields, semantic changes) require a new major API path (e.g., /v2).
CORE INVARIANTS:
- Reserve is atomic across all derived scopes.
- Commit and release are idempotent.
- No double-charge on retries (idempotency key enforced).
ERROR SEMANTICS (NORMATIVE):
- Budget denials MUST return HTTP 409 with error=BUDGET_EXCEEDED.
- Overdraft limit exceeded MUST return HTTP 409 with error=OVERDRAFT_LIMIT_EXCEEDED in two cases:
- During commit: when overage_policy=ALLOW_WITH_OVERDRAFT and (current_debt + delta) > overdraft_limit at commit time
- During reservation: when the scope is in over-limit state (debt > overdraft_limit due to prior concurrent commits)
- Outstanding debt blocking reservation MUST return HTTP 409 with error=DEBT_OUTSTANDING
(when debt > 0 and new reservation is attempted). - Finalized reservations MUST return HTTP 409 with error=RESERVATION_FINALIZED.
- Expired reservations MUST return HTTP 410 with error=RESERVATION_EXPIRED.
(commit/release: beyond expires_at_ms + grace_period_ms; extend: beyond expires_at_ms). - Reservations that never existed MUST return HTTP 404 with error=NOT_FOUND.
- HTTP 429 is reserved for server-side throttling/rate limiting (optional in v0), not deterministic budget exhaustion.
- Unit mismatch MUST return HTTP 400 with error=UNIT_MISMATCH in any of these cases:
(a) reserve — estimate.unit does not match any budget stored for the derived scopes,
but at least one of those scopes has a budget in a different unit;
(b) commit — actual.unit differs from the reservation's estimate.unit;
(c) event — actual.unit does not match the budget stored for the target scope;
(d) decide — estimate.unit does not match any budget stored for the derived scopes,
but at least one of those scopes has a budget in a different unit. This is an
exception to /decide's general "return decision=DENY (200) without 4xx" pattern,
which applies only to budget-state conditions (debt, overdraft, insufficient
remaining), not request-validity errors like a wrong unit.
When the cause is a wrong unit (rather than the absence of any budget at the scope),
servers SHOULD populate the error response'sdetailsobject with:scope— the canonical scope identifier where the mismatch was detectedrequested_unit— the unit supplied by the clientexpected_units— array of units for which a budget does exist at that scope
so clients can self-correct without a separate lookup. HTTP 404 with error=NOT_FOUND
is reserved for the case where the target scope has no budget in ANY unit (the
runtime plane uses the single NOT_FOUND code for all resource-not-found conditions;
the message field carries the specific reason, e.g. "Budget not found for provided
scope: ...").
- For expiry comparisons, “now” refers to server time (not client-provided time).
- When is_over_limit=true, server MUST return 409 OVERDRAFT_LIMIT_EXCEEDED for new reservations.
This takes precedence over DEBT_OUTSTANDING even when debt > 0.
OVERDRAFT RECONCILIATION (NORMATIVE):
- When concurrent commits cause debt > overdraft_limit on a scope, the server MUST mark that scope as "over-limit" (is_over_limit=true).
- Over-limit scopes MUST reject ALL new reservation attempts with 409 OVERDRAFT_LIMIT_EXCEEDED until debt is reduced below overdraft_limit.
- Operators reconcile over-limit scopes via budget funding operations (out-of-scope for this API).
When debt is repaid below overdraft_limit, is_over_limit automatically returns to false. - Servers SHOULD provide monitoring/alerting when scopes enter over-limit state:
- Log events with scope identifier, current debt, and overdraft_limit
- Optionally emit webhooks or notifications to operators
- Optionally expose metrics endpoint showing over-limit scope count
- Clients SHOULD handle 409 OVERDRAFT_LIMIT_EXCEEDED on reservation as a signal to wait/retry with exponential backoff, or escalate to operators.
IDEMPOTENCY (NORMATIVE):
- If X-Idempotency-Key header is present and body.idempotency_key is present, they MUST match.
- Server MUST enforce idempotency per (effective tenant, endpoint, idempotency_key).
- On replay of an idempotent request that previously succeeded, server MUST return the original successful
response payload (including any server-generated identifiers such as reservation_id). - If the same key is reused with a different request payload, server MUST return 409 IDEMPOTENCY_MISMATCH.
- Servers SHOULD compare idempotency payloads using a canonical JSON representation
(e.g., RFC 8785 JSON Canonicalization Scheme) or an equivalent stable serialization.
SCOPE DERIVATION (NORMATIVE):
- Server derives canonical scope identifiers and a canonical scope_path from Subject fields.
- Canonical ordering is: tenant → workspace → app → workflow → agent → toolset.
- Only explicitly provided subject levels are included in scope paths; intermediate gaps are skipped (not filled with "default").
- Scopes without budgets are skipped during enforcement; at least one derived scope MUST have a budget.
- affected_scopes returned by the server MUST be in that canonical order.
RESERVATION LEASING (GUIDANCE):
- To mitigate "zombie reservations" (client crash after reserve), SDKs SHOULD:
- keep ttl_ms short (typically 10s–30s),
- include modest estimation buffers when using overage_policy=REJECT,
- reserve in small initial leases and increase gradually ("slow start") for long or bursty operations,
- prefer chunked reserve/commit cycles for long-running actions rather than a single large reservation.
OVERDRAFT MONITORING (GUIDANCE):
- Implementations SHOULD provide visibility into over-limit states:
- Dashboard showing scopes with is_over_limit=true
- Alerts when debt exceeds overdraft_limit
- Time-series metrics: debt_utilization = debt / overdraft_limit
- Recommended alerting thresholds:
- Warning at 80% of overdraft_limit
- Critical at 100% (over-limit state)
- Recommended operator runbook:
- Investigate which reservations caused the over-limit state
- Determine if overdraft_limit should be increased (normal variance) or if this represents anomalous consumption (incident)
- Fund the scope to repay debt below limit
- Monitor that is_over_limit returns to false
- Resume operations automatically
CORRELATION AND TRACING (NORMATIVE, cross-plane):
This section defines the cross-surface correlation contract for the entire Cycles
spec family. It is normative for every Cycles server operation on every plane
(runtime, governance-admin, action-kinds, and any extension that layers onto
these bases). Companion specs SHOULD carry a brief pointer to this section and
MUST NOT restate a conflicting contract.
Three-tier correlation model:
* request_id — one HTTP request grain. Set by the server. Echoed on
X-Request-Id response header, on ErrorResponse, and on every event / audit
entry that is causally downstream of the request (including entries emitted
from queued or deferred work spawned by the request).
* trace_id — logical-operation grain. W3C Trace Context-compatible. Accepted
from inbound headers or generated by the server. Echoed on X-Cycles-Trace-Id
response header and propagated to events, audit entries, and outbound
webhook deliveries.
* correlation_id — event-stream cluster grain. Set by the server as a
deterministic hash over (tenant_id, scope, action_kind_or_risk_class,
window, window_key) to JOIN threshold-alert → trip → reset chains and
observed_denied ↔ reservation.denied pairs. Scoped to the event stream only.
Inbound header precedence (server extracts trace_id by the first matching rule):
1. traceparent header, if present AND parses as a valid W3C Trace Context
value (version 00, non-all-zero trace-id, non-all-zero span-id) → use its
trace-id (the leftmost 32-hex segment).
2. Else X-Cycles-Trace-Id header, if present AND matches ^[0-9a-f]{32}$
AND is not all-zero → use its value directly.
3. Else server generates a new trace_id: 16 random bytes encoded as 32
lowercase hex characters. The all-zero value is invalid per W3C Trace
Context §3.2.2.3 and MUST be re-rolled.
Header validation and precedence rules:
* A malformed traceparent OR malformed X-Cycles-Trace-Id MUST be treated
as absent for that header; the server falls through to the next rule. The
server MUST NOT reject a request for a malformed correlation header.
* If both traceparent and X-Cycles-Trace-Id are present, both valid, but
their trace-ids DISAGREE, traceparent wins (OpenTelemetry interop takes
precedence over the flat convenience header). The server MAY log this
condition for diagnostics but MUST NOT reject the request.
Outbound response contract (every plane, every response):
* Servers MUST echo X-Cycles-Trace-Id on every response (2xx, 4xx, 5xx).
The header is declared as X-Cycles-Trace-Id in this document's
components.headers and re-declared in the companion spec's own
components.headers for OpenAPI tooling conformance.
* ErrorResponse bodies MUST carry trace_id on every conformant error.
* Events and audit-log entries causally downstream of the request MUST
carry trace_id. See each companion spec's Event / AuditLogEntry schema.
Propagation contract:
* The server propagates trace_id onto: the audit-log entry for the request
(one per authenticated request that hits the governance plane), every
event emitted as a side effect of the request (runtime or governance),
and every outbound webhook delivery. Propagation across thread, queue, or
process boundaries is REQUIRED; loss at the request-thread boundary is
non-compliant.
* Outbound webhook deliveries carry X-Cycles-Trace-Id AND traceparent
headers constructed as documented in the WEBHOOK EVENT GUIDANCE section
below (including the trace-flags preservation rule).
Format: ^[0-9a-f]{32}$ — 32 lowercase hex characters (128-bit trace ID).
Backward compatibility:
* trace_id is declared as an OPTIONAL property on ErrorResponse, Event,
and AuditLogEntry schemas (no wire-contract break). Servers conformant
with this section MUST populate it; clients MUST tolerate its absence on
entries emitted by older servers.
* Adding X-Cycles-Trace-Id as a response header is additive; clients that
do not read the header are unaffected.
* Accepting traceparent / X-Cycles-Trace-Id as inbound request headers
is additive; clients that do not send them are unaffected.
WEBHOOK EVENT GUIDANCE (GUIDANCE):
Implementations MAY emit webhook events when runtime operations produce observable state changes.
This enables operators and tenant applications to react to budget state transitions in real-time
without polling. The webhook delivery system is separate from the protocol endpoints — it does not
add new API paths to the runtime server.
Event types emitted by the runtime server:
* reservation.denied — Reserve or decide returned DENY (budget exceeded, overdraft limit, frozen, etc.)
* reservation.commit_overage — Commit actual amount exceeded estimated amount
* reservation.expired — Reservation TTL expired without commit or release (via background sweeper)
* budget.exhausted — Remaining budget reached 0 after a reservation or event
* budget.debt_incurred — Commit created new debt via ALLOW_WITH_OVERDRAFT policy
* budget.over_limit_entered — is_over_limit flipped to true (debt > overdraft_limit)
* budget.over_limit_exited — Debt repaid below overdraft_limit (via admin funding operations)
* budget.threshold_crossed — Utilization crossed a configured threshold (e.g., 80%, 95%)
* budget.burn_rate_anomaly — Spend rate exceeded baseline by configured multiplier
Event types emitted by the admin/operator server:
* tenant.created/updated/suspended/reactivated/closed — Tenant lifecycle
* budget.created/updated/funded/debited/reset/debt_repaid/frozen/unfrozen/closed — Budget lifecycle
* api_key.created/revoked/expired/permissions_changed/auth_failed — API key lifecycle
* policy.created/updated/deleted — Policy lifecycle
* system.store_connection_lost/restored, system.high_latency — System health
* system.webhook_delivery_failed — Meta-alert for persistent delivery failures
Standard event payload schema (JSON):
* event_id (string, required) — Globally unique (e.g., "evt_01abc..."). Use for deduplication.
* event_type (string, required) — Dotted format: "{category}.{action}" (e.g., "reservation.denied")
* category (string, required) — One of: budget, reservation, tenant, api_key, policy, system
* timestamp (string, date-time, required) — ISO 8601 UTC
* tenant_id (string, required) — Tenant context. System events use "system".
* scope (string, optional) — Full scope path affected (e.g., "tenant:acme/agent:bot")
* actor (object, optional) — Who caused the event: { type: admin|api_key|system|scheduler, key_id?, source_ip? }
* source (string, required) — Service that emitted: "cycles-server", "cycles-admin", "expiry-sweeper"
* data (object, optional) — Event-specific payload (varies by event_type)
* correlation_id (string, optional) — Links related events for chain reconstruction
* request_id (string, optional) — X-Request-Id from the originating HTTP request.
MUST be populated on every event causally downstream of an HTTP request,
including events emitted from queued, deferred, or otherwise-async work spawned
by that request. MAY be absent on internal sweeper/expiry-generated events that
have no originating HTTP request. See CORRELATION AND TRACING section below.
* trace_id (string, optional, pattern ^[0-9a-f]{32}$) — W3C Trace Context trace-id
for the logical operation. Populated on every event produced by a server that
conforms to the CORRELATION AND TRACING contract below.
* metadata (object, optional) — Operator-defined key-value pairs
Webhook delivery protocol:
* Delivery method: HTTP POST to subscriber's URL with JSON event payload as body
* Delivery semantics: At-least-once. Consumers MUST deduplicate using event_id.
* Ordering: Events for the same tenant are dispatched in order. Cross-tenant ordering NOT guaranteed.
* Non-blocking: Webhook delivery MUST NOT block the operation that produced the event.
Required HTTP headers on webhook delivery:
* Content-Type: application/json
* X-Cycles-Event-Id: {event_id} — For deduplication
* X-Cycles-Event-Type: {event_type} — For routing
* X-Cycles-Signature: sha256={hex} — HMAC-SHA256 of raw request body using subscription's signing secret
* X-Cycles-Trace-Id: {trace_id} — W3C Trace Context trace-id (32-hex) for the
logical operation that produced this event. Always required; the server always
has a trace_id per the CORRELATION AND TRACING fallback-generate rule.
* traceparent: 00-{trace_id}-{fresh-span-id-16-hex}-{trace-flags} — W3C Trace
Context version 00 header. Always required. trace_id MUST equal the value in
X-Cycles-Trace-Id. span-id MUST be freshly generated for the outbound delivery
(NOT reused from inbound). trace-flags rules:
- If the inbound request to Cycles carried a valid traceparent, the server
MUST preserve the inbound trace-flags byte on the outbound traceparent
(so a sampled=0 upstream is not silently flipped to sampled=1).
- If the trace was derived from X-Cycles-Trace-Id (no inbound W3C
traceparent) OR generated fresh by the server, the server uses a default
trace-flags value of 01 (sampled).
The trace_id field also appears in the event envelope body so subscribers
unfamiliar with W3C Trace Context can still correlate via the JSON payload.
* User-Agent: {service-name}/{version}
* Custom headers from subscription configuration (e.g., Authorization)
Signature verification (X-Cycles-Signature):
* Algorithm: HMAC-SHA256
* Input: Raw JSON request body (bytes, not parsed)
* Key: Subscription's signing_secret (UTF-8 encoded)
* Format: "sha256=" + lowercase hex encoding of HMAC digest
* Consumers SHOULD verify the signature before processing the event.
* Use constant-time comparison (e.g., hmac.compare_digest) to prevent timing attacks.
Retry and failure handling:
* On non-2xx response: exponential backoff retry (default: 5 retries, 1s/2s/4s/8s/16s, max 60s)
* After all retries exhausted: delivery marked FAILED, system.webhook_delivery_failed event emitted
* After N consecutive failures (default 10): subscription auto-disabled (status → DISABLED)
* Disabled subscriptions can be re-enabled via admin API (resets failure counter)
Retention:
* Event records: 90 days hot storage (recommended). TTL enforced via Redis EXPIRE.
* Delivery records: 14 days (operational debugging data).
* ZSET index entries: Trimmed hourly by background cleanup job.
* Stale deliveries: Deliveries older than 24h (configurable) are auto-failed on pickup
to prevent delivering ancient webhooks after prolonged service outage.
Extensibility:
* New event types MAY be added in future versions without a breaking change.
* Consumers MUST ignore unrecognized event types gracefully.
* Custom event types MUST use a "custom." prefix (e.g., "custom.billing.invoice_sent").
License
Apache 2.0Servers
Optional preflight policy decision (no reservation created)
Returns ALLOW / DENY, optionally with Caps for soft landing. This endpoint does not reserve budget. Clients that require concurrency safety MUST use /v1/reservations.
IDEMPOTENCY (NORMATIVE): - On replay with the same idempotency_key, the server MUST return the original successful response payload.
TENANCY (NORMATIVE): - subject.tenant MUST match the effective tenant derived from auth; otherwise the server MUST return 403 FORBIDDEN.
DEBT/OVERDRAFT STATE (NORMATIVE): - If the subject scope has debt > 0 or is_over_limit=true, server SHOULD return decision=DENY with reason_code=DEBT_OUTSTANDING or reason_code=OVERDRAFT_LIMIT_EXCEEDED respectively. Server MUST NOT return 409 for these conditions on /decide.
Idempotency on /decide is for request deduplication only. A replayed ALLOW response reflects budget state at the time of the original call; clients MUST NOT treat a replayed decision as current budget authorization.
Authorizations
Parameters
Header Parameters
Optional idempotency key header. If both header and body idempotency_key are provided, they MUST match. Server MUST enforce idempotency per endpoint by (effective tenant, endpoint, idempotency_key). On replay of an idempotent request that previously succeeded, server MUST return the original successful response payload (including any server-generated identifiers such as reservation_id).
1256Request Body
Responses
Decision result
List reservations (optional recovery/debug endpoint)
Lists reservations visible to the effective tenant. This endpoint is OPTIONAL in v0 deployments.
RECOVERY (NORMATIVE):
- If a client loses reservation_id, it MAY recover it by querying with idempotency_key and/or subject filters.
- If idempotency_key is provided, the server SHOULD return at most one matching reservation (uniqueness is expected per (effective tenant, endpoint, idempotency_key)).
- Servers SHOULD support filtering by status=ACTIVE to identify "stuck" reservations.
SUBJECT FILTERS (GUIDANCE):
- Query parameters tenant/workspace/app/workflow/agent/toolset filter on the canonical Subject fields.
- Filtering on Subject.dimensions is out of scope for v0 unless explicitly implemented by the server.
TENANCY (NORMATIVE):
- Under ApiKeyAuth: the server MUST scope results to the effective
tenant derived from auth. If the tenant query parameter is
provided, it is validation-only and MUST match the effective
tenant; otherwise the server MUST return 403 FORBIDDEN. If
tenant is omitted, the effective tenant is used. - Under AdminKeyAuth (added 2026-04-13): the admin caller has
no effective tenant, so the tenant query parameter is REQUIRED
and used as a FILTER (not validation). Omitting it MUST return
400 INVALID_REQUEST with message "tenant query parameter is
required when using admin key authentication". This matches
the existing dual-auth pattern on listBudgets / listPolicies
in the governance-admin spec — same single param, semantics
keyed on auth type.
Authorizations
Parameters
Query Parameters
Lookup handle to recover the reservation_id from a prior createReservation call.
1256Filter by reservation status (e.g., ACTIVE).
"ACTIVE""COMMITTED""RELEASED""EXPIRED"Sort key. When provided, results are returned in the requested order and the returned cursor encodes the sort key so subsequent pages continue in sort order. When omitted, servers use their default ordering (unchanged pre-revision behavior). The reserved key sorts by the integer amount within each row; the single-unit-per- reservation invariant makes this comparison well-defined. The scope_path key sorts lexicographically over the server-derived canonical scope path string (e.g. "tenant:acme/workspace:prod/agent:x"); the tenant key sorts over the Subject.tenant field. Servers that don't recognize the parameter MUST ignore it without error.
"reservation_id""tenant""scope_path""status""reserved""created_at_ms""expires_at_ms""created_at_ms"Sort direction. Default descending.
"asc""desc""desc"Maximum number of results to return
120050Opaque cursor from previous response
Responses
Reservations list
Reserve budget for a planned action (concurrency-safe)
Atomically reserves the estimated amount across server-derived scopes and returns a reservation_id. Reservations expire at expires_at_ms; commits are accepted through (expires_at_ms + grace_period_ms).
If dry_run=true, server MUST evaluate the full reservation request and return decision/caps/affected_scopes/balances as if the reservation were live, but MUST NOT modify balances, persist a reservation, or require commit/release.
DRY-RUN RESPONSE RULES (NORMATIVE): - reservation_id and expires_at_ms MUST be absent. - affected_scopes MUST be populated regardless of decision outcome (ALLOW / ALLOW_WITH_CAPS / DENY). - If decision=ALLOW_WITH_CAPS, caps MUST be present; otherwise caps MUST be absent. - If decision=DENY, reason_code SHOULD be populated; it is the primary diagnostic signal for why the dry_run was denied. - balances MAY be populated (recommended for operator visibility), but MUST reflect a non-mutating evaluation.
OVER-LIMIT BLOCKING (NORMATIVE): - If ANY affected scope has debt > overdraft_limit (is_over_limit=true), the reservation MUST be rejected
with 409 OVERDRAFT_LIMIT_EXCEEDED, regardless of available remaining budget.
- This blocks new work when overdraft reconciliation is needed.
IDEMPOTENCY (NORMATIVE): - On replay with the same idempotency_key, the server MUST return the original successful response payload, including the original reservation_id (if any).
TENANCY (NORMATIVE): - subject.tenant MUST match the effective tenant derived from auth; otherwise the server MUST return 403 FORBIDDEN.
Authorizations
Parameters
Header Parameters
Optional idempotency key header. If both header and body idempotency_key are provided, they MUST match. Server MUST enforce idempotency per endpoint by (effective tenant, endpoint, idempotency_key). On replay of an idempotent request that previously succeeded, server MUST return the original successful response payload (including any server-generated identifiers such as reservation_id).
1256Request Body
Responses
Reservation decision (ALLOW/DENY with optional caps)
Get reservation details (optional, for debugging)
Retrieve current status and details of a reservation by ID. Useful for debugging and monitoring long-running operations.
TENANCY (NORMATIVE):
- Under ApiKeyAuth: if the reservation exists but is owned by a
different effective tenant, the server MUST return 403 FORBIDDEN. - Under AdminKeyAuth (added 2026-04-13): admin operators can
read any reservation regardless of owning tenant; reservation_id
already pins the owner so no extra parameter is needed.
Authorizations
Parameters
Path Parameters
1128Responses
Reservation details
Commit actual spend for a reservation (auto-releases delta)
Commits actual spend. If actual < reserved, delta is released automatically. If actual > reserved, behavior is controlled by the reservation's overage_policy.
IDEMPOTENCY (NORMATIVE): - On replay with the same idempotency_key, the server MUST return the original successful response payload.
TENANCY (NORMATIVE): - If the reservation exists but is owned by a different effective tenant, the server MUST return 403 FORBIDDEN.
Authorizations
Parameters
Header Parameters
Optional idempotency key header. If both header and body idempotency_key are provided, they MUST match. Server MUST enforce idempotency per endpoint by (effective tenant, endpoint, idempotency_key). On replay of an idempotent request that previously succeeded, server MUST return the original successful response payload (including any server-generated identifiers such as reservation_id).
1256Path Parameters
1128Request Body
Responses
Commit succeeded
Release an unused reservation
Releases reserved amount back to remaining budget.
IDEMPOTENCY (NORMATIVE): - On replay with the same idempotency_key, the server MUST return the original successful response payload.
TENANCY (NORMATIVE):
- Under ApiKeyAuth: if the reservation exists but is owned by
a different effective tenant, the server MUST return 403 FORBIDDEN. - Under AdminKeyAuth (added 2026-04-13): admin operators can
release any reservation regardless of owning tenant — the
ops use case is "force-expire a hung reservation" during
incident response. reservation_id pins the owner so no
extra parameter is needed.
AUDIT (NORMATIVE, AdminKeyAuth path):
- The audit-log entry for an admin-driven release MUST record
actor_type=admin_on_behalf_of (existing audit field, value
already used on createBudget / createPolicy / updatePolicy
in the governance-admin spec). This lets security review
distinguish admin-driven releases from tenant self-service
without joining to the keys table. - The entry MUST be discoverable via the governance plane's
audit-query surface (GET /v1/admin/audit/logs in the
governance-admin spec). Writing the entry to a store the
governance audit-query endpoint cannot read from does not
satisfy this requirement — the operational intent is that
admin-driven release actions surface in the same admin-
facing audit view that shows governance-plane audit
entries. How servers achieve this is an implementation
concern and out of scope for the spec. - Callers SHOULD populate the optional
reasonbody field
with a structured tag (e.g. "[INCIDENT_FORCE_RELEASE]")
for grep-ability in the audit log.
Authorizations
Parameters
Header Parameters
Optional idempotency key header. If both header and body idempotency_key are provided, they MUST match. Server MUST enforce idempotency per endpoint by (effective tenant, endpoint, idempotency_key). On replay of an idempotent request that previously succeeded, server MUST return the original successful response payload (including any server-generated identifiers such as reservation_id).
1256Path Parameters
1128Request Body
Responses
Release succeeded
Extend reservation TTL (lease refresh / heartbeat)
Extends the expiry of an ACTIVE reservation to support long-running agent workflows.
SEMANTICS (NORMATIVE): - Extension updates expires_at_ms only; it MUST NOT change reserved amount, unit, subject, action, scope_path, or affected_scopes. - Extensions MUST be applied in a concurrency-safe way. - Server MUST accept extend only when status is ACTIVE and the reservation has not yet expired
(i.e., server time ≤ expires_at_ms). If the reservation is expired, server MUST return 410 with error=RESERVATION_EXPIRED.
IDEMPOTENCY (NORMATIVE): - On replay with the same idempotency_key, the server MUST return the original successful response payload.
TENANCY (NORMATIVE): - If the reservation exists but is owned by a different effective tenant, the server MUST return 403 FORBIDDEN.
ERROR SEMANTICS (NORMATIVE): - If the reservation is COMMITTED or RELEASED, server MUST return 409 with error=RESERVATION_FINALIZED. - If the reservation is expired (server time > expires_at_ms), server MUST return 410 with error=RESERVATION_EXPIRED. - If the reservation never existed, server MUST return 404 with error=NOT_FOUND.
Authorizations
Parameters
Header Parameters
Optional idempotency key header. If both header and body idempotency_key are provided, they MUST match. Server MUST enforce idempotency per endpoint by (effective tenant, endpoint, idempotency_key). On replay of an idempotent request that previously succeeded, server MUST return the original successful response payload (including any server-generated identifiers such as reservation_id).
1256Path Parameters
1128Request Body
Responses
Reservation expiry extended
Query current budget balances across scopes (nice-to-have)
Returns balances for scopes matching the provided subject filter. include_children MAY be ignored by v0 implementations.
SUBJECT FILTER REQUIREMENT (NORMATIVE): - At least one of tenant/workspace/app/workflow/agent/toolset MUST be provided. - If all of these filters are omitted, server MUST return 400 with error=INVALID_REQUEST.
TENANCY (NORMATIVE): - The server MUST scope results to the effective tenant derived from auth. - If the tenant query parameter is provided, it is validation-only and MUST match the effective tenant; otherwise the server MUST return 403 FORBIDDEN. - If tenant is omitted, the effective tenant is used.
Authorizations
Parameters
Query Parameters
falseMaximum number of results to return
120050Opaque cursor from previous response
Responses
Balance response
Optional post-only accounting when pre-estimation is not available
Records an accounting event without a reservation. This endpoint is optional in v0 deployments. The event MUST be applied atomically across all derived scopes before the server returns 201.
IDEMPOTENCY (NORMATIVE): - On replay with the same idempotency_key, the server MUST return the original successful response payload.
TENANCY (NORMATIVE): - subject.tenant MUST match the effective tenant derived from auth; otherwise the server MUST return 403 FORBIDDEN.
Authorizations
Parameters
Header Parameters
Optional idempotency key header. If both header and body idempotency_key are provided, they MUST match. Server MUST enforce idempotency per endpoint by (effective tenant, endpoint, idempotency_key). On replay of an idempotent request that previously succeeded, server MUST return the original successful response payload (including any server-generated identifiers such as reservation_id).
1256Request Body
Responses
Event created and atomically applied to balances