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. - If the owning tenant's status is CLOSED (deployments with a governance plane), dry_run MUST NOT return 409 TENANT_CLOSED; a fresh (non-replay) evaluation MUST return decision=DENY with reason_code=TENANT_CLOSED, reflecting as-if-live the 409 the persisting create returns (same-key replays of pre-close evaluations follow the IDEMPOTENCY rule below). A tenant record whose status cannot be determined (malformed record) MUST fail closed with 500 INTERNAL_ERROR even on dry_run — see the closed-tenant binding in ERROR SEMANTICS.
- 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 outcome and payload, including the original reservation_id (if any), except that remaining_ttl_ms is a volatile transport observation not included in the attested CyclesEvidence payload. When emitted, it MUST be recomputed from the original expires_at_ms and current authoritative server time while constructing the replay response, MUST NOT be copied from the stored response, and MUST be 0 if the reservation is no longer ACTIVE. All other fields replay verbatim.
TENANCY (NORMATIVE): - subject.tenant MUST match the effective tenant derived from auth; otherwise the server MUST return 403 FORBIDDEN.
Request
- Base URL: https://api.cycles.local
- URL: https://api.cycles.local/v1/reservations
- Auth: API key in header X-Cycles-API-Key
Headers
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 outcome and payload (including any server-generated identifiers such as reservation_id), except that remaining_ttl_ms on a replayed createReservation or extendReservation success is a volatile response observation and is not part of the attested CyclesEvidence payload. When emitted, it MUST be recomputed from the original expires_at_ms and the current authoritative server time, never copied from the stored response; it MUST be 0 if the reservation is no longer ACTIVE. All other fields replay verbatim.
Request body
Example request
{
"idempotency_key": "idem_20260412_run42_step1",
"subject": {
"tenant": "acme-corp",
"workspace": "prod",
"agent": "summarizer"
},
"action": {
"kind": "llm.completion",
"name": "summarize-document"
},
"estimate": {
"unit": "USD_MICROCENTS",
"amount": 500000
},
"ttl_ms": 30000
}Response
Reservation decision (ALLOW/DENY with optional caps)
Example response
{
"decision": "ALLOW",
"reservation_id": "rsv_a1b2c3d4",
"reserved": {
"unit": "USD_MICROCENTS",
"amount": 500000
},
"expires_at_ms": 1712956830000,
"remaining_ttl_ms": 600000,
"scope_path": "tenant:acme-corp/workspace:prod/agent:summarizer",
"affected_scopes": [
"tenant:acme-corp",
"tenant:acme-corp/workspace:prod",
"tenant:acme-corp/workspace:prod/agent:summarizer"
]
}Changes
No recorded changes to this operation across all 1 revision of this API.