---
title: "Release an unused reservation"
method: POST
path: "/v1/reservations/{reservation_id}/release"
tags: ["Reservations"]
---

# Release an unused reservation

`POST /v1/reservations/{reservation_id}/release`

## Request

- Base URL: `https://api.cycles.local`
- URL: `https://api.cycles.local/v1/reservations/{reservation_id}/release`
- Auth: one of:
  - API key in header `X-Cycles-API-Key`
  - API key in header `X-Admin-API-Key`

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 `reason` body field
    with a structured tag (e.g. "[INCIDENT_FORCE_RELEASE]")
    for grep-ability in the audit log.

## Path parameters

- `reservation_id` string, required

## Headers

- `X-Idempotency-Key` string

## Request body

- object
  - `idempotency_key` string, required
  - `reason` string

## Response `200`

Release succeeded

- object
  - `status` 'RELEASED', required
  - `released` object, required
    - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
    - `amount` integer, required
  - `balances` object[]
    - `scope` string, required: Canonical scope identifier (server-derived), e.g. "tenant:t1", "workflow:run123"
    - `scope_path` string, required: Canonical hierarchical path (server-derived)
    - `remaining` object, required: Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.
      - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
      - `amount` integer, required: Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved).
    - `reserved` object
      - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
      - `amount` integer, required
    - `spent` object
      - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
      - `amount` integer, required
    - `debt` object
      - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
      - `amount` integer, required
    - `allocated` object
      - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
      - `amount` integer, required
    - `overdraft_limit` object
      - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
      - `amount` integer, required
    - `is_over_limit` boolean: True when debt > overdraft_limit due to concurrent commits creating cumulative debt beyond the limit, or when ALLOW_IF_AVAILABLE could not cover the full overage delta and capped the charge. When true, ALL new reservations against this scope are blocked with 409 OVERDRAFT_LIMIT_EXCEEDED until reconciled (debt repaid below overdraft_limit, or is_over_limit cleared by operator). Defaults to false when absent.
  - `cycles_evidence` object: Reference to the CyclesEvidence envelope emitted for an operation. The `evidence_id` is the sha256 content hash of the JCS-canonical envelope and is computed SYNCHRONOUSLY at decision time, so it is returned on the operation response even though the envelope is Ed25519-signed and stored ASYNCHRONOUSLY. A caller (e.g. an APS gateway) binds its own signed receipt to this evidence by recording `evidence_id`, then fetches the envelope at `cycles_evidence_url` (see `getEvidence`) to verify it offline. Because signing/storage is async, the envelope MAY be served shortly after this response — consumers SHOULD treat a transient `404` from `getEvidence` as not-yet-available and retry. TRANSPORT METADATA, NOT ATTESTED: `cycles_evidence` is added to the response for the caller's convenience and is NOT part of the evidence the envelope attests. `remaining_ttl_ms` on ReservationCreateResponse is likewise a volatile transport observation rather than attested decision data, so a same-key replay can safely recompute it. The `evidence_id` is computed over the operation response WITHOUT either transport-only field — the envelope's `payload.<artifact>.response` mirror (`cycles-evidence-v0.2.yaml`) keeps `additionalProperties: false` and omits them. Implementations MUST compute `evidence_id` before stamping transport-only fields onto the response, and MUST NOT include either field in the attested payload.
    - `evidence_id` string, required: sha256 content id of the emitted envelope; resolve via getEvidence.
    - `cycles_evidence_url` string, uri, required: Absolute URL of the signed envelope, formed as `{server_id}/evidence/{evidence_id}`. Note `server_id` is already the canonical deployment base INCLUDING the `/v1` prefix (e.g. `https://cycles.example.com/v1`), so the join adds only `/evidence/{evidence_id}` — it MUST NOT re-add `/v1`.

## Other responses

- `400`: Error response
- `401`: Error response
- `403`: Error response
- `404`: Error response
- `409`: Error response
- `410`: Error response
- `500`: Error response

---

[API](https://skmtc.dev/runcycles/apis/cycles-budget-authority-api.md) · [All operations](https://skmtc.dev/runcycles/apis/cycles-budget-authority-api/llms.txt) · [OpenAPI document](https://skmtc.dev/runcycles/apis/cycles-budget-authority-api/revisions/7d42267c1ea0?raw)
