---
title: "List reservations (optional recovery/debug endpoint)"
method: GET
path: "/v1/reservations"
tags: ["Reservations"]
---

# List reservations (optional recovery/debug endpoint)

`GET /v1/reservations`

## Request

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

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.

TIME-RANGE FILTERS (NORMATIVE, ADDITIVE):
  Three independent inclusive time-window filters are available,
  each bound to a specific timestamp field on the reservation
  entity. All bounds are ISO 8601 date-time strings. All are
  additive parameters: servers that don't recognize them MUST
  ignore without error.

  - `from` / `to` (revision 2026-05-21) — bound on
    `created_at_ms`. The original window filter; matches
    the family-wide convention on `listAuditLogs`,
    `listEvents`, and `listWebhookDeliveries`. Always binds
    to `created_at_ms` regardless of `sort_by`.

  - `expires_from` / `expires_to` — bound on `expires_at_ms`.
    Primary use case: locate reservations that have expired
    (or will expire) within a window — e.g. cleanup sweepers
    that need to discover abandoned ACTIVE reservations. The
    field is required on every `ReservationSummary` /
    `ReservationDetail`, so this filter applies to every
    row regardless of `status`.

  - `finalized_from` / `finalized_to` — bound on
    `finalized_at_ms`. The `finalized_at_ms` field is OPTIONAL
    on `ReservationSummary` / `ReservationDetail` and is
    populated ONLY on COMMITTED and RELEASED rows (absent on
    ACTIVE and EXPIRED). Rows where the field is absent MUST
    be excluded from results when either `finalized_from` or
    `finalized_to` is supplied — the predicate naturally fails
    on field-absent rows. Callers who want a window over
    EXPIRED rows should use `expires_from` / `expires_to`
    against `expires_at_ms`, which is required on every row.

  Validation (applies to all three pairs):
  - For each pair, `from > to` MUST return 400 INVALID_REQUEST
    (e.g., `expires_from > expires_to`).
  - Either side may be supplied alone (open interval).
  - Blank-string values for any window bound MUST be treated
    as unset (NORMATIVE). A client sending
    `?expires_from=&finalized_to=` MUST be handled identically
    to one omitting both parameters entirely. This applies to
    all six bounds (`from`, `to`, `expires_from`, `expires_to`,
    `finalized_from`, `finalized_to`). Servers MUST NOT 400 on
    empty-string values despite the `format: date-time`
    declaration. Rationale: clients commonly emit unconditional
    query strings whose values come from possibly-unset
    variables (e.g., `?from=${maybeUnset}&to=${maybeUnset}`),
    and an unset variable rendering as `""` is the common
    failure mode; rejecting these as malformed surfaces a
    cryptic 400 that adds nothing over treating them as unset.
  - The three pairs combine with AND semantics: a row must
    satisfy every supplied window predicate to be returned.

  Cursor invalidation: sorted-path cursors fold the supplied
  window bounds into the canonical filter hash. Reusing a
  sorted cursor under a different `(from, to, expires_from,
  expires_to, finalized_from, finalized_to)` tuple returns
  400 INVALID_REQUEST. Legacy SCAN cursors do not carry filter
  state; callers paginating without `sort_by` must keep all
  window bounds stable across pages, matching the legacy
  path's treatment of every other filter.

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.

## Query parameters

- `idempotency_key` string
- `status` 'ACTIVE' | 'COMMITTED' | 'RELEASED' | 'EXPIRED': Reservation lifecycle state (v1+ may add additional terminal states).
- `tenant` string
- `workspace` string
- `app` string
- `workflow` string
- `agent` string
- `toolset` string
- `from` string, date-time
- `to` string, date-time
- `expires_from` string, date-time
- `expires_to` string, date-time
- `finalized_from` string, date-time
- `finalized_to` string, date-time
- `sort_by` 'reservation_id' | 'tenant' | 'scope_path' | 'status' | 'reserved' | 'created_at_ms' | 'expires_at_ms'
- `sort_dir` 'asc' | 'desc': Sort direction for list endpoints. Used with per-endpoint `sort_by` parameters (introduced in revision 2026-04-16 on listReservations). When `sort_by` is provided but `sort_dir` is omitted, servers MUST default to "desc". Servers that do not recognize the parameter MUST ignore it without error (additive-parameter guarantee). This schema is carried inline in each spec of the family (protocol + governance); the companion-specs publication model does not use cross-spec $ref.
- `include` string[]
- `limit` integer
- `cursor` string

## Response `200`

Reservations list

- object
  - `reservations` object[], required
    - `reservation_id` string, required
    - `status` 'ACTIVE' | 'COMMITTED' | 'RELEASED' | 'EXPIRED', required: Reservation lifecycle state (v1+ may add additional terminal states).
    - `idempotency_key` string
    - `subject` union, required: Dimension bag for hierarchical budgets. At least one standard field (tenant, workspace, app, workflow, agent, or toolset) MUST be provided. A subject containing only `dimensions` is invalid; server MUST return 400 INVALID_REQUEST. Hierarchy: tenant → workspace → app → workflow → agent → toolset. CHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset) SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope paths use ":" and "/" as structural delimiters, so delimiter, whitespace, and control characters have no stable canonical encoding. - Servers MAY reject standard-field values outside this pattern with 400 INVALID_REQUEST (the reference implementation does). Clients MUST NOT rely on out-of-pattern values being accepted, and portable clients SHOULD restrict themselves to the pattern above. EXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies (e.g., cost_center/department/project) and policy/reporting. - v0 servers MAY ignore dimensions for budgeting decisions; if they do, they still MUST accept and round-trip it. - Keys SHOULD be lowercase and match ^[a-z0-9_.-]+$ for stable canonicalization; values are opaque strings.
      - object
        - `tenant` string, required
        - `workspace` string
        - `app` string
        - `workflow` string
        - `agent` string
        - `toolset` string
        - `dimensions` object: Optional custom dimensions for enterprise taxonomies and policy/reporting.
      - object
        - `tenant` string
        - `workspace` string, required
        - `app` string
        - `workflow` string
        - `agent` string
        - `toolset` string
        - `dimensions` object: Optional custom dimensions for enterprise taxonomies and policy/reporting.
      - object
        - `tenant` string
        - `workspace` string
        - `app` string, required
        - `workflow` string
        - `agent` string
        - `toolset` string
        - `dimensions` object: Optional custom dimensions for enterprise taxonomies and policy/reporting.
      - object
        - `tenant` string
        - `workspace` string
        - `app` string
        - `workflow` string, required
        - `agent` string
        - `toolset` string
        - `dimensions` object: Optional custom dimensions for enterprise taxonomies and policy/reporting.
      - object
        - `tenant` string
        - `workspace` string
        - `app` string
        - `workflow` string
        - `agent` string, required
        - `toolset` string
        - `dimensions` object: Optional custom dimensions for enterprise taxonomies and policy/reporting.
      - object
        - `tenant` string
        - `workspace` string
        - `app` string
        - `workflow` string
        - `agent` string
        - `toolset` string, required
        - `dimensions` object: Optional custom dimensions for enterprise taxonomies and policy/reporting.
    - `action` object, required
      - `kind` string, required: Action type identifier. Recommended format: <category>.<operation> Examples: llm.completion, llm.embedding, tool.search, tool.calculator, db.query, db.write, http.get, http.post, file.upload, message.email, webhook.outbound
      - `name` string, required: Provider/model/tool identifier (e.g., "openai:gpt-4o-mini", "web.search")
      - `tags` string[]: Optional policy tags (e.g., ["prod","customer-facing"])
    - `reserved` 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
    - `created_at_ms` integer, required
    - `expires_at_ms` integer, required
    - `finalized_at_ms` integer: Wall-clock time at which the reservation reached a terminal state. Populated on COMMITTED and RELEASED rows only; absent on ACTIVE and EXPIRED rows. OPTIONAL per the `additionalProperties: false` invariant — older summaries sent without this field remain valid. Mirrors the same field on `ReservationDetail`. Added in revision 2026-05-22 to support the `finalized_from` / `finalized_to` window filter on `listReservations`.
    - `scope_path` string, required
    - `affected_scopes` string[], required
    - `committed` 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
    - `metadata` object: RESERVE-time metadata (see `ReservationDetail.metadata`). On `listReservations` responses this is OMITTED BY DEFAULT and populated ONLY when the caller opts in with `include=metadata` — the map is arbitrary-size and may carry application PII, so it is not projected onto every list row unconditionally. Always present (when the reservation carried reserve-time metadata) on the single-row `ReservationDetail`. OPTIONAL; absent when not requested or when there was no reserve-time metadata. Added in revision 2026-06-19.
    - `committed_metadata` object: COMMIT-time metadata (see `ReservationDetail.committed_metadata`). On `listReservations` responses this is OMITTED BY DEFAULT and populated ONLY when the caller opts in with `include=committed_metadata` — same payload-size / PII rationale as `metadata`. Always present (on COMMITTED rows whose commit carried metadata) on the single-row `ReservationDetail`. OPTIONAL; absent when not requested, on non-COMMITTED rows, or when the commit carried no metadata. Added in revision 2026-06-19.
    - `evidence` object: Map of artifact type to the CyclesEvidence reference emitted for that operation on a reservation. Lets a consumer jump from a reservation straight to its signed envelope(s) via `getEvidence` without having captured the `evidence_id` off the original reserve / commit / release response. Keys are the reservation-applicable artifact types: a reservation has at most a `reserve` entry plus one terminal entry (`commit` XOR `release`). TRANSPORT METADATA, NOT ATTESTED (see `CyclesEvidenceRef`) — each entry is recorded after its artifact's `evidence_id` was computed and is not part of any attested payload.
      - `reserve` 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`.
      - `commit` 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`.
      - `release` 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`.
  - `next_cursor` string: Opaque cursor for next page (if any).
  - `has_more` boolean: True if next_cursor is present and more results may be available.

## Other responses

- `400`: Error response
- `401`: Error response
- `403`: 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)
