Reservation Recovery and Listing in Cycles
In production systems, things go wrong.
A client crashes after creating a reservation but before storing the reservation ID. A network partition delays a commit. An operator needs to find stuck reservations that are holding budget.
The Cycles protocol provides two endpoints for these situations:
GET /v1/reservations— list and filter reservationsGET /v1/reservations/{reservation_id}— get details for a specific reservation
Both are optional in v0 deployments, but they are essential for production operations.
Recovering a lost reservation ID
The most common recovery scenario: a client created a reservation, received the response, but crashed before persisting the reservation_id.
The reservation exists on the server. Budget is held. But the client has no way to commit or release it.
The solution is to query by idempotency key:
GET /v1/reservations?idempotency_key=my-unique-key-123Since idempotency keys are unique per (effective tenant, endpoint, idempotency_key), this query returns at most one matching reservation.
The client recovers the reservation_id and can then commit or release as needed.
This is why idempotency keys should be generated and persisted before creating the reservation — they serve as the recovery handle.
Listing reservations
The listing endpoint supports several filters:
By status
GET /v1/reservations?status=ACTIVEReservation statuses are:
- ACTIVE — the reservation is live and budget is held
- COMMITTED — actual usage has been recorded
- RELEASED — the reservation was canceled and budget returned
- EXPIRED — the TTL (plus grace period) elapsed without commit or release
Filtering by status=ACTIVE is the most operationally useful — it shows all reservations currently holding budget.
By subject fields
GET /v1/reservations?tenant=acme&app=support-bot
GET /v1/reservations?workflow=refund-assistantSubject filters match against the canonical Subject fields: tenant, workspace, app, workflow, agent, and toolset.
Filtering on custom dimensions is out of scope for v0.
By idempotency key
GET /v1/reservations?idempotency_key=run-abc-step-3Returns at most one reservation matching the key. This is the primary recovery mechanism.
Pagination
Responses are paginated:
limit— maximum results per page (1–200, default 50)cursor— opaque cursor from a previous responsehas_more— whether more results existnext_cursor— cursor for the next page
Getting reservation details
GET /v1/reservations/{reservation_id}Returns the full state of a specific reservation:
- reservation_id — the unique identifier
- status — current lifecycle state (ACTIVE, COMMITTED, RELEASED, EXPIRED)
- subject — the original Subject (tenant, workspace, app, etc.)
- action — the original Action (kind, name, tags)
- reserved — the amount that was reserved
- committed — the amount that was committed (if status is COMMITTED)
- created_at_ms — when the reservation was created
- expires_at_ms — when the reservation expires (or expired)
- finalized_at_ms — when the reservation was committed, released, or expired
- scope_path — the canonical scope path
- affected_scopes — all scopes impacted by this reservation
- idempotency_key — the creation idempotency key (if the server persists it)
- metadata — any metadata attached to the reservation
This endpoint is useful for debugging specific reservations and understanding their full lifecycle.
Tenancy enforcement
Both listing and detail endpoints enforce tenant isolation:
- results are scoped to the effective tenant derived from the API key
- if a
tenantquery parameter is provided, it must match the effective tenant - attempting to get details for a reservation owned by a different tenant returns
403 FORBIDDEN
A tenant cannot see or access another tenant's reservations.
Use cases
Stuck reservation detection
Periodically query GET /v1/reservations?status=ACTIVE and check for reservations with expires_at_ms in the past that have not yet been finalized.
These may indicate server-side cleanup delays or edge cases worth investigating.
Budget leak investigation
When budget appears lower than expected, list active reservations to see what is currently held:
GET /v1/reservations?status=ACTIVE&tenant=acme&app=support-botThis shows all live reservations holding budget for that app. High counts or large reserved amounts may explain the discrepancy.
Post-incident analysis
After a budget incident, query reservations by workflow or agent to understand the pattern:
GET /v1/reservations?workflow=refund-assistant&status=COMMITTEDThis shows all committed reservations for that workflow, which helps trace what consumed the budget.
Client crash recovery
When a client restarts after a crash:
- Check local state for any in-progress reservation IDs
- For any missing IDs, query by the idempotency key that was generated before the reservation
- For each recovered reservation, check its status:
- ACTIVE: commit or release depending on whether work completed
- EXPIRED: the budget was already returned; create a new reservation if work needs to continue
- COMMITTED or RELEASED: no action needed
This recovery pattern depends on the client generating and persisting idempotency keys before creating reservations.
Error conditions
GET /v1/reservations (listing)
400 INVALID_REQUEST— malformed query parameters401 UNAUTHORIZED— invalid API key403 FORBIDDEN— tenant mismatch
GET /v1/reservations/{reservation_id} (detail)
401 UNAUTHORIZED— invalid API key403 FORBIDDEN— reservation owned by a different tenant404 NOT_FOUND— reservation never existed410 RESERVATION_EXPIRED— reservation existed but is expired (some servers may return this)
Summary
The reservation listing and detail endpoints provide operational visibility and recovery capabilities:
- Recovery: query by idempotency key to recover lost reservation IDs
- Monitoring: list active reservations to understand what is holding budget
- Debugging: get full reservation details including status, amounts, scopes, and timestamps
- Investigation: filter by subject fields and status for post-incident analysis
These endpoints are optional in v0, but essential for production operations where client crashes, network issues, and budget investigations are a reality.
Next steps
To explore the Cycles stack:
- Read the Cycles Protocol
- Run the Cycles Server
- Manage budgets with Cycles Admin
- Integrate with Python using the Python Client
- Integrate with TypeScript using the TypeScript Client
- Integrate with Spring AI using the Spring Client
