{
  "openapi": "3.1.0",
  "info": {
    "title": "Cycles Budget Authority API",
    "version": "0.1.25.16",
    "license": {
      "name": "Apache 2.0",
      "url": "https://www.apache.org/licenses/LICENSE-2.0"
    },
    "summary": "v0 protocol for deterministic budget governance (reserve/commit) with optional decide + balance queries, plus debt/overdraft support with soft-limit reconciliation.",
    "x-changelog": {
      "url": "./changelogs/cycles-protocol-v0.md",
      "format": "keep-a-changelog"
    },
    "description": "PURPOSE (v0):\n  - Provide a minimal, language-agnostic protocol to enforce deterministic spend exposure for agent runtimes\n    via concurrency-safe reservations and idempotent commits.\n  - Include optional integration endpoints: /decide (soft landing) and /balances (operator visibility).\n\nNON-GOALS (v0) (NORMATIVE):\n  - Budget establishment and funding operations are out of scope for v0.\n    v0 defines the reservation/commit/release enforcement plane and balance reporting only.\n  - v0 provides no API for budget CRUD (create/update/delete), allocation setting, credit/deposit, or debit/withdrawal.\n    Implementations MAY provide these via an operator/admin plane or a separate API; future versions may standardize them.\n  - A reservation lifecycle is denominated in exactly one unit (single-unit reserve/commit/release).\n    Multi-unit atomic reservation/settlement is a v1+ concern.\n\nPAYMENT-RAIL TERMINOLOGY BOUNDARY (NORMATIVE):\n  - A Cycles reservation holds spending authority in a budget ledger; it does not hold funds\n    on a card, bank, blockchain, or other payment rail.\n  - A Cycles commit finalizes recorded economic exposure; it does not authorize, capture,\n    charge, or settle a payment on a payment rail.\n  - A Cycles release returns unused budget authority; it does not void or refund a payment.\n  - Implementations that move money MUST execute and reconcile payment-rail operations separately\n    and MUST NOT represent a Cycles lifecycle transition as proof that funds moved or were reversed.\n\nAUTH & TENANCY (NORMATIVE):\n  - Requests are authenticated via X-Cycles-API-Key, EXCEPT endpoints that\n    explicitly declare `security: []`, which are PUBLIC and require no API key.\n    Two such endpoints exist, both in the CyclesEvidence surface:\n    `GET /v1/evidence/{evidence_id}` (envelope retrieval — its `evidence_id`\n    is an unguessable content-hash capability and the envelope is\n    content-addressed and signed) and `GET /v1/.well-known/cycles-jwks.json`\n    (the signer's public JWK Set — public keys only, the standard posture for\n    a verification key set). See each operation's description for the rationale.\n  - Server determines an \"effective tenant\" from the API key (or other auth context).\n  - Subject.tenant is a budgeting dimension and MUST be validated against the effective tenant.\n    If mismatched, server MUST return 403 FORBIDDEN.\n  - Reservation ownership MUST be enforced: every reservation is bound to the effective tenant at creation.\n    Any subsequent GET/commit/release for a reservation that exists but is owned by a different tenant\n    MUST return 403 FORBIDDEN.\n  - Balance visibility MUST be tenant-scoped: the server MUST only return balances within the effective tenant.\n    If a request attempts to query another tenant (e.g., tenant filter mismatches), server MUST return 403 FORBIDDEN.\n\nEVOLUTION CONTRACT:\n  - This API starts at v0.1.0 with /v1 paths to avoid future client churn.\n  - v1+ evolution MUST be backward-compatible by default: new fields are additive, existing field meanings MUST NOT change.\n  - Breaking changes (e.g., new required fields, semantic changes) require a new major API path (e.g., /v2).\n\nCORE INVARIANTS:\n  - Reserve is atomic across all derived scopes.\n  - Commit and release are idempotent.\n  - No double-charge on retries (idempotency key enforced).\n\nERROR SEMANTICS (NORMATIVE):\n  - Budget denials MUST return HTTP 409 with error=BUDGET_EXCEEDED.\n  - Overdraft limit exceeded MUST return HTTP 409 with error=OVERDRAFT_LIMIT_EXCEEDED in two cases:\n    1. During commit: when overage_policy=ALLOW_WITH_OVERDRAFT and (current_debt + delta) > overdraft_limit at commit time\n    2. During reservation: when the scope is in over-limit state (debt > overdraft_limit due to prior concurrent commits)\n  - Outstanding debt blocking reservation MUST return HTTP 409 with error=DEBT_OUTSTANDING\n    (when debt > 0 and new reservation is attempted).\n  - Closed owning tenant MUST return HTTP 409 with error=TENANT_CLOSED on the persisting\n    mutation surface — reservation create (POST /v1/reservations with dry_run\n    absent or false), commit, release, extend, AND POST /v1/events (createEvent) — when the\n    owning tenant's status is CLOSED and the CLOSED flip is durable\n    (added to the ErrorCode enum in revision 2026-07-10, mirroring the governance spec's\n    code of the same name). POST /v1/events is a persisting BUDGET DEBIT (post-only\n    accounting that directly mutates remaining/spent on every budgeted derived scope and\n    MAY accrue overdraft debt under overage_policy=ALLOW_WITH_OVERDRAFT, with concurrency\n    semantics as for commit) — it is on the same runtime-plane persisting surface as the\n    reservation mutations and MUST be guarded identically. This is the runtime-plane half\n    of the terminal-owner mutation guard in cycles-governance-admin-v0.1.25.yaml (CASCADE\n    SEMANTICS Rule 2); that section enumerates \"any reservation create/commit/release/extend\"\n    on the runtime plane, and createEvent is the remaining runtime-plane persisting budget\n    mutation subject to the same Mode B invariant, so the runtime plane guards it on the\n    same basis. Rationale: the close\n    cascade revokes the tenant's API keys, so a closed tenant usually surfaces on this\n    plane as 401 UNAUTHORIZED — but Mode B invariant (a) of that cascade requires that a\n    mutation observed AFTER the CLOSED flip MUST NOT succeed even in the window before\n    keys are revoked; this binding closes that race on the runtime plane.\n    POST /v1/events has no dry_run or decide mode — it always persists — so on a FRESH\n    (non-replay) request the closed-tenant outcome there is the 409 TENANT_CLOSED of the\n    persisting surface; it never returns a decision=DENY. Everything below that the\n    persisting surface inherits — fail-closed on a malformed/undeterminable tenant record\n    (500 INTERNAL_ERROR), the not-applicable case when no governance plane exists, and the\n    idempotent same-key replay exception (a same-key replay of a pre-close event returns the\n    original stored 201 response, taking precedence over the guard exactly as on the\n    reservation surface) — applies to createEvent unchanged.\n    Non-persisting evaluations: POST /v1/reservations with dry_run=true and POST /v1/decide\n    MUST NOT produce HTTP 409 TENANT_CLOSED for a closed owning tenant. A fresh (non-replay)\n    evaluation MUST instead reflect the closed tenant as-if-live: decision=DENY with\n    reason_code=TENANT_CLOSED. (Same-key replays of pre-close evaluations return the\n    original stored response per the IDEMPOTENCY section — replay precedence applies here\n    exactly as on the persisting surface.) Rationale: dry-run and /decide outcomes are attestations of\n    what live execution would do (and MAY be captured as signed evidence per\n    cycles-evidence-v0.2.yaml); an evaluation that ignores a durable CLOSED flip would\n    attest ALLOW for a request whose live execution MUST fail.\n    Guard evaluation (both surfaces): a tenant record with status CLOSED triggers the\n    guard (409 on the persisting surface, decision=DENY on the non-persisting surface); a\n    tenant record that exists but whose status cannot be determined (malformed or corrupt\n    record) MUST fail closed with HTTP 500 INTERNAL_ERROR — on the non-persisting surface\n    too, because the server cannot attest against corrupt governance state; a subject\n    tenant with no tenant record is not guarded (there is no status to observe).\n    Precedence: for non-replay mutations on a closed tenant's reservations,\n    TENANT_CLOSED takes precedence over the reservation-state errors\n    (RESERVATION_FINALIZED, RESERVATION_EXPIRED) — Rule 2 rejects \"regardless of\n    that child's own current status\". Idempotent replays are the exception: a\n    same-key replay of a mutation that succeeded BEFORE the close retains replay\n    precedence and MUST return the original stored response payload per the\n    IDEMPOTENCY section (consistent with Rule 2's invariant (b) — the cascade is\n    idempotent and does not rewrite already-finalized outcomes).\n    Cross-plane applicability: a deployment that operates a governance plane (tenant\n    records exist) MUST enforce this guard — either by making the owning tenant's\n    CLOSED status observable to the runtime plane, or by enforcing an equivalent\n    post-flip mutation guard at a central enforcement point in front of these\n    operations. The requirement is behavioral (the 409 TENANT_CLOSED rejection),\n    not architectural: choosing not to wire tenant status through to the runtime\n    plane does NOT exempt a deployment. Only deployments with NO governance plane\n    at all (no tenant records exist anywhere in the deployment) have no tenant\n    status to enforce — the rule is not applicable to them.\n    Non-mutating reservation reads (GET /v1/reservations, GET /v1/reservations/{id})\n    MUST NOT be rejected with TENANT_CLOSED: they remain available (subject to normal\n    auth) on reservations of a CLOSED tenant for post-close audit, mirroring Rule 2's\n    read-access rule.\n  - Finalized reservations MUST return HTTP 409 with error=RESERVATION_FINALIZED.\n  - Expired reservations MUST return HTTP 410 with error=RESERVATION_EXPIRED.\n    (commit/release: beyond expires_at_ms + grace_period_ms; extend: beyond expires_at_ms;\n    getReservation: any reservation whose status is EXPIRED — see that operation's\n    EXPIRY note. Clarified in revision 2026-07-03; previously the parenthetical\n    enumerated only the mutation endpoints, leaving the GET case ambiguous.)\n  - Reservations that never existed MUST return HTTP 404 with error=NOT_FOUND.\n  - HTTP 429 is reserved for server-side throttling/rate limiting (optional in v0), not deterministic budget exhaustion.\n    429 responses carry error=LIMIT_EXCEEDED (added to the ErrorCode enum in revision 2026-07-04, mirroring the\n    governance spec's code of the same name) plus the Retry-After and X-RateLimit-Reset headers.\n  - Unit mismatch MUST return HTTP 400 with error=UNIT_MISMATCH in any of these cases:\n      (a) reserve — estimate.unit does not match any budget stored for the derived scopes,\n          but at least one of those scopes has a budget in a different unit;\n      (b) commit — actual.unit differs from the reservation's estimate.unit;\n      (c) event — actual.unit does not match the budget stored for the target scope;\n      (d) decide — estimate.unit does not match any budget stored for the derived scopes,\n          but at least one of those scopes has a budget in a different unit. This is an\n          exception to /decide's general \"return decision=DENY (200) without 4xx\" pattern,\n          which applies only to budget-state conditions (debt, overdraft, insufficient\n          remaining), not request-validity errors like a wrong unit.\n    When the cause is a wrong unit (rather than the absence of any budget at the scope),\n    servers SHOULD populate the error response's `details` object with:\n      - `scope` — the canonical scope identifier where the mismatch was detected\n      - `requested_unit` — the unit supplied by the client\n      - `expected_units` — array of units for which a budget does exist at that scope\n    so clients can self-correct without a separate lookup. HTTP 404 with error=NOT_FOUND\n    is reserved for the case where the target scope has no budget in ANY unit (the\n    runtime plane uses the single NOT_FOUND code for all resource-not-found conditions;\n    the message field carries the specific reason, e.g. \"Budget not found for provided\n    scope: ...\").\n  - For expiry comparisons, “now” refers to server time (not client-provided time).\n  - When is_over_limit=true, server MUST return 409 OVERDRAFT_LIMIT_EXCEEDED for new reservations. \n    This takes precedence over DEBT_OUTSTANDING even when debt > 0.\n\nOVERDRAFT RECONCILIATION (NORMATIVE):\n  - When concurrent commits cause debt > overdraft_limit on a scope, the server MUST mark that scope as \"over-limit\" (is_over_limit=true).\n  - Over-limit scopes MUST reject ALL new reservation attempts with 409 OVERDRAFT_LIMIT_EXCEEDED until debt is reduced below overdraft_limit.\n  - Operators reconcile over-limit scopes via budget funding operations (out-of-scope for this API). \n    When debt is repaid below overdraft_limit, is_over_limit automatically returns to false.\n  - Servers SHOULD provide monitoring/alerting when scopes enter over-limit state:\n      * Log events with scope identifier, current debt, and overdraft_limit\n      * Optionally emit webhooks or notifications to operators\n      * Optionally expose metrics endpoint showing over-limit scope count\n  - Clients SHOULD handle 409 OVERDRAFT_LIMIT_EXCEEDED on reservation as a signal to wait/retry with exponential backoff, or escalate to operators.\n\nIDEMPOTENCY (NORMATIVE):\n  - If X-Idempotency-Key header is present and body.idempotency_key is present, they MUST match.\n  - Server MUST enforce idempotency per (effective tenant, endpoint, idempotency_key).\n  - On replay of an idempotent request that previously succeeded, server MUST return the original successful\n    response outcome and payload (including any server-generated identifiers such as reservation_id), except\n    for volatile response observations defined below.\n  - remaining_ttl_ms on a replayed createReservation or extendReservation success is a volatile response\n    observation, not part of the attested CyclesEvidence payload. When emitted, the server MUST recompute it\n    while constructing the replay response as\n    max(0, original expires_at_ms - current authoritative server time); it MUST NOT replay the originally\n    stored remaining_ttl_ms value. It MUST be 0 if the reservation is no longer ACTIVE and MAY\n    conservatively understate current lead if a later, separately keyed extension moved expiry outward.\n    All other fields replay verbatim as the original outcome.\n  - If the same key is reused with a different request payload, server MUST return 409 IDEMPOTENCY_MISMATCH.\n  - Servers SHOULD compare idempotency payloads using a canonical JSON representation\n    (e.g., RFC 8785 JSON Canonicalization Scheme) or an equivalent stable serialization.\n\nSCOPE DERIVATION (NORMATIVE):\n  - Server derives canonical scope identifiers and a canonical scope_path from Subject fields.\n  - Canonical ordering is: tenant → workspace → app → workflow → agent → toolset.\n  - Only explicitly provided subject levels are included in scope paths; intermediate gaps are skipped (not filled with \"default\").\n  - Scopes without budgets are skipped during enforcement; at least one derived scope MUST have a budget.\n  - affected_scopes returned by the server MUST be in that canonical order.\n\nRESERVATION LEASING (GUIDANCE):\n  - To mitigate \"zombie reservations\" (client crash after reserve), SDKs SHOULD:\n    * keep ttl_ms short (typically 10s–30s),\n    * include modest estimation buffers when using overage_policy=REJECT,\n    * reserve in small initial leases and increase gradually (\"slow start\") for long or bursty operations,\n    * prefer chunked reserve/commit cycles for long-running actions rather than a single large reservation.\n\nOVERDRAFT MONITORING (GUIDANCE):\n  - Implementations SHOULD provide visibility into over-limit states:\n      * Dashboard showing scopes with is_over_limit=true\n      * Alerts when debt exceeds overdraft_limit\n      * Time-series metrics: debt_utilization = debt / overdraft_limit\n  - Recommended alerting thresholds:\n      * Warning at 80% of overdraft_limit\n      * Critical at 100% (over-limit state)\n  - Recommended operator runbook:\n      1. Investigate which reservations caused the over-limit state\n      2. Determine if overdraft_limit should be increased (normal variance) or if this represents anomalous consumption (incident)\n      3. Fund the scope to repay debt below limit\n      4. Monitor that is_over_limit returns to false\n      5. Resume operations automatically\n\nCORRELATION AND TRACING (NORMATIVE, cross-plane):\n  This section defines the cross-surface correlation contract for the entire Cycles\n  spec family. It is normative for every Cycles server operation on every plane\n  (runtime, governance-admin, action-kinds, and any extension that layers onto\n  these bases). Companion specs SHOULD carry a brief pointer to this section and\n  MUST NOT restate a conflicting contract.\n\n  Three-tier correlation model:\n    * request_id — one HTTP request grain. Set by the server. Echoed on\n      X-Request-Id response header, on ErrorResponse, and on every event / audit\n      entry that is causally downstream of the request (including entries emitted\n      from queued or deferred work spawned by the request).\n    * trace_id — logical-operation grain. W3C Trace Context-compatible. Accepted\n      from inbound headers or generated by the server. Echoed on X-Cycles-Trace-Id\n      response header and propagated to events, audit entries, and outbound\n      webhook deliveries.\n    * correlation_id — event-stream cluster grain. Set by the server as a\n      deterministic hash over (tenant_id, scope, action_kind_or_risk_class,\n      window, window_key) to JOIN threshold-alert → trip → reset chains and\n      observed_denied ↔ reservation.denied pairs. Scoped to the event stream only.\n\n  Inbound header precedence (server extracts trace_id by the first matching rule):\n    1. `traceparent` header, if present AND parses as a valid W3C Trace Context\n       value (version 00, non-all-zero trace-id, non-all-zero span-id) → use its\n       trace-id (the leftmost 32-hex segment).\n    2. Else `X-Cycles-Trace-Id` header, if present AND matches `^[0-9a-f]{32}$`\n       AND is not all-zero → use its value directly.\n    3. Else server generates a new trace_id: 16 random bytes encoded as 32\n       lowercase hex characters. The all-zero value is invalid per W3C Trace\n       Context §3.2.2.3 and MUST be re-rolled.\n\n  Header validation and precedence rules:\n    * A malformed `traceparent` OR malformed `X-Cycles-Trace-Id` MUST be treated\n      as absent for that header; the server falls through to the next rule. The\n      server MUST NOT reject a request for a malformed correlation header.\n    * If both `traceparent` and `X-Cycles-Trace-Id` are present, both valid, but\n      their trace-ids DISAGREE, `traceparent` wins (OpenTelemetry interop takes\n      precedence over the flat convenience header). The server MAY log this\n      condition for diagnostics but MUST NOT reject the request.\n\n  Outbound response contract (every plane, every response):\n    * Servers MUST echo `X-Cycles-Trace-Id` on every response (2xx, 4xx, 5xx).\n      The header is declared as `X-Cycles-Trace-Id` in this document's\n      `components.headers` and re-declared in the companion spec's own\n      `components.headers` for OpenAPI tooling conformance.\n    * ErrorResponse bodies MUST carry `trace_id` on every conformant error.\n    * Events and audit-log entries causally downstream of the request MUST\n      carry `trace_id`. See each companion spec's Event / AuditLogEntry schema.\n\n  Propagation contract:\n    * The server propagates trace_id onto: the audit-log entry for the request\n      (one per authenticated request that hits the governance plane), every\n      event emitted as a side effect of the request (runtime or governance),\n      and every outbound webhook delivery. Propagation across thread, queue, or\n      process boundaries is REQUIRED; loss at the request-thread boundary is\n      non-compliant.\n    * Outbound webhook deliveries carry `X-Cycles-Trace-Id` AND `traceparent`\n      headers constructed as documented in the WEBHOOK EVENT GUIDANCE section\n      below (including the trace-flags preservation rule).\n\n  Format: `^[0-9a-f]{32}$` — 32 lowercase hex characters (128-bit trace ID).\n\n  Backward compatibility:\n    * `trace_id` is declared as an OPTIONAL property on ErrorResponse, Event,\n      and AuditLogEntry schemas (no wire-contract break). Servers conformant\n      with this section MUST populate it; clients MUST tolerate its absence on\n      entries emitted by older servers.\n    * Adding `X-Cycles-Trace-Id` as a response header is additive; clients that\n      do not read the header are unaffected.\n    * Accepting `traceparent` / `X-Cycles-Trace-Id` as inbound request headers\n      is additive; clients that do not send them are unaffected.\n\nWEBHOOK EVENT GUIDANCE (GUIDANCE):\n  Implementations MAY emit webhook events when runtime operations produce observable state changes.\n  This enables operators and tenant applications to react to budget state transitions in real-time\n  without polling. The webhook delivery system is separate from the protocol endpoints — it does not\n  add new API paths to the runtime server.\n\n  Event types emitted by the runtime server:\n    * reservation.denied — Reserve or decide returned DENY (budget exceeded, overdraft limit, frozen, etc.)\n    * reservation.commit_overage — Commit actual amount exceeded estimated amount\n    * reservation.expired — Reservation TTL expired without commit or release (via background sweeper)\n    * budget.exhausted — Remaining budget reached 0 after a reservation or event\n    * budget.debt_incurred — Commit created new debt via ALLOW_WITH_OVERDRAFT policy\n    * budget.over_limit_entered — is_over_limit flipped to true (debt > overdraft_limit)\n    * budget.over_limit_exited — Debt repaid below overdraft_limit (via admin funding operations)\n    * budget.threshold_crossed — Utilization crossed a configured threshold (e.g., 80%, 95%)\n    * budget.burn_rate_anomaly — Spend rate exceeded baseline by configured multiplier\n\n  Event types emitted by the admin/operator server:\n    * tenant.created/updated/suspended/reactivated/closed — Tenant lifecycle\n    * budget.created/updated/funded/debited/reset/debt_repaid/frozen/unfrozen/closed — Budget lifecycle\n    * api_key.created/revoked/expired/permissions_changed/auth_failed — API key lifecycle\n    * policy.created/updated/deleted — Policy lifecycle\n    * system.store_connection_lost/restored, system.high_latency — System health\n    * system.webhook_delivery_failed — Meta-alert for persistent delivery failures\n\n  Standard event payload schema (JSON):\n    * event_id (string, required) — Globally unique (e.g., \"evt_01abc...\"). Use for deduplication.\n    * event_type (string, required) — Dotted format: \"{category}.{action}\" (e.g., \"reservation.denied\")\n    * category (string, required) — One of: budget, reservation, tenant, api_key, policy, system\n    * timestamp (string, date-time, required) — ISO 8601 UTC\n    * tenant_id (string, required) — Tenant context. System events use \"__system__\".\n    * scope (string, optional) — Full scope path affected (e.g., \"tenant:acme/agent:bot\")\n    * actor (object, optional) — Who caused the event:\n      { type: admin|api_key|admin_on_behalf_of|system|scheduler, key_id?, source_ip? }\n      (admin_on_behalf_of: admin key exercising a tenant-scoped dual-auth\n      operation; added in revision 2026-07-04, mirroring the governance\n      spec's Event.actor.type enum)\n    * source (string, required) — Service that emitted: \"cycles-server\", \"cycles-admin\", \"expiry-sweeper\"\n    * data (object, optional) — Event-specific payload (varies by event_type)\n    * correlation_id (string, optional) — Links related events for chain reconstruction\n    * request_id (string, optional) — X-Request-Id from the originating HTTP request.\n      MUST be populated on every event causally downstream of an HTTP request,\n      including events emitted from queued, deferred, or otherwise-async work spawned\n      by that request. MAY be absent on internal sweeper/expiry-generated events that\n      have no originating HTTP request. See CORRELATION AND TRACING section below.\n    * trace_id (string, optional, pattern ^[0-9a-f]{32}$) — W3C Trace Context trace-id\n      for the logical operation. Populated on every event produced by a server that\n      conforms to the CORRELATION AND TRACING contract below.\n    * metadata (object, optional) — Operator-defined key-value pairs\n\n  Webhook delivery protocol:\n    * Delivery method: HTTP POST to subscriber's URL with JSON event payload as body\n    * Delivery semantics: At-least-once. Consumers MUST deduplicate using event_id.\n    * Ordering: Events for the same tenant are INITIALLY DISPATCHED in\n      order; cross-tenant ordering NOT guaranteed. Clarified in revision\n      2026-07-04: the ordering guarantee applies to first delivery\n      attempts only. A failed delivery re-enters the queue after its\n      retry backoff, so retried deliveries MAY arrive after later events\n      for the same tenant — an unavoidable consequence of combining\n      per-delivery retry with non-blocking dispatch. Consumers MUST NOT\n      assume strict arrival ordering across retry boundaries; reconstruct\n      order from the event envelope's `timestamp` (and `correlation_id`\n      chains) rather than arrival order.\n    * Non-blocking: Webhook delivery MUST NOT block the operation that produced the event.\n\n  Required HTTP headers on webhook delivery:\n    * Content-Type: application/json\n    * X-Cycles-Event-Id: {event_id} — For deduplication\n    * X-Cycles-Event-Type: {event_type} — For routing\n    * X-Cycles-Signature: sha256={hex} — HMAC-SHA256 of raw request body using subscription's signing secret\n    * X-Cycles-Trace-Id: {trace_id} — W3C Trace Context trace-id (32-hex) for the\n      logical operation that produced this event. Always required; the server always\n      has a trace_id per the CORRELATION AND TRACING fallback-generate rule.\n    * traceparent: 00-{trace_id}-{fresh-span-id-16-hex}-{trace-flags} — W3C Trace\n      Context version 00 header. Always required. trace_id MUST equal the value in\n      X-Cycles-Trace-Id. span-id MUST be freshly generated for the outbound delivery\n      (NOT reused from inbound). trace-flags rules:\n        - If the inbound request to Cycles carried a valid `traceparent`, the server\n          MUST preserve the inbound trace-flags byte on the outbound `traceparent`\n          (so a `sampled=0` upstream is not silently flipped to `sampled=1`).\n        - If the trace was derived from `X-Cycles-Trace-Id` (no inbound W3C\n          `traceparent`) OR generated fresh by the server, the server uses a default\n          trace-flags value of `01` (sampled).\n      The `trace_id` field also appears in the event envelope body so subscribers\n      unfamiliar with W3C Trace Context can still correlate via the JSON payload.\n    * User-Agent: {service-name}/{version}\n    * Custom headers from subscription configuration (e.g., Authorization)\n\n  Signature verification (X-Cycles-Signature):\n    * Algorithm: HMAC-SHA256\n    * Input: Raw JSON request body (bytes, not parsed)\n    * Key: Subscription's signing_secret (UTF-8 encoded)\n    * Format: \"sha256=\" + lowercase hex encoding of HMAC digest\n    * Consumers SHOULD verify the signature before processing the event.\n    * Use constant-time comparison (e.g., hmac.compare_digest) to prevent timing attacks.\n\n  Retry and failure handling:\n    * On non-2xx response: exponential backoff retry (default: 5 retries, 1s/2s/4s/8s/16s, max 60s)\n    * After all retries exhausted: delivery marked FAILED, system.webhook_delivery_failed event emitted\n    * After N consecutive failures (default 10): subscription auto-disabled (status → DISABLED)\n    * Disabled subscriptions can be re-enabled via admin API (resets failure counter)\n\n  Retention:\n    * Event records: 90 days hot storage (recommended). TTL enforced via Redis EXPIRE.\n    * Delivery records: 14 days (operational debugging data).\n    * ZSET index entries: Trimmed hourly by background cleanup job.\n    * Stale deliveries: Deliveries older than 24h (configurable) are auto-failed on pickup\n      to prevent delivering ancient webhooks after prolonged service outage.\n\n  Extensibility:\n    * New event types MAY be added in future versions without a breaking change.\n    * Consumers MUST ignore unrecognized event types gracefully.\n    * Custom event types MUST use a \"custom.\" prefix (e.g., \"custom.billing.invoice_sent\")."
  },
  "servers": [
    {
      "url": "https://api.cycles.local",
      "description": "Replace with your implementation endpoint"
    }
  ],
  "tags": [
    {
      "name": "Decisions",
      "description": "Optional preflight checks (no reservation created)"
    },
    {
      "name": "Reservations",
      "description": "Reservation, commit, release, and (optional) get-by-id operations"
    },
    {
      "name": "Balances",
      "description": "Query balances for operator visibility"
    },
    {
      "name": "Events",
      "description": "Optional post-only accounting for non-estimable actions"
    },
    {
      "name": "Evidence",
      "description": "Public retrieval of signed CyclesEvidence envelopes by content id"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Cycles-API-Key"
      },
      "AdminKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Admin-API-Key"
      }
    },
    "headers": {
      "X-Request-Id": {
        "description": "Unique request identifier for debugging",
        "schema": {
          "type": "string"
        }
      },
      "X-Cycles-Trace-Id": {
        "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-f]{32}$"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Number of requests remaining in current window (optional in v0)",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Reset": {
        "description": "Unix timestamp (seconds) when rate limit resets (optional in v0)",
        "schema": {
          "type": "integer",
          "format": "int64"
        }
      },
      "Cache-Control": {
        "description": "Caching directive. For content-addressed responses (e.g. getEvidence) the server SHOULD return `public, immutable` — the body never changes.",
        "schema": {
          "type": "string"
        }
      },
      "Retry-After": {
        "description": "On 429, the non-negative delta-seconds a client SHOULD wait before retrying (optional in v0). This API intentionally supports only the delta-seconds form of HTTP Retry-After; the HTTP-date form is invalid for this contract because converting it to a safe monotonic delay would require wall-clock assumptions that lease scheduling forbids.",
        "schema": {
          "type": "integer",
          "format": "int64",
          "minimum": 0
        }
      },
      "X-Cycles-Tenant": {
        "description": "Effective tenant identifier derived from auth context (optional in v0)",
        "schema": {
          "type": "string"
        }
      },
      "Date": {
        "description": "Standard HTTP origination timestamp (RFC 9110 §6.6.1, IMF-fixdate per §5.6.7). Declared here — OPTIONAL — purely as documentation of standard HTTP behavior on the reservation responses. It plays NO role in the HEARTBEAT GUIDANCE on extendReservation and MUST NOT be used for lease arithmetic or heartbeat scheduling: Date has whole-second resolution, is a best-effort timestamp that may be generated at any point during message origination or replaced by intermediaries, and a server may derive `expires_at_ms` and Date from DIFFERENT clocks (e.g. a data store's clock vs the HTTP container's), so `expires_at_ms − Date` is not a same-frame measurement.",
        "schema": {
          "type": "string"
        }
      }
    },
    "parameters": {
      "IdempotencyKeyHeader": {
        "name": "X-Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "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.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 256
        }
      },
      "ReservationId": {
        "name": "reservation_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        }
      },
      "EvidenceId": {
        "name": "evidence_id",
        "in": "path",
        "required": true,
        "description": "sha256 content hash of the CyclesEvidence envelope (64 lowercase hex chars).",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        },
        "description": "Maximum number of results to return"
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Opaque cursor from previous response"
      }
    },
    "responses": {
      "ErrorResponse": {
        "description": "Error response",
        "headers": {
          "X-Request-Id": {
            "description": "Unique request identifier for debugging",
            "schema": {
              "type": "string"
            }
          },
          "X-Cycles-Trace-Id": {
            "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{32}$"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error",
                "message",
                "request_id"
              ],
              "additionalProperties": false,
              "example": {
                "error": "BUDGET_EXCEEDED",
                "message": "Insufficient remaining budget for scope tenant:acme-corp",
                "request_id": "req_abc123def456",
                "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
              },
              "properties": {
                "error": {
                  "type": "string",
                  "enum": [
                    "INVALID_REQUEST",
                    "UNAUTHORIZED",
                    "FORBIDDEN",
                    "NOT_FOUND",
                    "BUDGET_EXCEEDED",
                    "BUDGET_FROZEN",
                    "BUDGET_CLOSED",
                    "RESERVATION_EXPIRED",
                    "RESERVATION_FINALIZED",
                    "IDEMPOTENCY_MISMATCH",
                    "UNIT_MISMATCH",
                    "OVERDRAFT_LIMIT_EXCEEDED",
                    "DEBT_OUTSTANDING",
                    "MAX_EXTENSIONS_EXCEEDED",
                    "LIMIT_EXCEEDED",
                    "TENANT_CLOSED",
                    "INTERNAL_ERROR"
                  ]
                },
                "message": {
                  "type": "string"
                },
                "request_id": {
                  "type": "string"
                },
                "trace_id": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$",
                  "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                },
                "details": {
                  "type": "object",
                  "additionalProperties": true
                },
                "cycles_evidence": {
                  "type": "object",
                  "additionalProperties": false,
                  "description": "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.\n\nTRANSPORT 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.",
                  "required": [
                    "evidence_id",
                    "cycles_evidence_url"
                  ],
                  "properties": {
                    "evidence_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{64}$",
                      "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                    },
                    "cycles_evidence_url": {
                      "type": "string",
                      "format": "uri",
                      "description": "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`."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "CyclesEvidenceEnvelope": {
        "type": "object",
        "additionalProperties": true,
        "description": "A signed CyclesEvidence envelope returned by `getEvidence`. The NORMATIVE definition — field semantics and the JCS content-hash + Ed25519 signature derivation — lives in `cycles-evidence-v0.2.yaml`; this schema mirrors its required shape for the serving endpoint. The envelope is content-addressed (`evidence_id` = sha256 of the JCS-canonical bytes with `evidence_id` and `signature` emptied) and self-verifying, so a consumer can check it offline without trusting this server.",
        "required": [
          "schema_version",
          "artifact_type",
          "server_id",
          "signer_did",
          "issued_at_ms",
          "payload",
          "evidence_id",
          "signature"
        ],
        "properties": {
          "schema_version": {
            "type": "string",
            "const": "cycles-evidence/v0.1"
          },
          "artifact_type": {
            "type": "string",
            "enum": [
              "decide",
              "reserve",
              "commit",
              "release",
              "error"
            ]
          },
          "server_id": {
            "type": "string",
            "description": "Stable URI of the issuing Cycles server."
          },
          "signer_did": {
            "type": "string",
            "description": "Ed25519 public key (v0.1 hex) the signature verifies against."
          },
          "issued_at_ms": {
            "type": "integer",
            "format": "int64"
          },
          "trace_id": {
            "type": "string"
          },
          "payload": {
            "type": "object",
            "description": "One key matching artifact_type (decide/reserve/commit/release/error)."
          },
          "evidence_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$"
          },
          "signature": {
            "type": "string",
            "pattern": "^[0-9a-f]{128}$"
          }
        }
      },
      "CyclesEvidenceJwks": {
        "type": "object",
        "additionalProperties": true,
        "description": "The issuing server's signer key set, returned by `getEvidenceJwks`. An RFC 7517 JWK Set (extra top-level members allowed and ignored). The NORMATIVE resolution algorithm — `did:cycles` binding, the validity-window key selection (pick the key whose `[cycles_nbf_ms, cycles_exp_ms)` covers the envelope's `issued_at_ms`, never \"the current key\"), the deterministic-selection rules, and the verification dispositions — lives in `cycles-evidence-v0.2.yaml`; this schema mirrors its required shape for the serving endpoint.",
        "required": [
          "keys"
        ],
        "properties": {
          "keys": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "description": "One Ed25519 signing key with a Cycles validity window. RFC 7517 / RFC 8037 (OKP/Ed25519) JWK plus the `cycles_*` members. A retired key MUST remain in the set so envelopes signed before a rotation still verify. See `cycles-evidence-v0.2.yaml` for full semantics.",
              "required": [
                "kty",
                "crv",
                "x",
                "kid",
                "cycles_nbf_ms"
              ],
              "properties": {
                "kty": {
                  "type": "string",
                  "const": "OKP"
                },
                "crv": {
                  "type": "string",
                  "const": "Ed25519"
                },
                "alg": {
                  "type": "string",
                  "const": "EdDSA",
                  "description": "Optional (RFC 7517 §4.4); if present MUST be `EdDSA`. A present-but-different `alg` makes the JWK invalid (excluded)."
                },
                "x": {
                  "type": "string",
                  "description": "base64url (unpadded) of the raw 32-byte Ed25519 public key. Equals `base64url(hex-decode(signer_did))` for a raw-hex `signer_did`."
                },
                "kid": {
                  "type": "string",
                  "description": "Stable key id; equals the `#<kid>` fragment of a `did:cycles` `signer_did`."
                },
                "cycles_nbf_ms": {
                  "type": "integer",
                  "format": "int64",
                  "description": "Valid-from, epoch ms, INCLUSIVE."
                },
                "cycles_exp_ms": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "format": "int64",
                  "description": "Valid-until, epoch ms, EXCLUSIVE. Absent/null ⇒ active (open-ended)."
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "active",
                    "retired"
                  ],
                  "description": "Advisory only. Selection is by validity WINDOW, never by `status`."
                }
              }
            }
          }
        }
      },
      "CyclesEvidenceJwk": {
        "type": "object",
        "additionalProperties": true,
        "description": "One Ed25519 signing key with a Cycles validity window. RFC 7517 / RFC 8037 (OKP/Ed25519) JWK plus the `cycles_*` members. A retired key MUST remain in the set so envelopes signed before a rotation still verify. See `cycles-evidence-v0.2.yaml` for full semantics.",
        "required": [
          "kty",
          "crv",
          "x",
          "kid",
          "cycles_nbf_ms"
        ],
        "properties": {
          "kty": {
            "type": "string",
            "const": "OKP"
          },
          "crv": {
            "type": "string",
            "const": "Ed25519"
          },
          "alg": {
            "type": "string",
            "const": "EdDSA",
            "description": "Optional (RFC 7517 §4.4); if present MUST be `EdDSA`. A present-but-different `alg` makes the JWK invalid (excluded)."
          },
          "x": {
            "type": "string",
            "description": "base64url (unpadded) of the raw 32-byte Ed25519 public key. Equals `base64url(hex-decode(signer_did))` for a raw-hex `signer_did`."
          },
          "kid": {
            "type": "string",
            "description": "Stable key id; equals the `#<kid>` fragment of a `did:cycles` `signer_did`."
          },
          "cycles_nbf_ms": {
            "type": "integer",
            "format": "int64",
            "description": "Valid-from, epoch ms, INCLUSIVE."
          },
          "cycles_exp_ms": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "description": "Valid-until, epoch ms, EXCLUSIVE. Absent/null ⇒ active (open-ended)."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "retired"
            ],
            "description": "Advisory only. Selection is by validity WINDOW, never by `status`."
          }
        }
      },
      "CyclesEvidenceRef": {
        "type": "object",
        "additionalProperties": false,
        "description": "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.\n\nTRANSPORT 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.",
        "required": [
          "evidence_id",
          "cycles_evidence_url"
        ],
        "properties": {
          "evidence_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$",
            "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
          },
          "cycles_evidence_url": {
            "type": "string",
            "format": "uri",
            "description": "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`."
          }
        }
      },
      "ReservationEvidence": {
        "type": "object",
        "additionalProperties": false,
        "description": "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.",
        "properties": {
          "reserve": {
            "type": "object",
            "additionalProperties": false,
            "description": "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.\n\nTRANSPORT 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.",
            "required": [
              "evidence_id",
              "cycles_evidence_url"
            ],
            "properties": {
              "evidence_id": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$",
                "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
              },
              "cycles_evidence_url": {
                "type": "string",
                "format": "uri",
                "description": "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": {
            "type": "object",
            "additionalProperties": false,
            "description": "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.\n\nTRANSPORT 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.",
            "required": [
              "evidence_id",
              "cycles_evidence_url"
            ],
            "properties": {
              "evidence_id": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$",
                "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
              },
              "cycles_evidence_url": {
                "type": "string",
                "format": "uri",
                "description": "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": {
            "type": "object",
            "additionalProperties": false,
            "description": "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.\n\nTRANSPORT 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.",
            "required": [
              "evidence_id",
              "cycles_evidence_url"
            ],
            "properties": {
              "evidence_id": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$",
                "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
              },
              "cycles_evidence_url": {
                "type": "string",
                "format": "uri",
                "description": "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`."
              }
            }
          }
        }
      },
      "IdempotencyKey": {
        "type": "string",
        "minLength": 1,
        "maxLength": 256
      },
      "ErrorCode": {
        "type": "string",
        "enum": [
          "INVALID_REQUEST",
          "UNAUTHORIZED",
          "FORBIDDEN",
          "NOT_FOUND",
          "BUDGET_EXCEEDED",
          "BUDGET_FROZEN",
          "BUDGET_CLOSED",
          "RESERVATION_EXPIRED",
          "RESERVATION_FINALIZED",
          "IDEMPOTENCY_MISMATCH",
          "UNIT_MISMATCH",
          "OVERDRAFT_LIMIT_EXCEEDED",
          "DEBT_OUTSTANDING",
          "MAX_EXTENSIONS_EXCEEDED",
          "LIMIT_EXCEEDED",
          "TENANT_CLOSED",
          "INTERNAL_ERROR"
        ]
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error",
          "message",
          "request_id"
        ],
        "additionalProperties": false,
        "example": {
          "error": "BUDGET_EXCEEDED",
          "message": "Insufficient remaining budget for scope tenant:acme-corp",
          "request_id": "req_abc123def456",
          "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
        },
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "INVALID_REQUEST",
              "UNAUTHORIZED",
              "FORBIDDEN",
              "NOT_FOUND",
              "BUDGET_EXCEEDED",
              "BUDGET_FROZEN",
              "BUDGET_CLOSED",
              "RESERVATION_EXPIRED",
              "RESERVATION_FINALIZED",
              "IDEMPOTENCY_MISMATCH",
              "UNIT_MISMATCH",
              "OVERDRAFT_LIMIT_EXCEEDED",
              "DEBT_OUTSTANDING",
              "MAX_EXTENSIONS_EXCEEDED",
              "LIMIT_EXCEEDED",
              "TENANT_CLOSED",
              "INTERNAL_ERROR"
            ]
          },
          "message": {
            "type": "string"
          },
          "request_id": {
            "type": "string"
          },
          "trace_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{32}$",
            "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
          },
          "details": {
            "type": "object",
            "additionalProperties": true
          },
          "cycles_evidence": {
            "type": "object",
            "additionalProperties": false,
            "description": "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.\n\nTRANSPORT 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.",
            "required": [
              "evidence_id",
              "cycles_evidence_url"
            ],
            "properties": {
              "evidence_id": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$",
                "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
              },
              "cycles_evidence_url": {
                "type": "string",
                "format": "uri",
                "description": "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`."
              }
            }
          }
        }
      },
      "DecisionEnum": {
        "type": "string",
        "enum": [
          "ALLOW",
          "ALLOW_WITH_CAPS",
          "DENY"
        ]
      },
      "DecisionReasonCode": {
        "type": "string",
        "maxLength": 128,
        "description": "Stable machine-readable reason for why a /decide call or a reservation returned decision=DENY. Populated on DecisionResponse.reason_code and on ReservationCreateResponse.reason_code. The human-readable `message` field on the parent response (when present) may carry additional context.\nEXTENSIBILITY (NORMATIVE):\n  DecisionReasonCode is intentionally an OPEN string (not a closed enum)\n  so that:\n    (a) future minor versions of this base spec can add new values without\n        a breaking change, and\n    (b) companion extension specs (e.g., cycles-protocol-extensions-v0.1.26.yaml)\n        can define additional reason codes without modifying the base schema.\n  Clients MUST gracefully handle unknown values — log them and map to\n  generic DENY handling (i.e., \"the request was denied; treat as a terminal\n  failure even if we don't recognize the specific reason\").\n\nKNOWN VALUES (v0.1.25 base):\n  - BUDGET_EXCEEDED — remaining amount insufficient on at least one derived scope\n    (evaluated against the requested estimate.amount).\n  - BUDGET_FROZEN — a derived scope has a budget in FROZEN status\n    (operator-set, no mutations allowed).\n  - BUDGET_CLOSED — a derived scope has a budget in CLOSED status\n    (permanently closed).\n  - BUDGET_NOT_FOUND — no budget exists at any derived scope in the requested unit.\n    On non-dry reserve and /v1/events paths this same condition surfaces as HTTP\n    404 with error=NOT_FOUND instead.\n  - OVERDRAFT_LIMIT_EXCEEDED — either (a) debt + delta > overdraft_limit on commit,\n    or (b) the scope is in over-limit state (is_over_limit=true) and no new\n    reservations are permitted until reconciled.\n  - DEBT_OUTSTANDING — a derived scope has debt > 0 and overdraft_limit == 0\n    (no policy permits further debt accrual).\n  - TENANT_CLOSED — the owning tenant's status is CLOSED (deployments with a\n    governance plane; added in revision 2026-07-10). Populated on /decide and\n    dry_run=true evaluations; the persisting mutation surface reports the same\n    condition as HTTP 409 error=TENANT_CLOSED instead (see the closed-tenant\n    binding in ERROR SEMANTICS).\n\nKNOWN VALUES (v0.1.26 runtime extension):\n  - ACTION_QUOTA_EXCEEDED — a per-kind or risk-class action quota rule was\n    exceeded for the target scope and window.\n  - ACTION_KIND_DENIED — the action kind is in the matching policy's\n    denied_action_kinds list.\n  - ACTION_KIND_NOT_ALLOWED — the matching policy has a non-empty\n    allowed_action_kinds list and the action kind is not in it.\n  See cycles-protocol-extensions-v0.1.26.yaml for full semantics,\n  evaluation order, and DenyDetail structure (populated alongside\n  reason_code for v0.1.26 denials).\n\nDistinct from the `ErrorCode` enum: reason codes appear only on 200 OK responses with decision=DENY; ErrorCodes appear only on 4xx/5xx responses with an `error` field. Some labels overlap (e.g. BUDGET_EXCEEDED appears in both sets) because the same underlying condition is reported two ways depending on the endpoint: /decide and dry_run surface it as a non-4xx DENY decision, while non-dry reserve surfaces it as a 409 error.\nSDK GENERATION GUIDANCE:\n  Codegen tools SHOULD generate a string type (not a strict enum) and\n  expose the known values above as constants or string literal union\n  members for ergonomic use. SDK code MUST NOT reject unknown values\n  at the deserialization boundary."
      },
      "UnitEnum": {
        "type": "string",
        "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
        "enum": [
          "USD_MICROCENTS",
          "TOKENS",
          "CREDITS",
          "RISK_POINTS"
        ]
      },
      "SortDirection": {
        "type": "string",
        "enum": [
          "asc",
          "desc"
        ],
        "default": "desc",
        "description": "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."
      },
      "Amount": {
        "type": "object",
        "required": [
          "unit",
          "amount"
        ],
        "additionalProperties": false,
        "example": {
          "unit": "USD_MICROCENTS",
          "amount": 500000
        },
        "properties": {
          "unit": {
            "type": "string",
            "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
            "enum": [
              "USD_MICROCENTS",
              "TOKENS",
              "CREDITS",
              "RISK_POINTS"
            ]
          },
          "amount": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          }
        }
      },
      "SignedAmount": {
        "type": "object",
        "required": [
          "unit",
          "amount"
        ],
        "additionalProperties": false,
        "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
        "example": {
          "unit": "USD_MICROCENTS",
          "amount": -120000
        },
        "properties": {
          "unit": {
            "type": "string",
            "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
            "enum": [
              "USD_MICROCENTS",
              "TOKENS",
              "CREDITS",
              "RISK_POINTS"
            ]
          },
          "amount": {
            "type": "integer",
            "format": "int64",
            "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
          }
        }
      },
      "Subject": {
        "type": "object",
        "description": "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.\nCHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset)\n  SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope\n  paths use \":\" and \"/\" as structural delimiters, so delimiter,\n  whitespace, and control characters have no stable canonical encoding.\n- Servers MAY reject standard-field values outside this pattern with\n  400 INVALID_REQUEST (the reference implementation does). Clients\n  MUST NOT rely on out-of-pattern values being accepted, and portable\n  clients SHOULD restrict themselves to the pattern above.\n\nEXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies\n  (e.g., cost_center/department/project) and policy/reporting.\n- 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.\n",
        "additionalProperties": false,
        "minProperties": 1,
        "anyOf": [
          {
            "required": [
              "tenant"
            ]
          },
          {
            "required": [
              "workspace"
            ]
          },
          {
            "required": [
              "app"
            ]
          },
          {
            "required": [
              "workflow"
            ]
          },
          {
            "required": [
              "agent"
            ]
          },
          {
            "required": [
              "toolset"
            ]
          }
        ],
        "properties": {
          "tenant": {
            "type": "string",
            "maxLength": 128
          },
          "workspace": {
            "type": "string",
            "maxLength": 128
          },
          "app": {
            "type": "string",
            "maxLength": 128
          },
          "workflow": {
            "type": "string",
            "maxLength": 128
          },
          "agent": {
            "type": "string",
            "maxLength": 128
          },
          "toolset": {
            "type": "string",
            "maxLength": 128
          },
          "dimensions": {
            "type": "object",
            "description": "Optional custom dimensions for enterprise taxonomies and policy/reporting.",
            "additionalProperties": {
              "type": "string",
              "maxLength": 256
            },
            "maxProperties": 16
          }
        }
      },
      "Action": {
        "type": "object",
        "required": [
          "kind",
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "kind": {
            "type": "string",
            "maxLength": 64,
            "description": "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\n"
          },
          "name": {
            "type": "string",
            "maxLength": 256,
            "description": "Provider/model/tool identifier (e.g., \"openai:gpt-4o-mini\", \"web.search\")"
          },
          "tags": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "maxLength": 64
            },
            "description": "Optional policy tags (e.g., [\"prod\",\"customer-facing\"])"
          }
        }
      },
      "Caps": {
        "type": "object",
        "description": "Optional soft-landing constraints returned by /decide or reservation ALLOW_WITH_CAPS. v0 intentionally keeps caps simple and concrete (no condition language).\nPRECEDENCE RULES: - If tool_allowlist is non-empty, ONLY those tools are allowed (denylist ignored). - Otherwise, if tool_denylist is non-empty, all tools EXCEPT those are allowed. - If both are empty/null, no tool restrictions apply. - Tool names are case-sensitive and match Action.name exactly.\n",
        "additionalProperties": false,
        "properties": {
          "max_tokens": {
            "type": "integer",
            "minimum": 0
          },
          "max_steps_remaining": {
            "type": "integer",
            "minimum": 0
          },
          "tool_allowlist": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 256
            }
          },
          "tool_denylist": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 256
            }
          },
          "cooldown_ms": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "DecisionRequest": {
        "type": "object",
        "required": [
          "idempotency_key",
          "subject",
          "action",
          "estimate"
        ],
        "additionalProperties": false,
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256
          },
          "subject": {
            "type": "object",
            "description": "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.\nCHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset)\n  SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope\n  paths use \":\" and \"/\" as structural delimiters, so delimiter,\n  whitespace, and control characters have no stable canonical encoding.\n- Servers MAY reject standard-field values outside this pattern with\n  400 INVALID_REQUEST (the reference implementation does). Clients\n  MUST NOT rely on out-of-pattern values being accepted, and portable\n  clients SHOULD restrict themselves to the pattern above.\n\nEXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies\n  (e.g., cost_center/department/project) and policy/reporting.\n- 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.\n",
            "additionalProperties": false,
            "minProperties": 1,
            "anyOf": [
              {
                "required": [
                  "tenant"
                ]
              },
              {
                "required": [
                  "workspace"
                ]
              },
              {
                "required": [
                  "app"
                ]
              },
              {
                "required": [
                  "workflow"
                ]
              },
              {
                "required": [
                  "agent"
                ]
              },
              {
                "required": [
                  "toolset"
                ]
              }
            ],
            "properties": {
              "tenant": {
                "type": "string",
                "maxLength": 128
              },
              "workspace": {
                "type": "string",
                "maxLength": 128
              },
              "app": {
                "type": "string",
                "maxLength": 128
              },
              "workflow": {
                "type": "string",
                "maxLength": 128
              },
              "agent": {
                "type": "string",
                "maxLength": 128
              },
              "toolset": {
                "type": "string",
                "maxLength": 128
              },
              "dimensions": {
                "type": "object",
                "description": "Optional custom dimensions for enterprise taxonomies and policy/reporting.",
                "additionalProperties": {
                  "type": "string",
                  "maxLength": 256
                },
                "maxProperties": 16
              }
            }
          },
          "action": {
            "type": "object",
            "required": [
              "kind",
              "name"
            ],
            "additionalProperties": false,
            "properties": {
              "kind": {
                "type": "string",
                "maxLength": 64,
                "description": "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\n"
              },
              "name": {
                "type": "string",
                "maxLength": 256,
                "description": "Provider/model/tool identifier (e.g., \"openai:gpt-4o-mini\", \"web.search\")"
              },
              "tags": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "type": "string",
                  "maxLength": 64
                },
                "description": "Optional policy tags (e.g., [\"prod\",\"customer-facing\"])"
              }
            }
          },
          "estimate": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "DecisionResponse": {
        "type": "object",
        "required": [
          "decision"
        ],
        "additionalProperties": false,
        "example": {
          "decision": "ALLOW",
          "affected_scopes": [
            "tenant:acme-corp",
            "tenant:acme-corp/workspace:prod"
          ]
        },
        "properties": {
          "decision": {
            "type": "string",
            "enum": [
              "ALLOW",
              "ALLOW_WITH_CAPS",
              "DENY"
            ]
          },
          "caps": {
            "type": "object",
            "description": "Optional soft-landing constraints returned by /decide or reservation ALLOW_WITH_CAPS. v0 intentionally keeps caps simple and concrete (no condition language).\nPRECEDENCE RULES: - If tool_allowlist is non-empty, ONLY those tools are allowed (denylist ignored). - Otherwise, if tool_denylist is non-empty, all tools EXCEPT those are allowed. - If both are empty/null, no tool restrictions apply. - Tool names are case-sensitive and match Action.name exactly.\n",
            "additionalProperties": false,
            "properties": {
              "max_tokens": {
                "type": "integer",
                "minimum": 0
              },
              "max_steps_remaining": {
                "type": "integer",
                "minimum": 0
              },
              "tool_allowlist": {
                "type": "array",
                "items": {
                  "type": "string",
                  "maxLength": 256
                }
              },
              "tool_denylist": {
                "type": "array",
                "items": {
                  "type": "string",
                  "maxLength": 256
                }
              },
              "cooldown_ms": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "reason_code": {
            "type": "string",
            "maxLength": 128,
            "description": "Stable machine-readable reason for why a /decide call or a reservation returned decision=DENY. Populated on DecisionResponse.reason_code and on ReservationCreateResponse.reason_code. The human-readable `message` field on the parent response (when present) may carry additional context.\nEXTENSIBILITY (NORMATIVE):\n  DecisionReasonCode is intentionally an OPEN string (not a closed enum)\n  so that:\n    (a) future minor versions of this base spec can add new values without\n        a breaking change, and\n    (b) companion extension specs (e.g., cycles-protocol-extensions-v0.1.26.yaml)\n        can define additional reason codes without modifying the base schema.\n  Clients MUST gracefully handle unknown values — log them and map to\n  generic DENY handling (i.e., \"the request was denied; treat as a terminal\n  failure even if we don't recognize the specific reason\").\n\nKNOWN VALUES (v0.1.25 base):\n  - BUDGET_EXCEEDED — remaining amount insufficient on at least one derived scope\n    (evaluated against the requested estimate.amount).\n  - BUDGET_FROZEN — a derived scope has a budget in FROZEN status\n    (operator-set, no mutations allowed).\n  - BUDGET_CLOSED — a derived scope has a budget in CLOSED status\n    (permanently closed).\n  - BUDGET_NOT_FOUND — no budget exists at any derived scope in the requested unit.\n    On non-dry reserve and /v1/events paths this same condition surfaces as HTTP\n    404 with error=NOT_FOUND instead.\n  - OVERDRAFT_LIMIT_EXCEEDED — either (a) debt + delta > overdraft_limit on commit,\n    or (b) the scope is in over-limit state (is_over_limit=true) and no new\n    reservations are permitted until reconciled.\n  - DEBT_OUTSTANDING — a derived scope has debt > 0 and overdraft_limit == 0\n    (no policy permits further debt accrual).\n  - TENANT_CLOSED — the owning tenant's status is CLOSED (deployments with a\n    governance plane; added in revision 2026-07-10). Populated on /decide and\n    dry_run=true evaluations; the persisting mutation surface reports the same\n    condition as HTTP 409 error=TENANT_CLOSED instead (see the closed-tenant\n    binding in ERROR SEMANTICS).\n\nKNOWN VALUES (v0.1.26 runtime extension):\n  - ACTION_QUOTA_EXCEEDED — a per-kind or risk-class action quota rule was\n    exceeded for the target scope and window.\n  - ACTION_KIND_DENIED — the action kind is in the matching policy's\n    denied_action_kinds list.\n  - ACTION_KIND_NOT_ALLOWED — the matching policy has a non-empty\n    allowed_action_kinds list and the action kind is not in it.\n  See cycles-protocol-extensions-v0.1.26.yaml for full semantics,\n  evaluation order, and DenyDetail structure (populated alongside\n  reason_code for v0.1.26 denials).\n\nDistinct from the `ErrorCode` enum: reason codes appear only on 200 OK responses with decision=DENY; ErrorCodes appear only on 4xx/5xx responses with an `error` field. Some labels overlap (e.g. BUDGET_EXCEEDED appears in both sets) because the same underlying condition is reported two ways depending on the endpoint: /decide and dry_run surface it as a non-4xx DENY decision, while non-dry reserve surfaces it as a 409 error.\nSDK GENERATION GUIDANCE:\n  Codegen tools SHOULD generate a string type (not a strict enum) and\n  expose the known values above as constants or string literal union\n  members for ergonomic use. SDK code MUST NOT reject unknown values\n  at the deserialization boundary."
          },
          "retry_after_ms": {
            "type": "integer",
            "minimum": 0
          },
          "affected_scopes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Canonical scope identifiers impacted by this decision, in canonical order."
          },
          "cycles_evidence": {
            "type": "object",
            "additionalProperties": false,
            "description": "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.\n\nTRANSPORT 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.",
            "required": [
              "evidence_id",
              "cycles_evidence_url"
            ],
            "properties": {
              "evidence_id": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$",
                "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
              },
              "cycles_evidence_url": {
                "type": "string",
                "format": "uri",
                "description": "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`."
              }
            }
          }
        }
      },
      "CommitOveragePolicy": {
        "type": "string",
        "description": "How server handles commits where actual > reserved.\nREJECT: reject commit (client must reserve a buffer or over-estimate).\n  Recommended: add 10-20% buffer to estimates when using REJECT.\n  If the action already happened externally, this creates an unaccounted gap.\n\nALLOW_IF_AVAILABLE (default): commit always succeeds when actual > reserved. If remaining budget supports the full delta, charge it atomically across all derived scopes. Otherwise, cap the delta to available remaining (minimum across all affected scopes, floor 0), charge estimate + capped_delta, and set is_over_limit=true on scopes where the full delta could not be covered. This blocks future reservations until reconciled. Never creates debt. Never rejects a commit — the action already happened.\nALLOW_WITH_OVERDRAFT: if remaining budget supports the delta, commit normally. Otherwise, check if (debt + delta) <= overdraft_limit across all affected scopes.\n  If yes: commit succeeds, add delta to debt, remaining can go negative.\n  If no: commit fails with 409 OVERDRAFT_LIMIT_EXCEEDED.\nThis policy ensures the ledger always reflects reality even when budget is exhausted at commit time.\nCONCURRENCY (NORMATIVE):\n  - The overdraft_limit check is performed per-commit and is NOT atomic across concurrent commits to the same scope.\n  - Multiple commits may each individually pass the (debt + delta) <= limit check but collectively cause debt to exceed overdraft_limit.\n  - This is acceptable: all commits succeed (actions already happened), and the scope enters \"over-limit\" state, \n    blocking future reservations until reconciled.\n  - Servers SHOULD use optimistic locking or compare-and-swap on debt updates to ensure individual commit atomicity, \n    but cross-commit atomicity is not required.\n",
        "enum": [
          "REJECT",
          "ALLOW_IF_AVAILABLE",
          "ALLOW_WITH_OVERDRAFT"
        ]
      },
      "ReservationStatus": {
        "type": "string",
        "description": "Reservation lifecycle state (v1+ may add additional terminal states).",
        "enum": [
          "ACTIVE",
          "COMMITTED",
          "RELEASED",
          "EXPIRED"
        ]
      },
      "ReservationCreateRequest": {
        "type": "object",
        "required": [
          "idempotency_key",
          "subject",
          "action",
          "estimate"
        ],
        "additionalProperties": false,
        "example": {
          "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
        },
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256
          },
          "subject": {
            "type": "object",
            "description": "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.\nCHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset)\n  SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope\n  paths use \":\" and \"/\" as structural delimiters, so delimiter,\n  whitespace, and control characters have no stable canonical encoding.\n- Servers MAY reject standard-field values outside this pattern with\n  400 INVALID_REQUEST (the reference implementation does). Clients\n  MUST NOT rely on out-of-pattern values being accepted, and portable\n  clients SHOULD restrict themselves to the pattern above.\n\nEXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies\n  (e.g., cost_center/department/project) and policy/reporting.\n- 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.\n",
            "additionalProperties": false,
            "minProperties": 1,
            "anyOf": [
              {
                "required": [
                  "tenant"
                ]
              },
              {
                "required": [
                  "workspace"
                ]
              },
              {
                "required": [
                  "app"
                ]
              },
              {
                "required": [
                  "workflow"
                ]
              },
              {
                "required": [
                  "agent"
                ]
              },
              {
                "required": [
                  "toolset"
                ]
              }
            ],
            "properties": {
              "tenant": {
                "type": "string",
                "maxLength": 128
              },
              "workspace": {
                "type": "string",
                "maxLength": 128
              },
              "app": {
                "type": "string",
                "maxLength": 128
              },
              "workflow": {
                "type": "string",
                "maxLength": 128
              },
              "agent": {
                "type": "string",
                "maxLength": 128
              },
              "toolset": {
                "type": "string",
                "maxLength": 128
              },
              "dimensions": {
                "type": "object",
                "description": "Optional custom dimensions for enterprise taxonomies and policy/reporting.",
                "additionalProperties": {
                  "type": "string",
                  "maxLength": 256
                },
                "maxProperties": 16
              }
            }
          },
          "action": {
            "type": "object",
            "required": [
              "kind",
              "name"
            ],
            "additionalProperties": false,
            "properties": {
              "kind": {
                "type": "string",
                "maxLength": 64,
                "description": "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\n"
              },
              "name": {
                "type": "string",
                "maxLength": 256,
                "description": "Provider/model/tool identifier (e.g., \"openai:gpt-4o-mini\", \"web.search\")"
              },
              "tags": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "type": "string",
                  "maxLength": 64
                },
                "description": "Optional policy tags (e.g., [\"prod\",\"customer-facing\"])"
              }
            }
          },
          "estimate": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "ttl_ms": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 86400000,
            "default": 60000
          },
          "grace_period_ms": {
            "type": "integer",
            "minimum": 0,
            "maximum": 60000,
            "default": 5000,
            "description": "Grace window after TTL for in-flight commits. Default 5s.\nOPERATIONAL GUIDANCE: - For high-latency actions (streaming LLM, slow APIs), consider setting higher (10-30s).\nINTEROP / SAFETY RATIONALE: - The 60s maximum is an interoperability ceiling to limit long-lived \"in-flight\" commit windows and reduce\n  zombie-reservation and abuse surface (grace extends the time that a crashed client can keep budget locked).\n\nLONG-RUNNING WORKFLOWS: - For multi-minute operations, clients SHOULD keep ttl_ms relatively short and use /v1/reservations/{reservation_id}/extend\n  as a heartbeat, and/or use chunked reserve/commit cycles rather than relying on large grace_period_ms.\n"
          },
          "overage_policy": {
            "type": "string",
            "description": "How server handles commits where actual > reserved.\nREJECT: reject commit (client must reserve a buffer or over-estimate).\n  Recommended: add 10-20% buffer to estimates when using REJECT.\n  If the action already happened externally, this creates an unaccounted gap.\n\nALLOW_IF_AVAILABLE (default): commit always succeeds when actual > reserved. If remaining budget supports the full delta, charge it atomically across all derived scopes. Otherwise, cap the delta to available remaining (minimum across all affected scopes, floor 0), charge estimate + capped_delta, and set is_over_limit=true on scopes where the full delta could not be covered. This blocks future reservations until reconciled. Never creates debt. Never rejects a commit — the action already happened.\nALLOW_WITH_OVERDRAFT: if remaining budget supports the delta, commit normally. Otherwise, check if (debt + delta) <= overdraft_limit across all affected scopes.\n  If yes: commit succeeds, add delta to debt, remaining can go negative.\n  If no: commit fails with 409 OVERDRAFT_LIMIT_EXCEEDED.\nThis policy ensures the ledger always reflects reality even when budget is exhausted at commit time.\nCONCURRENCY (NORMATIVE):\n  - The overdraft_limit check is performed per-commit and is NOT atomic across concurrent commits to the same scope.\n  - Multiple commits may each individually pass the (debt + delta) <= limit check but collectively cause debt to exceed overdraft_limit.\n  - This is acceptable: all commits succeed (actions already happened), and the scope enters \"over-limit\" state, \n    blocking future reservations until reconciled.\n  - Servers SHOULD use optimistic locking or compare-and-swap on debt updates to ensure individual commit atomicity, \n    but cross-commit atomicity is not required.\n",
            "enum": [
              "REJECT",
              "ALLOW_IF_AVAILABLE",
              "ALLOW_WITH_OVERDRAFT"
            ]
          },
          "dry_run": {
            "type": "boolean",
            "default": false,
            "description": "Shadow-mode evaluation. If true, the server MUST evaluate the reservation request and return decision/caps/affected_scopes as if it would reserve, but MUST NOT modify balances, persist a reservation, or require commit/release. Intended for safe rollout and testing of full reserve-path logic.\n"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Balance": {
        "type": "object",
        "required": [
          "scope",
          "scope_path",
          "remaining"
        ],
        "additionalProperties": false,
        "example": {
          "scope": "tenant:acme-corp/workspace:prod",
          "scope_path": "tenant:acme-corp/workspace:prod",
          "remaining": {
            "unit": "USD_MICROCENTS",
            "amount": 4577000
          },
          "reserved": {
            "unit": "USD_MICROCENTS",
            "amount": 500000
          },
          "spent": {
            "unit": "USD_MICROCENTS",
            "amount": 423000
          },
          "allocated": {
            "unit": "USD_MICROCENTS",
            "amount": 5500000
          }
        },
        "description": "Ledger state for a single (scope, unit) balance.\nUNIT CONSISTENCY (NORMATIVE):\n  - All Amount fields within a Balance (remaining/reserved/spent/allocated/debt/overdraft_limit), if present, MUST share the same unit.\n  - Servers MUST NOT emit a Balance with mixed units; clients MAY treat such responses as invalid.\n\nLEDGER INVARIANT (NORMATIVE):\n   - If allocated, spent, reserved, and debt are all present, then remaining MUST satisfy: \n     remaining.amount = allocated.amount - spent.amount - reserved.amount - debt.amount (same unit).\n   - Note that remaining.amount can be negative when debt exceeds available budget.\n   - If allocated is absent, remaining is authoritative and clients MUST treat it as opaque (i.e., MUST NOT derive it from other fields).\n   - Servers MUST NOT emit a Balance where the invariant is violated when all fields are present.\n\nDEBT SEMANTICS (NORMATIVE):\n   - debt represents actual consumption that occurred when insufficient budget was available to cover a commit overage.\n   - debt is created only when overage_policy=ALLOW_WITH_OVERDRAFT and (remaining + released_reservation) < actual at commit time.\n   - When debt > 0, new reservations MUST be rejected with 409 DEBT_OUTSTANDING (unless explicitly allowed by policy).\n   - debt MUST be repaid via budget funding operations (out-of-scope for this API) before new reservations are permitted.\n   - When budget is added to a scope with debt > 0, debt MUST be repaid first, then remaining funds added to remaining.\n\nOVERDRAFT LIMIT (NORMATIVE):\n   - overdraft_limit defines the target maximum debt for this scope.\n   - If overdraft_limit is absent or amount=0, no overdraft is permitted (behaves as ALLOW_IF_AVAILABLE).\n   - The overdraft_limit is enforced per-commit but is NOT atomic across concurrent commits.\n     Multiple concurrent commits MAY cause cumulative debt to briefly exceed overdraft_limit.\n   - Individual commits are rejected with 409 OVERDRAFT_LIMIT_EXCEEDED only if (current_debt + delta) > overdraft_limit \n     at the moment of that specific commit.\n   - When debt > overdraft_limit after reconciliation (due to concurrent commits), the scope enters an \"over-limit\" state:\n       * The server MUST set is_over_limit=true on the Balance\n       * ALL new reservations against that scope MUST be rejected with 409 OVERDRAFT_LIMIT_EXCEEDED (not DEBT_OUTSTANDING)\n       * Existing active reservations MAY be committed or released normally\n       * The server SHOULD emit alerts/events for operator intervention\n   - Once debt is repaid below overdraft_limit, is_over_limit returns to false and normal reservation operation resumes.",
        "properties": {
          "scope": {
            "type": "string",
            "description": "Canonical scope identifier (server-derived), e.g. \"tenant:t1\", \"workflow:run123\""
          },
          "scope_path": {
            "type": "string",
            "description": "Canonical hierarchical path (server-derived)"
          },
          "remaining": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": -120000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
              }
            }
          },
          "reserved": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "spent": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "debt": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "allocated": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "overdraft_limit": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "is_over_limit": {
            "type": "boolean",
            "description": "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."
          }
        }
      },
      "ReservationCreateResponse": {
        "type": "object",
        "required": [
          "decision",
          "affected_scopes"
        ],
        "additionalProperties": false,
        "example": {
          "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"
          ]
        },
        "properties": {
          "decision": {
            "type": "string",
            "enum": [
              "ALLOW",
              "ALLOW_WITH_CAPS",
              "DENY"
            ]
          },
          "reservation_id": {
            "type": "string",
            "description": "Present if decision is ALLOW or ALLOW_WITH_CAPS and dry_run is false. MUST be absent when dry_run is true."
          },
          "reserved": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "expires_at_ms": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          },
          "remaining_ttl_ms": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "Observation of remaining reservation lifetime in milliseconds while this response payload is constructed, computed as max(0, expires_at_ms - current authoritative server time) using the same authoritative clock used for reservation expiry decisions. Servers SHOULD emit it on successful responses that return a live reservation; it MUST be absent on dry-run and DENY responses. This field is volatile transport metadata and is not part of the attested CyclesEvidence payload. On a same-key idempotent replay, a server that emits it MUST recompute it at replay-response construction time from the original expires_at_ms and MUST NOT copy its originally stored value; it MUST be 0 if the reservation is no longer ACTIVE and MAY conservatively understate current lead if a later, separately keyed extension moved expiry outward. All other fields replay verbatim. Added in 0.1.25.16 (additive); clients MUST treat it as optional for backward compatibility. When present it is the NORMATIVE input to heartbeat scheduling — see HEARTBEAT GUIDANCE on extendReservation.\n"
          },
          "scope_path": {
            "type": "string",
            "description": "Canonical scope path (server-derived)"
          },
          "affected_scopes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Canonical scope identifiers impacted by this reservation, in canonical order."
          },
          "caps": {
            "type": "object",
            "description": "Optional soft-landing constraints returned by /decide or reservation ALLOW_WITH_CAPS. v0 intentionally keeps caps simple and concrete (no condition language).\nPRECEDENCE RULES: - If tool_allowlist is non-empty, ONLY those tools are allowed (denylist ignored). - Otherwise, if tool_denylist is non-empty, all tools EXCEPT those are allowed. - If both are empty/null, no tool restrictions apply. - Tool names are case-sensitive and match Action.name exactly.\n",
            "additionalProperties": false,
            "properties": {
              "max_tokens": {
                "type": "integer",
                "minimum": 0
              },
              "max_steps_remaining": {
                "type": "integer",
                "minimum": 0
              },
              "tool_allowlist": {
                "type": "array",
                "items": {
                  "type": "string",
                  "maxLength": 256
                }
              },
              "tool_denylist": {
                "type": "array",
                "items": {
                  "type": "string",
                  "maxLength": 256
                }
              },
              "cooldown_ms": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "balances": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "scope",
                "scope_path",
                "remaining"
              ],
              "additionalProperties": false,
              "example": {
                "scope": "tenant:acme-corp/workspace:prod",
                "scope_path": "tenant:acme-corp/workspace:prod",
                "remaining": {
                  "unit": "USD_MICROCENTS",
                  "amount": 4577000
                },
                "reserved": {
                  "unit": "USD_MICROCENTS",
                  "amount": 500000
                },
                "spent": {
                  "unit": "USD_MICROCENTS",
                  "amount": 423000
                },
                "allocated": {
                  "unit": "USD_MICROCENTS",
                  "amount": 5500000
                }
              },
              "description": "Ledger state for a single (scope, unit) balance.\nUNIT CONSISTENCY (NORMATIVE):\n  - All Amount fields within a Balance (remaining/reserved/spent/allocated/debt/overdraft_limit), if present, MUST share the same unit.\n  - Servers MUST NOT emit a Balance with mixed units; clients MAY treat such responses as invalid.\n\nLEDGER INVARIANT (NORMATIVE):\n   - If allocated, spent, reserved, and debt are all present, then remaining MUST satisfy: \n     remaining.amount = allocated.amount - spent.amount - reserved.amount - debt.amount (same unit).\n   - Note that remaining.amount can be negative when debt exceeds available budget.\n   - If allocated is absent, remaining is authoritative and clients MUST treat it as opaque (i.e., MUST NOT derive it from other fields).\n   - Servers MUST NOT emit a Balance where the invariant is violated when all fields are present.\n\nDEBT SEMANTICS (NORMATIVE):\n   - debt represents actual consumption that occurred when insufficient budget was available to cover a commit overage.\n   - debt is created only when overage_policy=ALLOW_WITH_OVERDRAFT and (remaining + released_reservation) < actual at commit time.\n   - When debt > 0, new reservations MUST be rejected with 409 DEBT_OUTSTANDING (unless explicitly allowed by policy).\n   - debt MUST be repaid via budget funding operations (out-of-scope for this API) before new reservations are permitted.\n   - When budget is added to a scope with debt > 0, debt MUST be repaid first, then remaining funds added to remaining.\n\nOVERDRAFT LIMIT (NORMATIVE):\n   - overdraft_limit defines the target maximum debt for this scope.\n   - If overdraft_limit is absent or amount=0, no overdraft is permitted (behaves as ALLOW_IF_AVAILABLE).\n   - The overdraft_limit is enforced per-commit but is NOT atomic across concurrent commits.\n     Multiple concurrent commits MAY cause cumulative debt to briefly exceed overdraft_limit.\n   - Individual commits are rejected with 409 OVERDRAFT_LIMIT_EXCEEDED only if (current_debt + delta) > overdraft_limit \n     at the moment of that specific commit.\n   - When debt > overdraft_limit after reconciliation (due to concurrent commits), the scope enters an \"over-limit\" state:\n       * The server MUST set is_over_limit=true on the Balance\n       * ALL new reservations against that scope MUST be rejected with 409 OVERDRAFT_LIMIT_EXCEEDED (not DEBT_OUTSTANDING)\n       * Existing active reservations MAY be committed or released normally\n       * The server SHOULD emit alerts/events for operator intervention\n   - Once debt is repaid below overdraft_limit, is_over_limit returns to false and normal reservation operation resumes.",
              "properties": {
                "scope": {
                  "type": "string",
                  "description": "Canonical scope identifier (server-derived), e.g. \"tenant:t1\", \"workflow:run123\""
                },
                "scope_path": {
                  "type": "string",
                  "description": "Canonical hierarchical path (server-derived)"
                },
                "remaining": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": -120000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
                    }
                  }
                },
                "reserved": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "spent": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "debt": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "allocated": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "overdraft_limit": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "is_over_limit": {
                  "type": "boolean",
                  "description": "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."
                }
              }
            }
          },
          "reason_code": {
            "type": "string",
            "maxLength": 128,
            "description": "Stable machine-readable reason for why a /decide call or a reservation returned decision=DENY. Populated on DecisionResponse.reason_code and on ReservationCreateResponse.reason_code. The human-readable `message` field on the parent response (when present) may carry additional context.\nEXTENSIBILITY (NORMATIVE):\n  DecisionReasonCode is intentionally an OPEN string (not a closed enum)\n  so that:\n    (a) future minor versions of this base spec can add new values without\n        a breaking change, and\n    (b) companion extension specs (e.g., cycles-protocol-extensions-v0.1.26.yaml)\n        can define additional reason codes without modifying the base schema.\n  Clients MUST gracefully handle unknown values — log them and map to\n  generic DENY handling (i.e., \"the request was denied; treat as a terminal\n  failure even if we don't recognize the specific reason\").\n\nKNOWN VALUES (v0.1.25 base):\n  - BUDGET_EXCEEDED — remaining amount insufficient on at least one derived scope\n    (evaluated against the requested estimate.amount).\n  - BUDGET_FROZEN — a derived scope has a budget in FROZEN status\n    (operator-set, no mutations allowed).\n  - BUDGET_CLOSED — a derived scope has a budget in CLOSED status\n    (permanently closed).\n  - BUDGET_NOT_FOUND — no budget exists at any derived scope in the requested unit.\n    On non-dry reserve and /v1/events paths this same condition surfaces as HTTP\n    404 with error=NOT_FOUND instead.\n  - OVERDRAFT_LIMIT_EXCEEDED — either (a) debt + delta > overdraft_limit on commit,\n    or (b) the scope is in over-limit state (is_over_limit=true) and no new\n    reservations are permitted until reconciled.\n  - DEBT_OUTSTANDING — a derived scope has debt > 0 and overdraft_limit == 0\n    (no policy permits further debt accrual).\n  - TENANT_CLOSED — the owning tenant's status is CLOSED (deployments with a\n    governance plane; added in revision 2026-07-10). Populated on /decide and\n    dry_run=true evaluations; the persisting mutation surface reports the same\n    condition as HTTP 409 error=TENANT_CLOSED instead (see the closed-tenant\n    binding in ERROR SEMANTICS).\n\nKNOWN VALUES (v0.1.26 runtime extension):\n  - ACTION_QUOTA_EXCEEDED — a per-kind or risk-class action quota rule was\n    exceeded for the target scope and window.\n  - ACTION_KIND_DENIED — the action kind is in the matching policy's\n    denied_action_kinds list.\n  - ACTION_KIND_NOT_ALLOWED — the matching policy has a non-empty\n    allowed_action_kinds list and the action kind is not in it.\n  See cycles-protocol-extensions-v0.1.26.yaml for full semantics,\n  evaluation order, and DenyDetail structure (populated alongside\n  reason_code for v0.1.26 denials).\n\nDistinct from the `ErrorCode` enum: reason codes appear only on 200 OK responses with decision=DENY; ErrorCodes appear only on 4xx/5xx responses with an `error` field. Some labels overlap (e.g. BUDGET_EXCEEDED appears in both sets) because the same underlying condition is reported two ways depending on the endpoint: /decide and dry_run surface it as a non-4xx DENY decision, while non-dry reserve surfaces it as a 409 error.\nSDK GENERATION GUIDANCE:\n  Codegen tools SHOULD generate a string type (not a strict enum) and\n  expose the known values above as constants or string literal union\n  members for ergonomic use. SDK code MUST NOT reject unknown values\n  at the deserialization boundary."
          },
          "retry_after_ms": {
            "type": "integer",
            "minimum": 0
          },
          "cycles_evidence": {
            "type": "object",
            "additionalProperties": false,
            "description": "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.\n\nTRANSPORT 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.",
            "required": [
              "evidence_id",
              "cycles_evidence_url"
            ],
            "properties": {
              "evidence_id": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$",
                "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
              },
              "cycles_evidence_url": {
                "type": "string",
                "format": "uri",
                "description": "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`."
              }
            }
          }
        }
      },
      "StandardMetrics": {
        "type": "object",
        "description": "Optional standard metrics for commit/event. All fields are optional. Recommended for consistency across clients. For custom fields, use `custom`.\n",
        "additionalProperties": false,
        "properties": {
          "tokens_input": {
            "type": "integer",
            "minimum": 0,
            "description": "Input tokens consumed"
          },
          "tokens_output": {
            "type": "integer",
            "minimum": 0,
            "description": "Output tokens generated"
          },
          "latency_ms": {
            "type": "integer",
            "minimum": 0,
            "description": "Total operation latency in milliseconds"
          },
          "model_version": {
            "type": "string",
            "maxLength": 128,
            "description": "Actual model/tool version used"
          },
          "custom": {
            "type": "object",
            "additionalProperties": true,
            "description": "Arbitrary additional metrics"
          }
        }
      },
      "CommitRequest": {
        "type": "object",
        "required": [
          "idempotency_key",
          "actual"
        ],
        "additionalProperties": false,
        "example": {
          "idempotency_key": "idem_20260412_run42_step1_commit",
          "actual": {
            "unit": "USD_MICROCENTS",
            "amount": 423000
          },
          "metrics": {
            "tokens_input": 1500,
            "tokens_output": 800,
            "latency_ms": 2340,
            "model_version": "claude-sonnet-4-20250514"
          }
        },
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256
          },
          "actual": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "metrics": {
            "type": "object",
            "description": "Optional standard metrics for commit/event. All fields are optional. Recommended for consistency across clients. For custom fields, use `custom`.\n",
            "additionalProperties": false,
            "properties": {
              "tokens_input": {
                "type": "integer",
                "minimum": 0,
                "description": "Input tokens consumed"
              },
              "tokens_output": {
                "type": "integer",
                "minimum": 0,
                "description": "Output tokens generated"
              },
              "latency_ms": {
                "type": "integer",
                "minimum": 0,
                "description": "Total operation latency in milliseconds"
              },
              "model_version": {
                "type": "string",
                "maxLength": 128,
                "description": "Actual model/tool version used"
              },
              "custom": {
                "type": "object",
                "additionalProperties": true,
                "description": "Arbitrary additional metrics"
              }
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "CommitResponse": {
        "type": "object",
        "required": [
          "status",
          "charged"
        ],
        "additionalProperties": false,
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "COMMITTED"
            ]
          },
          "charged": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "released": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "balances": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "scope",
                "scope_path",
                "remaining"
              ],
              "additionalProperties": false,
              "example": {
                "scope": "tenant:acme-corp/workspace:prod",
                "scope_path": "tenant:acme-corp/workspace:prod",
                "remaining": {
                  "unit": "USD_MICROCENTS",
                  "amount": 4577000
                },
                "reserved": {
                  "unit": "USD_MICROCENTS",
                  "amount": 500000
                },
                "spent": {
                  "unit": "USD_MICROCENTS",
                  "amount": 423000
                },
                "allocated": {
                  "unit": "USD_MICROCENTS",
                  "amount": 5500000
                }
              },
              "description": "Ledger state for a single (scope, unit) balance.\nUNIT CONSISTENCY (NORMATIVE):\n  - All Amount fields within a Balance (remaining/reserved/spent/allocated/debt/overdraft_limit), if present, MUST share the same unit.\n  - Servers MUST NOT emit a Balance with mixed units; clients MAY treat such responses as invalid.\n\nLEDGER INVARIANT (NORMATIVE):\n   - If allocated, spent, reserved, and debt are all present, then remaining MUST satisfy: \n     remaining.amount = allocated.amount - spent.amount - reserved.amount - debt.amount (same unit).\n   - Note that remaining.amount can be negative when debt exceeds available budget.\n   - If allocated is absent, remaining is authoritative and clients MUST treat it as opaque (i.e., MUST NOT derive it from other fields).\n   - Servers MUST NOT emit a Balance where the invariant is violated when all fields are present.\n\nDEBT SEMANTICS (NORMATIVE):\n   - debt represents actual consumption that occurred when insufficient budget was available to cover a commit overage.\n   - debt is created only when overage_policy=ALLOW_WITH_OVERDRAFT and (remaining + released_reservation) < actual at commit time.\n   - When debt > 0, new reservations MUST be rejected with 409 DEBT_OUTSTANDING (unless explicitly allowed by policy).\n   - debt MUST be repaid via budget funding operations (out-of-scope for this API) before new reservations are permitted.\n   - When budget is added to a scope with debt > 0, debt MUST be repaid first, then remaining funds added to remaining.\n\nOVERDRAFT LIMIT (NORMATIVE):\n   - overdraft_limit defines the target maximum debt for this scope.\n   - If overdraft_limit is absent or amount=0, no overdraft is permitted (behaves as ALLOW_IF_AVAILABLE).\n   - The overdraft_limit is enforced per-commit but is NOT atomic across concurrent commits.\n     Multiple concurrent commits MAY cause cumulative debt to briefly exceed overdraft_limit.\n   - Individual commits are rejected with 409 OVERDRAFT_LIMIT_EXCEEDED only if (current_debt + delta) > overdraft_limit \n     at the moment of that specific commit.\n   - When debt > overdraft_limit after reconciliation (due to concurrent commits), the scope enters an \"over-limit\" state:\n       * The server MUST set is_over_limit=true on the Balance\n       * ALL new reservations against that scope MUST be rejected with 409 OVERDRAFT_LIMIT_EXCEEDED (not DEBT_OUTSTANDING)\n       * Existing active reservations MAY be committed or released normally\n       * The server SHOULD emit alerts/events for operator intervention\n   - Once debt is repaid below overdraft_limit, is_over_limit returns to false and normal reservation operation resumes.",
              "properties": {
                "scope": {
                  "type": "string",
                  "description": "Canonical scope identifier (server-derived), e.g. \"tenant:t1\", \"workflow:run123\""
                },
                "scope_path": {
                  "type": "string",
                  "description": "Canonical hierarchical path (server-derived)"
                },
                "remaining": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": -120000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
                    }
                  }
                },
                "reserved": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "spent": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "debt": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "allocated": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "overdraft_limit": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "is_over_limit": {
                  "type": "boolean",
                  "description": "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": {
            "type": "object",
            "additionalProperties": false,
            "description": "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.\n\nTRANSPORT 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.",
            "required": [
              "evidence_id",
              "cycles_evidence_url"
            ],
            "properties": {
              "evidence_id": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$",
                "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
              },
              "cycles_evidence_url": {
                "type": "string",
                "format": "uri",
                "description": "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`."
              }
            }
          }
        }
      },
      "ReleaseRequest": {
        "type": "object",
        "required": [
          "idempotency_key"
        ],
        "additionalProperties": false,
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256
          },
          "reason": {
            "type": "string",
            "maxLength": 256
          }
        }
      },
      "ReleaseResponse": {
        "type": "object",
        "required": [
          "status",
          "released"
        ],
        "additionalProperties": false,
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "RELEASED"
            ]
          },
          "released": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "balances": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "scope",
                "scope_path",
                "remaining"
              ],
              "additionalProperties": false,
              "example": {
                "scope": "tenant:acme-corp/workspace:prod",
                "scope_path": "tenant:acme-corp/workspace:prod",
                "remaining": {
                  "unit": "USD_MICROCENTS",
                  "amount": 4577000
                },
                "reserved": {
                  "unit": "USD_MICROCENTS",
                  "amount": 500000
                },
                "spent": {
                  "unit": "USD_MICROCENTS",
                  "amount": 423000
                },
                "allocated": {
                  "unit": "USD_MICROCENTS",
                  "amount": 5500000
                }
              },
              "description": "Ledger state for a single (scope, unit) balance.\nUNIT CONSISTENCY (NORMATIVE):\n  - All Amount fields within a Balance (remaining/reserved/spent/allocated/debt/overdraft_limit), if present, MUST share the same unit.\n  - Servers MUST NOT emit a Balance with mixed units; clients MAY treat such responses as invalid.\n\nLEDGER INVARIANT (NORMATIVE):\n   - If allocated, spent, reserved, and debt are all present, then remaining MUST satisfy: \n     remaining.amount = allocated.amount - spent.amount - reserved.amount - debt.amount (same unit).\n   - Note that remaining.amount can be negative when debt exceeds available budget.\n   - If allocated is absent, remaining is authoritative and clients MUST treat it as opaque (i.e., MUST NOT derive it from other fields).\n   - Servers MUST NOT emit a Balance where the invariant is violated when all fields are present.\n\nDEBT SEMANTICS (NORMATIVE):\n   - debt represents actual consumption that occurred when insufficient budget was available to cover a commit overage.\n   - debt is created only when overage_policy=ALLOW_WITH_OVERDRAFT and (remaining + released_reservation) < actual at commit time.\n   - When debt > 0, new reservations MUST be rejected with 409 DEBT_OUTSTANDING (unless explicitly allowed by policy).\n   - debt MUST be repaid via budget funding operations (out-of-scope for this API) before new reservations are permitted.\n   - When budget is added to a scope with debt > 0, debt MUST be repaid first, then remaining funds added to remaining.\n\nOVERDRAFT LIMIT (NORMATIVE):\n   - overdraft_limit defines the target maximum debt for this scope.\n   - If overdraft_limit is absent or amount=0, no overdraft is permitted (behaves as ALLOW_IF_AVAILABLE).\n   - The overdraft_limit is enforced per-commit but is NOT atomic across concurrent commits.\n     Multiple concurrent commits MAY cause cumulative debt to briefly exceed overdraft_limit.\n   - Individual commits are rejected with 409 OVERDRAFT_LIMIT_EXCEEDED only if (current_debt + delta) > overdraft_limit \n     at the moment of that specific commit.\n   - When debt > overdraft_limit after reconciliation (due to concurrent commits), the scope enters an \"over-limit\" state:\n       * The server MUST set is_over_limit=true on the Balance\n       * ALL new reservations against that scope MUST be rejected with 409 OVERDRAFT_LIMIT_EXCEEDED (not DEBT_OUTSTANDING)\n       * Existing active reservations MAY be committed or released normally\n       * The server SHOULD emit alerts/events for operator intervention\n   - Once debt is repaid below overdraft_limit, is_over_limit returns to false and normal reservation operation resumes.",
              "properties": {
                "scope": {
                  "type": "string",
                  "description": "Canonical scope identifier (server-derived), e.g. \"tenant:t1\", \"workflow:run123\""
                },
                "scope_path": {
                  "type": "string",
                  "description": "Canonical hierarchical path (server-derived)"
                },
                "remaining": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": -120000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
                    }
                  }
                },
                "reserved": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "spent": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "debt": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "allocated": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "overdraft_limit": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "is_over_limit": {
                  "type": "boolean",
                  "description": "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": {
            "type": "object",
            "additionalProperties": false,
            "description": "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.\n\nTRANSPORT 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.",
            "required": [
              "evidence_id",
              "cycles_evidence_url"
            ],
            "properties": {
              "evidence_id": {
                "type": "string",
                "pattern": "^[0-9a-f]{64}$",
                "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
              },
              "cycles_evidence_url": {
                "type": "string",
                "format": "uri",
                "description": "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`."
              }
            }
          }
        }
      },
      "ReservationDetail": {
        "type": "object",
        "description": "Reservation details returned by GET /v1/reservations/{reservation_id}. Useful for debugging.\n",
        "required": [
          "reservation_id",
          "status",
          "subject",
          "action",
          "reserved",
          "created_at_ms",
          "expires_at_ms",
          "scope_path",
          "affected_scopes"
        ],
        "additionalProperties": false,
        "properties": {
          "reservation_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "Reservation lifecycle state (v1+ may add additional terminal states).",
            "enum": [
              "ACTIVE",
              "COMMITTED",
              "RELEASED",
              "EXPIRED"
            ]
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256
          },
          "subject": {
            "type": "object",
            "description": "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.\nCHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset)\n  SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope\n  paths use \":\" and \"/\" as structural delimiters, so delimiter,\n  whitespace, and control characters have no stable canonical encoding.\n- Servers MAY reject standard-field values outside this pattern with\n  400 INVALID_REQUEST (the reference implementation does). Clients\n  MUST NOT rely on out-of-pattern values being accepted, and portable\n  clients SHOULD restrict themselves to the pattern above.\n\nEXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies\n  (e.g., cost_center/department/project) and policy/reporting.\n- 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.\n",
            "additionalProperties": false,
            "minProperties": 1,
            "anyOf": [
              {
                "required": [
                  "tenant"
                ]
              },
              {
                "required": [
                  "workspace"
                ]
              },
              {
                "required": [
                  "app"
                ]
              },
              {
                "required": [
                  "workflow"
                ]
              },
              {
                "required": [
                  "agent"
                ]
              },
              {
                "required": [
                  "toolset"
                ]
              }
            ],
            "properties": {
              "tenant": {
                "type": "string",
                "maxLength": 128
              },
              "workspace": {
                "type": "string",
                "maxLength": 128
              },
              "app": {
                "type": "string",
                "maxLength": 128
              },
              "workflow": {
                "type": "string",
                "maxLength": 128
              },
              "agent": {
                "type": "string",
                "maxLength": 128
              },
              "toolset": {
                "type": "string",
                "maxLength": 128
              },
              "dimensions": {
                "type": "object",
                "description": "Optional custom dimensions for enterprise taxonomies and policy/reporting.",
                "additionalProperties": {
                  "type": "string",
                  "maxLength": 256
                },
                "maxProperties": 16
              }
            }
          },
          "action": {
            "type": "object",
            "required": [
              "kind",
              "name"
            ],
            "additionalProperties": false,
            "properties": {
              "kind": {
                "type": "string",
                "maxLength": 64,
                "description": "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\n"
              },
              "name": {
                "type": "string",
                "maxLength": 256,
                "description": "Provider/model/tool identifier (e.g., \"openai:gpt-4o-mini\", \"web.search\")"
              },
              "tags": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "type": "string",
                  "maxLength": 64
                },
                "description": "Optional policy tags (e.g., [\"prod\",\"customer-facing\"])"
              }
            }
          },
          "reserved": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "committed": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "created_at_ms": {
            "type": "integer",
            "format": "int64"
          },
          "expires_at_ms": {
            "type": "integer",
            "format": "int64"
          },
          "finalized_at_ms": {
            "type": "integer",
            "format": "int64",
            "description": "Wall-clock time at which the reservation reached a terminal state. Populated on COMMITTED and RELEASED rows only; absent on ACTIVE and EXPIRED rows. The `finalized_from` / `finalized_to` window filter on `listReservations` (revision 2026-05-22) operates against this same field with the same population semantics."
          },
          "scope_path": {
            "type": "string",
            "description": "Canonical scope path (server-derived)"
          },
          "affected_scopes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Canonical scope identifiers impacted by this reservation, in canonical order."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Metadata supplied at RESERVE time (on the create-reservation request), if any. For metadata attached at COMMIT time see `committed_metadata`."
          },
          "committed_metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Metadata supplied on the COMMIT request (`CommitRequest.metadata`), preserved on the reservation record. Present on COMMITTED rows whose commit carried metadata; absent otherwise. Distinct from `metadata`, which is captured at reserve time."
          },
          "evidence": {
            "type": "object",
            "additionalProperties": false,
            "description": "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.",
            "properties": {
              "reserve": {
                "type": "object",
                "additionalProperties": false,
                "description": "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.\n\nTRANSPORT 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.",
                "required": [
                  "evidence_id",
                  "cycles_evidence_url"
                ],
                "properties": {
                  "evidence_id": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$",
                    "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                  },
                  "cycles_evidence_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "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": {
                "type": "object",
                "additionalProperties": false,
                "description": "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.\n\nTRANSPORT 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.",
                "required": [
                  "evidence_id",
                  "cycles_evidence_url"
                ],
                "properties": {
                  "evidence_id": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$",
                    "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                  },
                  "cycles_evidence_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "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": {
                "type": "object",
                "additionalProperties": false,
                "description": "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.\n\nTRANSPORT 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.",
                "required": [
                  "evidence_id",
                  "cycles_evidence_url"
                ],
                "properties": {
                  "evidence_id": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$",
                    "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                  },
                  "cycles_evidence_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "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`."
                  }
                }
              }
            }
          }
        }
      },
      "BalanceResponse": {
        "type": "object",
        "required": [
          "balances"
        ],
        "additionalProperties": false,
        "properties": {
          "balances": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "scope",
                "scope_path",
                "remaining"
              ],
              "additionalProperties": false,
              "example": {
                "scope": "tenant:acme-corp/workspace:prod",
                "scope_path": "tenant:acme-corp/workspace:prod",
                "remaining": {
                  "unit": "USD_MICROCENTS",
                  "amount": 4577000
                },
                "reserved": {
                  "unit": "USD_MICROCENTS",
                  "amount": 500000
                },
                "spent": {
                  "unit": "USD_MICROCENTS",
                  "amount": 423000
                },
                "allocated": {
                  "unit": "USD_MICROCENTS",
                  "amount": 5500000
                }
              },
              "description": "Ledger state for a single (scope, unit) balance.\nUNIT CONSISTENCY (NORMATIVE):\n  - All Amount fields within a Balance (remaining/reserved/spent/allocated/debt/overdraft_limit), if present, MUST share the same unit.\n  - Servers MUST NOT emit a Balance with mixed units; clients MAY treat such responses as invalid.\n\nLEDGER INVARIANT (NORMATIVE):\n   - If allocated, spent, reserved, and debt are all present, then remaining MUST satisfy: \n     remaining.amount = allocated.amount - spent.amount - reserved.amount - debt.amount (same unit).\n   - Note that remaining.amount can be negative when debt exceeds available budget.\n   - If allocated is absent, remaining is authoritative and clients MUST treat it as opaque (i.e., MUST NOT derive it from other fields).\n   - Servers MUST NOT emit a Balance where the invariant is violated when all fields are present.\n\nDEBT SEMANTICS (NORMATIVE):\n   - debt represents actual consumption that occurred when insufficient budget was available to cover a commit overage.\n   - debt is created only when overage_policy=ALLOW_WITH_OVERDRAFT and (remaining + released_reservation) < actual at commit time.\n   - When debt > 0, new reservations MUST be rejected with 409 DEBT_OUTSTANDING (unless explicitly allowed by policy).\n   - debt MUST be repaid via budget funding operations (out-of-scope for this API) before new reservations are permitted.\n   - When budget is added to a scope with debt > 0, debt MUST be repaid first, then remaining funds added to remaining.\n\nOVERDRAFT LIMIT (NORMATIVE):\n   - overdraft_limit defines the target maximum debt for this scope.\n   - If overdraft_limit is absent or amount=0, no overdraft is permitted (behaves as ALLOW_IF_AVAILABLE).\n   - The overdraft_limit is enforced per-commit but is NOT atomic across concurrent commits.\n     Multiple concurrent commits MAY cause cumulative debt to briefly exceed overdraft_limit.\n   - Individual commits are rejected with 409 OVERDRAFT_LIMIT_EXCEEDED only if (current_debt + delta) > overdraft_limit \n     at the moment of that specific commit.\n   - When debt > overdraft_limit after reconciliation (due to concurrent commits), the scope enters an \"over-limit\" state:\n       * The server MUST set is_over_limit=true on the Balance\n       * ALL new reservations against that scope MUST be rejected with 409 OVERDRAFT_LIMIT_EXCEEDED (not DEBT_OUTSTANDING)\n       * Existing active reservations MAY be committed or released normally\n       * The server SHOULD emit alerts/events for operator intervention\n   - Once debt is repaid below overdraft_limit, is_over_limit returns to false and normal reservation operation resumes.",
              "properties": {
                "scope": {
                  "type": "string",
                  "description": "Canonical scope identifier (server-derived), e.g. \"tenant:t1\", \"workflow:run123\""
                },
                "scope_path": {
                  "type": "string",
                  "description": "Canonical hierarchical path (server-derived)"
                },
                "remaining": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": -120000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
                    }
                  }
                },
                "reserved": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "spent": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "debt": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "allocated": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "overdraft_limit": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "is_over_limit": {
                  "type": "boolean",
                  "description": "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."
                }
              }
            }
          },
          "next_cursor": {
            "type": "string",
            "description": "Opaque cursor for next page (if any)."
          },
          "has_more": {
            "type": "boolean",
            "description": "True if next_cursor is present and more results may be available."
          }
        }
      },
      "EventCreateRequest": {
        "type": "object",
        "required": [
          "idempotency_key",
          "subject",
          "action",
          "actual"
        ],
        "additionalProperties": false,
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256
          },
          "subject": {
            "type": "object",
            "description": "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.\nCHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset)\n  SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope\n  paths use \":\" and \"/\" as structural delimiters, so delimiter,\n  whitespace, and control characters have no stable canonical encoding.\n- Servers MAY reject standard-field values outside this pattern with\n  400 INVALID_REQUEST (the reference implementation does). Clients\n  MUST NOT rely on out-of-pattern values being accepted, and portable\n  clients SHOULD restrict themselves to the pattern above.\n\nEXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies\n  (e.g., cost_center/department/project) and policy/reporting.\n- 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.\n",
            "additionalProperties": false,
            "minProperties": 1,
            "anyOf": [
              {
                "required": [
                  "tenant"
                ]
              },
              {
                "required": [
                  "workspace"
                ]
              },
              {
                "required": [
                  "app"
                ]
              },
              {
                "required": [
                  "workflow"
                ]
              },
              {
                "required": [
                  "agent"
                ]
              },
              {
                "required": [
                  "toolset"
                ]
              }
            ],
            "properties": {
              "tenant": {
                "type": "string",
                "maxLength": 128
              },
              "workspace": {
                "type": "string",
                "maxLength": 128
              },
              "app": {
                "type": "string",
                "maxLength": 128
              },
              "workflow": {
                "type": "string",
                "maxLength": 128
              },
              "agent": {
                "type": "string",
                "maxLength": 128
              },
              "toolset": {
                "type": "string",
                "maxLength": 128
              },
              "dimensions": {
                "type": "object",
                "description": "Optional custom dimensions for enterprise taxonomies and policy/reporting.",
                "additionalProperties": {
                  "type": "string",
                  "maxLength": 256
                },
                "maxProperties": 16
              }
            }
          },
          "action": {
            "type": "object",
            "required": [
              "kind",
              "name"
            ],
            "additionalProperties": false,
            "properties": {
              "kind": {
                "type": "string",
                "maxLength": 64,
                "description": "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\n"
              },
              "name": {
                "type": "string",
                "maxLength": 256,
                "description": "Provider/model/tool identifier (e.g., \"openai:gpt-4o-mini\", \"web.search\")"
              },
              "tags": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "type": "string",
                  "maxLength": 64
                },
                "description": "Optional policy tags (e.g., [\"prod\",\"customer-facing\"])"
              }
            }
          },
          "actual": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "overage_policy": {
            "type": "string",
            "description": "How server handles commits where actual > reserved.\nREJECT: reject commit (client must reserve a buffer or over-estimate).\n  Recommended: add 10-20% buffer to estimates when using REJECT.\n  If the action already happened externally, this creates an unaccounted gap.\n\nALLOW_IF_AVAILABLE (default): commit always succeeds when actual > reserved. If remaining budget supports the full delta, charge it atomically across all derived scopes. Otherwise, cap the delta to available remaining (minimum across all affected scopes, floor 0), charge estimate + capped_delta, and set is_over_limit=true on scopes where the full delta could not be covered. This blocks future reservations until reconciled. Never creates debt. Never rejects a commit — the action already happened.\nALLOW_WITH_OVERDRAFT: if remaining budget supports the delta, commit normally. Otherwise, check if (debt + delta) <= overdraft_limit across all affected scopes.\n  If yes: commit succeeds, add delta to debt, remaining can go negative.\n  If no: commit fails with 409 OVERDRAFT_LIMIT_EXCEEDED.\nThis policy ensures the ledger always reflects reality even when budget is exhausted at commit time.\nCONCURRENCY (NORMATIVE):\n  - The overdraft_limit check is performed per-commit and is NOT atomic across concurrent commits to the same scope.\n  - Multiple commits may each individually pass the (debt + delta) <= limit check but collectively cause debt to exceed overdraft_limit.\n  - This is acceptable: all commits succeed (actions already happened), and the scope enters \"over-limit\" state, \n    blocking future reservations until reconciled.\n  - Servers SHOULD use optimistic locking or compare-and-swap on debt updates to ensure individual commit atomicity, \n    but cross-commit atomicity is not required.\n",
            "enum": [
              "REJECT",
              "ALLOW_IF_AVAILABLE",
              "ALLOW_WITH_OVERDRAFT"
            ]
          },
          "metrics": {
            "type": "object",
            "description": "Optional standard metrics for commit/event. All fields are optional. Recommended for consistency across clients. For custom fields, use `custom`.\n",
            "additionalProperties": false,
            "properties": {
              "tokens_input": {
                "type": "integer",
                "minimum": 0,
                "description": "Input tokens consumed"
              },
              "tokens_output": {
                "type": "integer",
                "minimum": 0,
                "description": "Output tokens generated"
              },
              "latency_ms": {
                "type": "integer",
                "minimum": 0,
                "description": "Total operation latency in milliseconds"
              },
              "model_version": {
                "type": "string",
                "maxLength": 128,
                "description": "Actual model/tool version used"
              },
              "custom": {
                "type": "object",
                "additionalProperties": true,
                "description": "Arbitrary additional metrics"
              }
            }
          },
          "client_time_ms": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "Optional client-observed timestamp (ms). This value is advisory only and MUST NOT be used for authorization, expiry,  idempotency or budget enforcement decisions. The server's receipt time governs all budget comparisons and invariants.\n"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "description": "NOTE: Events MUST be applied atomically across derived scopes (same as reservations), or rejected.\n"
      },
      "EventCreateResponse": {
        "type": "object",
        "required": [
          "status",
          "event_id"
        ],
        "additionalProperties": false,
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "APPLIED"
            ]
          },
          "event_id": {
            "type": "string"
          },
          "charged": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "balances": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "scope",
                "scope_path",
                "remaining"
              ],
              "additionalProperties": false,
              "example": {
                "scope": "tenant:acme-corp/workspace:prod",
                "scope_path": "tenant:acme-corp/workspace:prod",
                "remaining": {
                  "unit": "USD_MICROCENTS",
                  "amount": 4577000
                },
                "reserved": {
                  "unit": "USD_MICROCENTS",
                  "amount": 500000
                },
                "spent": {
                  "unit": "USD_MICROCENTS",
                  "amount": 423000
                },
                "allocated": {
                  "unit": "USD_MICROCENTS",
                  "amount": 5500000
                }
              },
              "description": "Ledger state for a single (scope, unit) balance.\nUNIT CONSISTENCY (NORMATIVE):\n  - All Amount fields within a Balance (remaining/reserved/spent/allocated/debt/overdraft_limit), if present, MUST share the same unit.\n  - Servers MUST NOT emit a Balance with mixed units; clients MAY treat such responses as invalid.\n\nLEDGER INVARIANT (NORMATIVE):\n   - If allocated, spent, reserved, and debt are all present, then remaining MUST satisfy: \n     remaining.amount = allocated.amount - spent.amount - reserved.amount - debt.amount (same unit).\n   - Note that remaining.amount can be negative when debt exceeds available budget.\n   - If allocated is absent, remaining is authoritative and clients MUST treat it as opaque (i.e., MUST NOT derive it from other fields).\n   - Servers MUST NOT emit a Balance where the invariant is violated when all fields are present.\n\nDEBT SEMANTICS (NORMATIVE):\n   - debt represents actual consumption that occurred when insufficient budget was available to cover a commit overage.\n   - debt is created only when overage_policy=ALLOW_WITH_OVERDRAFT and (remaining + released_reservation) < actual at commit time.\n   - When debt > 0, new reservations MUST be rejected with 409 DEBT_OUTSTANDING (unless explicitly allowed by policy).\n   - debt MUST be repaid via budget funding operations (out-of-scope for this API) before new reservations are permitted.\n   - When budget is added to a scope with debt > 0, debt MUST be repaid first, then remaining funds added to remaining.\n\nOVERDRAFT LIMIT (NORMATIVE):\n   - overdraft_limit defines the target maximum debt for this scope.\n   - If overdraft_limit is absent or amount=0, no overdraft is permitted (behaves as ALLOW_IF_AVAILABLE).\n   - The overdraft_limit is enforced per-commit but is NOT atomic across concurrent commits.\n     Multiple concurrent commits MAY cause cumulative debt to briefly exceed overdraft_limit.\n   - Individual commits are rejected with 409 OVERDRAFT_LIMIT_EXCEEDED only if (current_debt + delta) > overdraft_limit \n     at the moment of that specific commit.\n   - When debt > overdraft_limit after reconciliation (due to concurrent commits), the scope enters an \"over-limit\" state:\n       * The server MUST set is_over_limit=true on the Balance\n       * ALL new reservations against that scope MUST be rejected with 409 OVERDRAFT_LIMIT_EXCEEDED (not DEBT_OUTSTANDING)\n       * Existing active reservations MAY be committed or released normally\n       * The server SHOULD emit alerts/events for operator intervention\n   - Once debt is repaid below overdraft_limit, is_over_limit returns to false and normal reservation operation resumes.",
              "properties": {
                "scope": {
                  "type": "string",
                  "description": "Canonical scope identifier (server-derived), e.g. \"tenant:t1\", \"workflow:run123\""
                },
                "scope_path": {
                  "type": "string",
                  "description": "Canonical hierarchical path (server-derived)"
                },
                "remaining": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": -120000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
                    }
                  }
                },
                "reserved": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "spent": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "debt": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "allocated": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "overdraft_limit": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "is_over_limit": {
                  "type": "boolean",
                  "description": "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."
                }
              }
            }
          }
        }
      },
      "ReservationSummary": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "reservation_id",
          "status",
          "subject",
          "action",
          "reserved",
          "created_at_ms",
          "expires_at_ms",
          "scope_path",
          "affected_scopes"
        ],
        "properties": {
          "reservation_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "Reservation lifecycle state (v1+ may add additional terminal states).",
            "enum": [
              "ACTIVE",
              "COMMITTED",
              "RELEASED",
              "EXPIRED"
            ]
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256
          },
          "subject": {
            "type": "object",
            "description": "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.\nCHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset)\n  SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope\n  paths use \":\" and \"/\" as structural delimiters, so delimiter,\n  whitespace, and control characters have no stable canonical encoding.\n- Servers MAY reject standard-field values outside this pattern with\n  400 INVALID_REQUEST (the reference implementation does). Clients\n  MUST NOT rely on out-of-pattern values being accepted, and portable\n  clients SHOULD restrict themselves to the pattern above.\n\nEXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies\n  (e.g., cost_center/department/project) and policy/reporting.\n- 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.\n",
            "additionalProperties": false,
            "minProperties": 1,
            "anyOf": [
              {
                "required": [
                  "tenant"
                ]
              },
              {
                "required": [
                  "workspace"
                ]
              },
              {
                "required": [
                  "app"
                ]
              },
              {
                "required": [
                  "workflow"
                ]
              },
              {
                "required": [
                  "agent"
                ]
              },
              {
                "required": [
                  "toolset"
                ]
              }
            ],
            "properties": {
              "tenant": {
                "type": "string",
                "maxLength": 128
              },
              "workspace": {
                "type": "string",
                "maxLength": 128
              },
              "app": {
                "type": "string",
                "maxLength": 128
              },
              "workflow": {
                "type": "string",
                "maxLength": 128
              },
              "agent": {
                "type": "string",
                "maxLength": 128
              },
              "toolset": {
                "type": "string",
                "maxLength": 128
              },
              "dimensions": {
                "type": "object",
                "description": "Optional custom dimensions for enterprise taxonomies and policy/reporting.",
                "additionalProperties": {
                  "type": "string",
                  "maxLength": 256
                },
                "maxProperties": 16
              }
            }
          },
          "action": {
            "type": "object",
            "required": [
              "kind",
              "name"
            ],
            "additionalProperties": false,
            "properties": {
              "kind": {
                "type": "string",
                "maxLength": 64,
                "description": "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\n"
              },
              "name": {
                "type": "string",
                "maxLength": 256,
                "description": "Provider/model/tool identifier (e.g., \"openai:gpt-4o-mini\", \"web.search\")"
              },
              "tags": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "type": "string",
                  "maxLength": 64
                },
                "description": "Optional policy tags (e.g., [\"prod\",\"customer-facing\"])"
              }
            }
          },
          "reserved": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "created_at_ms": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          },
          "expires_at_ms": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          },
          "finalized_at_ms": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "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": {
            "type": "string"
          },
          "affected_scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "committed": {
            "type": "object",
            "required": [
              "unit",
              "amount"
            ],
            "additionalProperties": false,
            "example": {
              "unit": "USD_MICROCENTS",
              "amount": 500000
            },
            "properties": {
              "unit": {
                "type": "string",
                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                "enum": [
                  "USD_MICROCENTS",
                  "TOKENS",
                  "CREDITS",
                  "RISK_POINTS"
                ]
              },
              "amount": {
                "type": "integer",
                "format": "int64",
                "minimum": 0
              }
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "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": {
            "type": "object",
            "additionalProperties": true,
            "description": "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": {
            "type": "object",
            "additionalProperties": false,
            "description": "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.",
            "properties": {
              "reserve": {
                "type": "object",
                "additionalProperties": false,
                "description": "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.\n\nTRANSPORT 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.",
                "required": [
                  "evidence_id",
                  "cycles_evidence_url"
                ],
                "properties": {
                  "evidence_id": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$",
                    "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                  },
                  "cycles_evidence_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "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": {
                "type": "object",
                "additionalProperties": false,
                "description": "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.\n\nTRANSPORT 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.",
                "required": [
                  "evidence_id",
                  "cycles_evidence_url"
                ],
                "properties": {
                  "evidence_id": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$",
                    "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                  },
                  "cycles_evidence_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "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": {
                "type": "object",
                "additionalProperties": false,
                "description": "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.\n\nTRANSPORT 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.",
                "required": [
                  "evidence_id",
                  "cycles_evidence_url"
                ],
                "properties": {
                  "evidence_id": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$",
                    "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                  },
                  "cycles_evidence_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "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`."
                  }
                }
              }
            }
          }
        }
      },
      "ReservationListResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "reservations"
        ],
        "properties": {
          "reservations": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "reservation_id",
                "status",
                "subject",
                "action",
                "reserved",
                "created_at_ms",
                "expires_at_ms",
                "scope_path",
                "affected_scopes"
              ],
              "properties": {
                "reservation_id": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "description": "Reservation lifecycle state (v1+ may add additional terminal states).",
                  "enum": [
                    "ACTIVE",
                    "COMMITTED",
                    "RELEASED",
                    "EXPIRED"
                  ]
                },
                "idempotency_key": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 256
                },
                "subject": {
                  "type": "object",
                  "description": "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.\nCHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset)\n  SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope\n  paths use \":\" and \"/\" as structural delimiters, so delimiter,\n  whitespace, and control characters have no stable canonical encoding.\n- Servers MAY reject standard-field values outside this pattern with\n  400 INVALID_REQUEST (the reference implementation does). Clients\n  MUST NOT rely on out-of-pattern values being accepted, and portable\n  clients SHOULD restrict themselves to the pattern above.\n\nEXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies\n  (e.g., cost_center/department/project) and policy/reporting.\n- 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.\n",
                  "additionalProperties": false,
                  "minProperties": 1,
                  "anyOf": [
                    {
                      "required": [
                        "tenant"
                      ]
                    },
                    {
                      "required": [
                        "workspace"
                      ]
                    },
                    {
                      "required": [
                        "app"
                      ]
                    },
                    {
                      "required": [
                        "workflow"
                      ]
                    },
                    {
                      "required": [
                        "agent"
                      ]
                    },
                    {
                      "required": [
                        "toolset"
                      ]
                    }
                  ],
                  "properties": {
                    "tenant": {
                      "type": "string",
                      "maxLength": 128
                    },
                    "workspace": {
                      "type": "string",
                      "maxLength": 128
                    },
                    "app": {
                      "type": "string",
                      "maxLength": 128
                    },
                    "workflow": {
                      "type": "string",
                      "maxLength": 128
                    },
                    "agent": {
                      "type": "string",
                      "maxLength": 128
                    },
                    "toolset": {
                      "type": "string",
                      "maxLength": 128
                    },
                    "dimensions": {
                      "type": "object",
                      "description": "Optional custom dimensions for enterprise taxonomies and policy/reporting.",
                      "additionalProperties": {
                        "type": "string",
                        "maxLength": 256
                      },
                      "maxProperties": 16
                    }
                  }
                },
                "action": {
                  "type": "object",
                  "required": [
                    "kind",
                    "name"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "kind": {
                      "type": "string",
                      "maxLength": 64,
                      "description": "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\n"
                    },
                    "name": {
                      "type": "string",
                      "maxLength": 256,
                      "description": "Provider/model/tool identifier (e.g., \"openai:gpt-4o-mini\", \"web.search\")"
                    },
                    "tags": {
                      "type": "array",
                      "maxItems": 10,
                      "items": {
                        "type": "string",
                        "maxLength": 64
                      },
                      "description": "Optional policy tags (e.g., [\"prod\",\"customer-facing\"])"
                    }
                  }
                },
                "reserved": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "created_at_ms": {
                  "type": "integer",
                  "format": "int64",
                  "minimum": 0
                },
                "expires_at_ms": {
                  "type": "integer",
                  "format": "int64",
                  "minimum": 0
                },
                "finalized_at_ms": {
                  "type": "integer",
                  "format": "int64",
                  "minimum": 0,
                  "description": "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": {
                  "type": "string"
                },
                "affected_scopes": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "committed": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "metadata": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "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": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "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": {
                  "type": "object",
                  "additionalProperties": false,
                  "description": "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.",
                  "properties": {
                    "reserve": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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": {
            "type": "string",
            "description": "Opaque cursor for next page (if any)."
          },
          "has_more": {
            "type": "boolean",
            "description": "True if next_cursor is present and more results may be available."
          }
        }
      },
      "ReservationExtendRequest": {
        "type": "object",
        "required": [
          "idempotency_key",
          "extend_by_ms"
        ],
        "additionalProperties": false,
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256
          },
          "extend_by_ms": {
            "type": "integer",
            "minimum": 1,
            "maximum": 86400000,
            "description": "Amount of time (ms) to extend the reservation expiry relative to its current expires_at_ms (i.e., not relative to request time). This does NOT change reserved amount. Server MAY clamp the effective extension to policy limits; the returned expires_at_ms is authoritative. Keep-alive clients SHOULD NOT extend by ttl_ms on every sub-TTL beat — because extension is relative to current expires_at_ms, that drifts expiry unboundedly outward; see HEARTBEAT GUIDANCE on the extendReservation operation.\n"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Optional debugging/audit metadata."
          }
        }
      },
      "ReservationExtendResponse": {
        "type": "object",
        "required": [
          "status",
          "expires_at_ms"
        ],
        "additionalProperties": false,
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ACTIVE"
            ],
            "description": "Reservation remains ACTIVE after a successful extension."
          },
          "expires_at_ms": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "New server-authoritative expiry timestamp (ms)."
          },
          "remaining_ttl_ms": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "Volatile observation of remaining reservation lifetime in milliseconds while this response payload is constructed, computed as max(0, expires_at_ms - current authoritative server time) using the same authoritative clock used for reservation expiry decisions. Servers SHOULD emit it on every successful extension response. On a same-key idempotent replay, a server that emits this field MUST recompute it at replay-response construction time and MUST NOT copy its originally stored value, while ALL OTHER fields replay verbatim from the original response. Rationale: a heartbeat retrying a lost extend with the same idempotency key schedules its next beat from the replayed body, and a cached remaining_ttl_ms would be stale by at least the retry delay — scheduling from it would overshoot the real lease. It MAY therefore be 0 even though the original extension succeeded, and MUST be 0 if the reservation is no longer ACTIVE. It MAY conservatively understate current lead if a later, separately keyed extension moved expiry outward. Added in 0.1.25.16 (additive); clients MUST treat it as optional for backward compatibility. When present it is the NORMATIVE input to heartbeat scheduling — see HEARTBEAT GUIDANCE on extendReservation.\n"
          },
          "balances": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "scope",
                "scope_path",
                "remaining"
              ],
              "additionalProperties": false,
              "example": {
                "scope": "tenant:acme-corp/workspace:prod",
                "scope_path": "tenant:acme-corp/workspace:prod",
                "remaining": {
                  "unit": "USD_MICROCENTS",
                  "amount": 4577000
                },
                "reserved": {
                  "unit": "USD_MICROCENTS",
                  "amount": 500000
                },
                "spent": {
                  "unit": "USD_MICROCENTS",
                  "amount": 423000
                },
                "allocated": {
                  "unit": "USD_MICROCENTS",
                  "amount": 5500000
                }
              },
              "description": "Ledger state for a single (scope, unit) balance.\nUNIT CONSISTENCY (NORMATIVE):\n  - All Amount fields within a Balance (remaining/reserved/spent/allocated/debt/overdraft_limit), if present, MUST share the same unit.\n  - Servers MUST NOT emit a Balance with mixed units; clients MAY treat such responses as invalid.\n\nLEDGER INVARIANT (NORMATIVE):\n   - If allocated, spent, reserved, and debt are all present, then remaining MUST satisfy: \n     remaining.amount = allocated.amount - spent.amount - reserved.amount - debt.amount (same unit).\n   - Note that remaining.amount can be negative when debt exceeds available budget.\n   - If allocated is absent, remaining is authoritative and clients MUST treat it as opaque (i.e., MUST NOT derive it from other fields).\n   - Servers MUST NOT emit a Balance where the invariant is violated when all fields are present.\n\nDEBT SEMANTICS (NORMATIVE):\n   - debt represents actual consumption that occurred when insufficient budget was available to cover a commit overage.\n   - debt is created only when overage_policy=ALLOW_WITH_OVERDRAFT and (remaining + released_reservation) < actual at commit time.\n   - When debt > 0, new reservations MUST be rejected with 409 DEBT_OUTSTANDING (unless explicitly allowed by policy).\n   - debt MUST be repaid via budget funding operations (out-of-scope for this API) before new reservations are permitted.\n   - When budget is added to a scope with debt > 0, debt MUST be repaid first, then remaining funds added to remaining.\n\nOVERDRAFT LIMIT (NORMATIVE):\n   - overdraft_limit defines the target maximum debt for this scope.\n   - If overdraft_limit is absent or amount=0, no overdraft is permitted (behaves as ALLOW_IF_AVAILABLE).\n   - The overdraft_limit is enforced per-commit but is NOT atomic across concurrent commits.\n     Multiple concurrent commits MAY cause cumulative debt to briefly exceed overdraft_limit.\n   - Individual commits are rejected with 409 OVERDRAFT_LIMIT_EXCEEDED only if (current_debt + delta) > overdraft_limit \n     at the moment of that specific commit.\n   - When debt > overdraft_limit after reconciliation (due to concurrent commits), the scope enters an \"over-limit\" state:\n       * The server MUST set is_over_limit=true on the Balance\n       * ALL new reservations against that scope MUST be rejected with 409 OVERDRAFT_LIMIT_EXCEEDED (not DEBT_OUTSTANDING)\n       * Existing active reservations MAY be committed or released normally\n       * The server SHOULD emit alerts/events for operator intervention\n   - Once debt is repaid below overdraft_limit, is_over_limit returns to false and normal reservation operation resumes.",
              "properties": {
                "scope": {
                  "type": "string",
                  "description": "Canonical scope identifier (server-derived), e.g. \"tenant:t1\", \"workflow:run123\""
                },
                "scope_path": {
                  "type": "string",
                  "description": "Canonical hierarchical path (server-derived)"
                },
                "remaining": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": -120000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
                    }
                  }
                },
                "reserved": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "spent": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "debt": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "allocated": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "overdraft_limit": {
                  "type": "object",
                  "required": [
                    "unit",
                    "amount"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "unit": "USD_MICROCENTS",
                    "amount": 500000
                  },
                  "properties": {
                    "unit": {
                      "type": "string",
                      "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                      "enum": [
                        "USD_MICROCENTS",
                        "TOKENS",
                        "CREDITS",
                        "RISK_POINTS"
                      ]
                    },
                    "amount": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    }
                  }
                },
                "is_over_limit": {
                  "type": "boolean",
                  "description": "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."
                }
              }
            },
            "description": "Optional updated balances snapshot after extension (nice-to-have)."
          }
        }
      }
    }
  },
  "paths": {
    "/v1/decide": {
      "post": {
        "tags": [
          "Decisions"
        ],
        "operationId": "decide",
        "x-conformance": "normative",
        "summary": "Optional preflight policy decision (no reservation created)",
        "description": "Returns ALLOW / DENY, optionally with Caps for soft landing. This endpoint does not reserve budget. Clients that require concurrency safety MUST use /v1/reservations.\nIDEMPOTENCY (NORMATIVE):  - On replay with the same idempotency_key, the server MUST return the original successful response payload.\nTENANCY (NORMATIVE):  - subject.tenant MUST match the effective tenant derived from auth; otherwise the server MUST return 403 FORBIDDEN.\nDEBT/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.\nCLOSED TENANT (NORMATIVE): - If the owning tenant's status is CLOSED (deployments with a governance plane),\n  a fresh (non-replay) evaluation MUST return decision=DENY with\n  reason_code=TENANT_CLOSED, reflecting as-if-live the 409 TENANT_CLOSED the\n  persisting mutation surface returns (same-key replays of pre-close decisions\n  follow the IDEMPOTENCY rule above and the replayed-decision caveat below).\n  Server MUST NOT return 409 for this condition on /decide. A tenant record whose\n  status cannot be determined (malformed record) MUST fail closed with 500\n  INTERNAL_ERROR — see the closed-tenant binding in ERROR SEMANTICS.\n\nIdempotency 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.",
        "parameters": [
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "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.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "idempotency_key",
                  "subject",
                  "action",
                  "estimate"
                ],
                "additionalProperties": false,
                "properties": {
                  "idempotency_key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "subject": {
                    "type": "object",
                    "description": "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.\nCHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset)\n  SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope\n  paths use \":\" and \"/\" as structural delimiters, so delimiter,\n  whitespace, and control characters have no stable canonical encoding.\n- Servers MAY reject standard-field values outside this pattern with\n  400 INVALID_REQUEST (the reference implementation does). Clients\n  MUST NOT rely on out-of-pattern values being accepted, and portable\n  clients SHOULD restrict themselves to the pattern above.\n\nEXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies\n  (e.g., cost_center/department/project) and policy/reporting.\n- 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.\n",
                    "additionalProperties": false,
                    "minProperties": 1,
                    "anyOf": [
                      {
                        "required": [
                          "tenant"
                        ]
                      },
                      {
                        "required": [
                          "workspace"
                        ]
                      },
                      {
                        "required": [
                          "app"
                        ]
                      },
                      {
                        "required": [
                          "workflow"
                        ]
                      },
                      {
                        "required": [
                          "agent"
                        ]
                      },
                      {
                        "required": [
                          "toolset"
                        ]
                      }
                    ],
                    "properties": {
                      "tenant": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "workspace": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "app": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "workflow": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "agent": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "toolset": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "dimensions": {
                        "type": "object",
                        "description": "Optional custom dimensions for enterprise taxonomies and policy/reporting.",
                        "additionalProperties": {
                          "type": "string",
                          "maxLength": 256
                        },
                        "maxProperties": 16
                      }
                    }
                  },
                  "action": {
                    "type": "object",
                    "required": [
                      "kind",
                      "name"
                    ],
                    "additionalProperties": false,
                    "properties": {
                      "kind": {
                        "type": "string",
                        "maxLength": 64,
                        "description": "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\n"
                      },
                      "name": {
                        "type": "string",
                        "maxLength": 256,
                        "description": "Provider/model/tool identifier (e.g., \"openai:gpt-4o-mini\", \"web.search\")"
                      },
                      "tags": {
                        "type": "array",
                        "maxItems": 10,
                        "items": {
                          "type": "string",
                          "maxLength": 64
                        },
                        "description": "Optional policy tags (e.g., [\"prod\",\"customer-facing\"])"
                      }
                    }
                  },
                  "estimate": {
                    "type": "object",
                    "required": [
                      "unit",
                      "amount"
                    ],
                    "additionalProperties": false,
                    "example": {
                      "unit": "USD_MICROCENTS",
                      "amount": 500000
                    },
                    "properties": {
                      "unit": {
                        "type": "string",
                        "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                        "enum": [
                          "USD_MICROCENTS",
                          "TOKENS",
                          "CREDITS",
                          "RISK_POINTS"
                        ]
                      },
                      "amount": {
                        "type": "integer",
                        "format": "int64",
                        "minimum": 0
                      }
                    }
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decision result",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "X-Cycles-Tenant": {
                "description": "Effective tenant identifier derived from auth context (optional in v0)",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Number of requests remaining in current window (optional in v0)",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) when rate limit resets (optional in v0)",
                "schema": {
                  "type": "integer",
                  "format": "int64"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "decision"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "decision": "ALLOW",
                    "affected_scopes": [
                      "tenant:acme-corp",
                      "tenant:acme-corp/workspace:prod"
                    ]
                  },
                  "properties": {
                    "decision": {
                      "type": "string",
                      "enum": [
                        "ALLOW",
                        "ALLOW_WITH_CAPS",
                        "DENY"
                      ]
                    },
                    "caps": {
                      "type": "object",
                      "description": "Optional soft-landing constraints returned by /decide or reservation ALLOW_WITH_CAPS. v0 intentionally keeps caps simple and concrete (no condition language).\nPRECEDENCE RULES: - If tool_allowlist is non-empty, ONLY those tools are allowed (denylist ignored). - Otherwise, if tool_denylist is non-empty, all tools EXCEPT those are allowed. - If both are empty/null, no tool restrictions apply. - Tool names are case-sensitive and match Action.name exactly.\n",
                      "additionalProperties": false,
                      "properties": {
                        "max_tokens": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "max_steps_remaining": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "tool_allowlist": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "maxLength": 256
                          }
                        },
                        "tool_denylist": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "maxLength": 256
                          }
                        },
                        "cooldown_ms": {
                          "type": "integer",
                          "minimum": 0
                        }
                      }
                    },
                    "reason_code": {
                      "type": "string",
                      "maxLength": 128,
                      "description": "Stable machine-readable reason for why a /decide call or a reservation returned decision=DENY. Populated on DecisionResponse.reason_code and on ReservationCreateResponse.reason_code. The human-readable `message` field on the parent response (when present) may carry additional context.\nEXTENSIBILITY (NORMATIVE):\n  DecisionReasonCode is intentionally an OPEN string (not a closed enum)\n  so that:\n    (a) future minor versions of this base spec can add new values without\n        a breaking change, and\n    (b) companion extension specs (e.g., cycles-protocol-extensions-v0.1.26.yaml)\n        can define additional reason codes without modifying the base schema.\n  Clients MUST gracefully handle unknown values — log them and map to\n  generic DENY handling (i.e., \"the request was denied; treat as a terminal\n  failure even if we don't recognize the specific reason\").\n\nKNOWN VALUES (v0.1.25 base):\n  - BUDGET_EXCEEDED — remaining amount insufficient on at least one derived scope\n    (evaluated against the requested estimate.amount).\n  - BUDGET_FROZEN — a derived scope has a budget in FROZEN status\n    (operator-set, no mutations allowed).\n  - BUDGET_CLOSED — a derived scope has a budget in CLOSED status\n    (permanently closed).\n  - BUDGET_NOT_FOUND — no budget exists at any derived scope in the requested unit.\n    On non-dry reserve and /v1/events paths this same condition surfaces as HTTP\n    404 with error=NOT_FOUND instead.\n  - OVERDRAFT_LIMIT_EXCEEDED — either (a) debt + delta > overdraft_limit on commit,\n    or (b) the scope is in over-limit state (is_over_limit=true) and no new\n    reservations are permitted until reconciled.\n  - DEBT_OUTSTANDING — a derived scope has debt > 0 and overdraft_limit == 0\n    (no policy permits further debt accrual).\n  - TENANT_CLOSED — the owning tenant's status is CLOSED (deployments with a\n    governance plane; added in revision 2026-07-10). Populated on /decide and\n    dry_run=true evaluations; the persisting mutation surface reports the same\n    condition as HTTP 409 error=TENANT_CLOSED instead (see the closed-tenant\n    binding in ERROR SEMANTICS).\n\nKNOWN VALUES (v0.1.26 runtime extension):\n  - ACTION_QUOTA_EXCEEDED — a per-kind or risk-class action quota rule was\n    exceeded for the target scope and window.\n  - ACTION_KIND_DENIED — the action kind is in the matching policy's\n    denied_action_kinds list.\n  - ACTION_KIND_NOT_ALLOWED — the matching policy has a non-empty\n    allowed_action_kinds list and the action kind is not in it.\n  See cycles-protocol-extensions-v0.1.26.yaml for full semantics,\n  evaluation order, and DenyDetail structure (populated alongside\n  reason_code for v0.1.26 denials).\n\nDistinct from the `ErrorCode` enum: reason codes appear only on 200 OK responses with decision=DENY; ErrorCodes appear only on 4xx/5xx responses with an `error` field. Some labels overlap (e.g. BUDGET_EXCEEDED appears in both sets) because the same underlying condition is reported two ways depending on the endpoint: /decide and dry_run surface it as a non-4xx DENY decision, while non-dry reserve surfaces it as a 409 error.\nSDK GENERATION GUIDANCE:\n  Codegen tools SHOULD generate a string type (not a strict enum) and\n  expose the known values above as constants or string literal union\n  members for ergonomic use. SDK code MUST NOT reject unknown values\n  at the deserialization boundary."
                    },
                    "retry_after_ms": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "affected_scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Canonical scope identifiers impacted by this decision, in canonical order."
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/reservations": {
      "post": {
        "tags": [
          "Reservations"
        ],
        "operationId": "createReservation",
        "x-conformance": "normative",
        "summary": "Reserve budget for a planned action (concurrency-safe)",
        "description": "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). \nIf 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.\nDRY-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),\n  dry_run MUST NOT return 409 TENANT_CLOSED; a fresh (non-replay) evaluation MUST\n  return decision=DENY with reason_code=TENANT_CLOSED, reflecting as-if-live the\n  409 the persisting create returns (same-key replays of pre-close evaluations\n  follow the IDEMPOTENCY rule below). A tenant record whose status cannot be determined (malformed record)\n  MUST fail closed with 500 INTERNAL_ERROR even on dry_run — see the closed-tenant\n  binding in ERROR SEMANTICS.\n- balances MAY be populated (recommended for operator visibility), but MUST reflect a non-mutating evaluation.\nOVER-LIMIT BLOCKING (NORMATIVE): - If ANY affected scope has debt > overdraft_limit (is_over_limit=true), the reservation MUST be rejected \n  with 409 OVERDRAFT_LIMIT_EXCEEDED, regardless of available remaining budget.\n- This blocks new work when overdraft reconciliation is needed.\nIDEMPOTENCY (NORMATIVE): - On replay with the same idempotency_key, the server MUST return the original successful response\n  outcome and payload, including the original reservation_id (if any), except that remaining_ttl_ms is a\n  volatile transport observation not included in the attested CyclesEvidence payload. When emitted, it\n  MUST be recomputed from the original expires_at_ms and current authoritative server time while\n  constructing the replay response, MUST NOT be copied from the stored response, and MUST be 0 if the\n  reservation is no longer ACTIVE. All other fields replay verbatim.\n\nTENANCY (NORMATIVE): - subject.tenant MUST match the effective tenant derived from auth; otherwise the server MUST return 403 FORBIDDEN.",
        "parameters": [
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "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.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "idempotency_key",
                  "subject",
                  "action",
                  "estimate"
                ],
                "additionalProperties": false,
                "example": {
                  "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
                },
                "properties": {
                  "idempotency_key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "subject": {
                    "type": "object",
                    "description": "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.\nCHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset)\n  SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope\n  paths use \":\" and \"/\" as structural delimiters, so delimiter,\n  whitespace, and control characters have no stable canonical encoding.\n- Servers MAY reject standard-field values outside this pattern with\n  400 INVALID_REQUEST (the reference implementation does). Clients\n  MUST NOT rely on out-of-pattern values being accepted, and portable\n  clients SHOULD restrict themselves to the pattern above.\n\nEXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies\n  (e.g., cost_center/department/project) and policy/reporting.\n- 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.\n",
                    "additionalProperties": false,
                    "minProperties": 1,
                    "anyOf": [
                      {
                        "required": [
                          "tenant"
                        ]
                      },
                      {
                        "required": [
                          "workspace"
                        ]
                      },
                      {
                        "required": [
                          "app"
                        ]
                      },
                      {
                        "required": [
                          "workflow"
                        ]
                      },
                      {
                        "required": [
                          "agent"
                        ]
                      },
                      {
                        "required": [
                          "toolset"
                        ]
                      }
                    ],
                    "properties": {
                      "tenant": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "workspace": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "app": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "workflow": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "agent": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "toolset": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "dimensions": {
                        "type": "object",
                        "description": "Optional custom dimensions for enterprise taxonomies and policy/reporting.",
                        "additionalProperties": {
                          "type": "string",
                          "maxLength": 256
                        },
                        "maxProperties": 16
                      }
                    }
                  },
                  "action": {
                    "type": "object",
                    "required": [
                      "kind",
                      "name"
                    ],
                    "additionalProperties": false,
                    "properties": {
                      "kind": {
                        "type": "string",
                        "maxLength": 64,
                        "description": "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\n"
                      },
                      "name": {
                        "type": "string",
                        "maxLength": 256,
                        "description": "Provider/model/tool identifier (e.g., \"openai:gpt-4o-mini\", \"web.search\")"
                      },
                      "tags": {
                        "type": "array",
                        "maxItems": 10,
                        "items": {
                          "type": "string",
                          "maxLength": 64
                        },
                        "description": "Optional policy tags (e.g., [\"prod\",\"customer-facing\"])"
                      }
                    }
                  },
                  "estimate": {
                    "type": "object",
                    "required": [
                      "unit",
                      "amount"
                    ],
                    "additionalProperties": false,
                    "example": {
                      "unit": "USD_MICROCENTS",
                      "amount": 500000
                    },
                    "properties": {
                      "unit": {
                        "type": "string",
                        "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                        "enum": [
                          "USD_MICROCENTS",
                          "TOKENS",
                          "CREDITS",
                          "RISK_POINTS"
                        ]
                      },
                      "amount": {
                        "type": "integer",
                        "format": "int64",
                        "minimum": 0
                      }
                    }
                  },
                  "ttl_ms": {
                    "type": "integer",
                    "minimum": 1000,
                    "maximum": 86400000,
                    "default": 60000
                  },
                  "grace_period_ms": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 60000,
                    "default": 5000,
                    "description": "Grace window after TTL for in-flight commits. Default 5s.\nOPERATIONAL GUIDANCE: - For high-latency actions (streaming LLM, slow APIs), consider setting higher (10-30s).\nINTEROP / SAFETY RATIONALE: - The 60s maximum is an interoperability ceiling to limit long-lived \"in-flight\" commit windows and reduce\n  zombie-reservation and abuse surface (grace extends the time that a crashed client can keep budget locked).\n\nLONG-RUNNING WORKFLOWS: - For multi-minute operations, clients SHOULD keep ttl_ms relatively short and use /v1/reservations/{reservation_id}/extend\n  as a heartbeat, and/or use chunked reserve/commit cycles rather than relying on large grace_period_ms.\n"
                  },
                  "overage_policy": {
                    "type": "string",
                    "description": "How server handles commits where actual > reserved.\nREJECT: reject commit (client must reserve a buffer or over-estimate).\n  Recommended: add 10-20% buffer to estimates when using REJECT.\n  If the action already happened externally, this creates an unaccounted gap.\n\nALLOW_IF_AVAILABLE (default): commit always succeeds when actual > reserved. If remaining budget supports the full delta, charge it atomically across all derived scopes. Otherwise, cap the delta to available remaining (minimum across all affected scopes, floor 0), charge estimate + capped_delta, and set is_over_limit=true on scopes where the full delta could not be covered. This blocks future reservations until reconciled. Never creates debt. Never rejects a commit — the action already happened.\nALLOW_WITH_OVERDRAFT: if remaining budget supports the delta, commit normally. Otherwise, check if (debt + delta) <= overdraft_limit across all affected scopes.\n  If yes: commit succeeds, add delta to debt, remaining can go negative.\n  If no: commit fails with 409 OVERDRAFT_LIMIT_EXCEEDED.\nThis policy ensures the ledger always reflects reality even when budget is exhausted at commit time.\nCONCURRENCY (NORMATIVE):\n  - The overdraft_limit check is performed per-commit and is NOT atomic across concurrent commits to the same scope.\n  - Multiple commits may each individually pass the (debt + delta) <= limit check but collectively cause debt to exceed overdraft_limit.\n  - This is acceptable: all commits succeed (actions already happened), and the scope enters \"over-limit\" state, \n    blocking future reservations until reconciled.\n  - Servers SHOULD use optimistic locking or compare-and-swap on debt updates to ensure individual commit atomicity, \n    but cross-commit atomicity is not required.\n",
                    "enum": [
                      "REJECT",
                      "ALLOW_IF_AVAILABLE",
                      "ALLOW_WITH_OVERDRAFT"
                    ]
                  },
                  "dry_run": {
                    "type": "boolean",
                    "default": false,
                    "description": "Shadow-mode evaluation. If true, the server MUST evaluate the reservation request and return decision/caps/affected_scopes as if it would reserve, but MUST NOT modify balances, persist a reservation, or require commit/release. Intended for safe rollout and testing of full reserve-path logic.\n"
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reservation decision (ALLOW/DENY with optional caps)",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "X-Cycles-Tenant": {
                "description": "Effective tenant identifier derived from auth context (optional in v0)",
                "schema": {
                  "type": "string"
                }
              },
              "Date": {
                "description": "Standard HTTP origination timestamp (RFC 9110 §6.6.1, IMF-fixdate per §5.6.7). Declared here — OPTIONAL — purely as documentation of standard HTTP behavior on the reservation responses. It plays NO role in the HEARTBEAT GUIDANCE on extendReservation and MUST NOT be used for lease arithmetic or heartbeat scheduling: Date has whole-second resolution, is a best-effort timestamp that may be generated at any point during message origination or replaced by intermediaries, and a server may derive `expires_at_ms` and Date from DIFFERENT clocks (e.g. a data store's clock vs the HTTP container's), so `expires_at_ms − Date` is not a same-frame measurement.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "decision",
                    "affected_scopes"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "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"
                    ]
                  },
                  "properties": {
                    "decision": {
                      "type": "string",
                      "enum": [
                        "ALLOW",
                        "ALLOW_WITH_CAPS",
                        "DENY"
                      ]
                    },
                    "reservation_id": {
                      "type": "string",
                      "description": "Present if decision is ALLOW or ALLOW_WITH_CAPS and dry_run is false. MUST be absent when dry_run is true."
                    },
                    "reserved": {
                      "type": "object",
                      "required": [
                        "unit",
                        "amount"
                      ],
                      "additionalProperties": false,
                      "example": {
                        "unit": "USD_MICROCENTS",
                        "amount": 500000
                      },
                      "properties": {
                        "unit": {
                          "type": "string",
                          "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                          "enum": [
                            "USD_MICROCENTS",
                            "TOKENS",
                            "CREDITS",
                            "RISK_POINTS"
                          ]
                        },
                        "amount": {
                          "type": "integer",
                          "format": "int64",
                          "minimum": 0
                        }
                      }
                    },
                    "expires_at_ms": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0
                    },
                    "remaining_ttl_ms": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0,
                      "description": "Observation of remaining reservation lifetime in milliseconds while this response payload is constructed, computed as max(0, expires_at_ms - current authoritative server time) using the same authoritative clock used for reservation expiry decisions. Servers SHOULD emit it on successful responses that return a live reservation; it MUST be absent on dry-run and DENY responses. This field is volatile transport metadata and is not part of the attested CyclesEvidence payload. On a same-key idempotent replay, a server that emits it MUST recompute it at replay-response construction time from the original expires_at_ms and MUST NOT copy its originally stored value; it MUST be 0 if the reservation is no longer ACTIVE and MAY conservatively understate current lead if a later, separately keyed extension moved expiry outward. All other fields replay verbatim. Added in 0.1.25.16 (additive); clients MUST treat it as optional for backward compatibility. When present it is the NORMATIVE input to heartbeat scheduling — see HEARTBEAT GUIDANCE on extendReservation.\n"
                    },
                    "scope_path": {
                      "type": "string",
                      "description": "Canonical scope path (server-derived)"
                    },
                    "affected_scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Canonical scope identifiers impacted by this reservation, in canonical order."
                    },
                    "caps": {
                      "type": "object",
                      "description": "Optional soft-landing constraints returned by /decide or reservation ALLOW_WITH_CAPS. v0 intentionally keeps caps simple and concrete (no condition language).\nPRECEDENCE RULES: - If tool_allowlist is non-empty, ONLY those tools are allowed (denylist ignored). - Otherwise, if tool_denylist is non-empty, all tools EXCEPT those are allowed. - If both are empty/null, no tool restrictions apply. - Tool names are case-sensitive and match Action.name exactly.\n",
                      "additionalProperties": false,
                      "properties": {
                        "max_tokens": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "max_steps_remaining": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "tool_allowlist": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "maxLength": 256
                          }
                        },
                        "tool_denylist": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "maxLength": 256
                          }
                        },
                        "cooldown_ms": {
                          "type": "integer",
                          "minimum": 0
                        }
                      }
                    },
                    "balances": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "scope",
                          "scope_path",
                          "remaining"
                        ],
                        "additionalProperties": false,
                        "example": {
                          "scope": "tenant:acme-corp/workspace:prod",
                          "scope_path": "tenant:acme-corp/workspace:prod",
                          "remaining": {
                            "unit": "USD_MICROCENTS",
                            "amount": 4577000
                          },
                          "reserved": {
                            "unit": "USD_MICROCENTS",
                            "amount": 500000
                          },
                          "spent": {
                            "unit": "USD_MICROCENTS",
                            "amount": 423000
                          },
                          "allocated": {
                            "unit": "USD_MICROCENTS",
                            "amount": 5500000
                          }
                        },
                        "description": "Ledger state for a single (scope, unit) balance.\nUNIT CONSISTENCY (NORMATIVE):\n  - All Amount fields within a Balance (remaining/reserved/spent/allocated/debt/overdraft_limit), if present, MUST share the same unit.\n  - Servers MUST NOT emit a Balance with mixed units; clients MAY treat such responses as invalid.\n\nLEDGER INVARIANT (NORMATIVE):\n   - If allocated, spent, reserved, and debt are all present, then remaining MUST satisfy: \n     remaining.amount = allocated.amount - spent.amount - reserved.amount - debt.amount (same unit).\n   - Note that remaining.amount can be negative when debt exceeds available budget.\n   - If allocated is absent, remaining is authoritative and clients MUST treat it as opaque (i.e., MUST NOT derive it from other fields).\n   - Servers MUST NOT emit a Balance where the invariant is violated when all fields are present.\n\nDEBT SEMANTICS (NORMATIVE):\n   - debt represents actual consumption that occurred when insufficient budget was available to cover a commit overage.\n   - debt is created only when overage_policy=ALLOW_WITH_OVERDRAFT and (remaining + released_reservation) < actual at commit time.\n   - When debt > 0, new reservations MUST be rejected with 409 DEBT_OUTSTANDING (unless explicitly allowed by policy).\n   - debt MUST be repaid via budget funding operations (out-of-scope for this API) before new reservations are permitted.\n   - When budget is added to a scope with debt > 0, debt MUST be repaid first, then remaining funds added to remaining.\n\nOVERDRAFT LIMIT (NORMATIVE):\n   - overdraft_limit defines the target maximum debt for this scope.\n   - If overdraft_limit is absent or amount=0, no overdraft is permitted (behaves as ALLOW_IF_AVAILABLE).\n   - The overdraft_limit is enforced per-commit but is NOT atomic across concurrent commits.\n     Multiple concurrent commits MAY cause cumulative debt to briefly exceed overdraft_limit.\n   - Individual commits are rejected with 409 OVERDRAFT_LIMIT_EXCEEDED only if (current_debt + delta) > overdraft_limit \n     at the moment of that specific commit.\n   - When debt > overdraft_limit after reconciliation (due to concurrent commits), the scope enters an \"over-limit\" state:\n       * The server MUST set is_over_limit=true on the Balance\n       * ALL new reservations against that scope MUST be rejected with 409 OVERDRAFT_LIMIT_EXCEEDED (not DEBT_OUTSTANDING)\n       * Existing active reservations MAY be committed or released normally\n       * The server SHOULD emit alerts/events for operator intervention\n   - Once debt is repaid below overdraft_limit, is_over_limit returns to false and normal reservation operation resumes.",
                        "properties": {
                          "scope": {
                            "type": "string",
                            "description": "Canonical scope identifier (server-derived), e.g. \"tenant:t1\", \"workflow:run123\""
                          },
                          "scope_path": {
                            "type": "string",
                            "description": "Canonical hierarchical path (server-derived)"
                          },
                          "remaining": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": -120000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
                              }
                            }
                          },
                          "reserved": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "spent": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "debt": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "allocated": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "overdraft_limit": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "is_over_limit": {
                            "type": "boolean",
                            "description": "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."
                          }
                        }
                      }
                    },
                    "reason_code": {
                      "type": "string",
                      "maxLength": 128,
                      "description": "Stable machine-readable reason for why a /decide call or a reservation returned decision=DENY. Populated on DecisionResponse.reason_code and on ReservationCreateResponse.reason_code. The human-readable `message` field on the parent response (when present) may carry additional context.\nEXTENSIBILITY (NORMATIVE):\n  DecisionReasonCode is intentionally an OPEN string (not a closed enum)\n  so that:\n    (a) future minor versions of this base spec can add new values without\n        a breaking change, and\n    (b) companion extension specs (e.g., cycles-protocol-extensions-v0.1.26.yaml)\n        can define additional reason codes without modifying the base schema.\n  Clients MUST gracefully handle unknown values — log them and map to\n  generic DENY handling (i.e., \"the request was denied; treat as a terminal\n  failure even if we don't recognize the specific reason\").\n\nKNOWN VALUES (v0.1.25 base):\n  - BUDGET_EXCEEDED — remaining amount insufficient on at least one derived scope\n    (evaluated against the requested estimate.amount).\n  - BUDGET_FROZEN — a derived scope has a budget in FROZEN status\n    (operator-set, no mutations allowed).\n  - BUDGET_CLOSED — a derived scope has a budget in CLOSED status\n    (permanently closed).\n  - BUDGET_NOT_FOUND — no budget exists at any derived scope in the requested unit.\n    On non-dry reserve and /v1/events paths this same condition surfaces as HTTP\n    404 with error=NOT_FOUND instead.\n  - OVERDRAFT_LIMIT_EXCEEDED — either (a) debt + delta > overdraft_limit on commit,\n    or (b) the scope is in over-limit state (is_over_limit=true) and no new\n    reservations are permitted until reconciled.\n  - DEBT_OUTSTANDING — a derived scope has debt > 0 and overdraft_limit == 0\n    (no policy permits further debt accrual).\n  - TENANT_CLOSED — the owning tenant's status is CLOSED (deployments with a\n    governance plane; added in revision 2026-07-10). Populated on /decide and\n    dry_run=true evaluations; the persisting mutation surface reports the same\n    condition as HTTP 409 error=TENANT_CLOSED instead (see the closed-tenant\n    binding in ERROR SEMANTICS).\n\nKNOWN VALUES (v0.1.26 runtime extension):\n  - ACTION_QUOTA_EXCEEDED — a per-kind or risk-class action quota rule was\n    exceeded for the target scope and window.\n  - ACTION_KIND_DENIED — the action kind is in the matching policy's\n    denied_action_kinds list.\n  - ACTION_KIND_NOT_ALLOWED — the matching policy has a non-empty\n    allowed_action_kinds list and the action kind is not in it.\n  See cycles-protocol-extensions-v0.1.26.yaml for full semantics,\n  evaluation order, and DenyDetail structure (populated alongside\n  reason_code for v0.1.26 denials).\n\nDistinct from the `ErrorCode` enum: reason codes appear only on 200 OK responses with decision=DENY; ErrorCodes appear only on 4xx/5xx responses with an `error` field. Some labels overlap (e.g. BUDGET_EXCEEDED appears in both sets) because the same underlying condition is reported two ways depending on the endpoint: /decide and dry_run surface it as a non-4xx DENY decision, while non-dry reserve surfaces it as a 409 error.\nSDK GENERATION GUIDANCE:\n  Codegen tools SHOULD generate a string type (not a strict enum) and\n  expose the known values above as constants or string literal union\n  members for ergonomic use. SDK code MUST NOT reject unknown values\n  at the deserialization boundary."
                    },
                    "retry_after_ms": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND — no budget exists at any of the reservation's derived scopes in any unit. The message field carries the specific \"Budget not found for provided scope: ...\" detail. Distinct from 400 UNIT_MISMATCH (a budget exists at the scope but in a different unit).",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "X-Cycles-Tenant": {
                "description": "Effective tenant identifier derived from auth context (optional in v0)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Reservations"
        ],
        "operationId": "listReservations",
        "x-conformance": "normative",
        "summary": "List reservations (optional recovery/debug endpoint)",
        "description": "Lists reservations visible to the effective tenant. This endpoint is OPTIONAL in v0 deployments.\n\nRECOVERY (NORMATIVE):\n  - If a client loses reservation_id, it MAY recover it by querying with idempotency_key and/or subject filters.\n  - If idempotency_key is provided, the server SHOULD return at most one matching reservation (uniqueness is expected per (effective tenant, endpoint, idempotency_key)).\n  - Servers SHOULD support filtering by status=ACTIVE to identify \"stuck\" reservations.\n\nSUBJECT FILTERS (GUIDANCE):\n  - Query parameters tenant/workspace/app/workflow/agent/toolset filter on the canonical Subject fields.\n  - Filtering on Subject.dimensions is out of scope for v0 unless explicitly implemented by the server.\n\nTIME-RANGE FILTERS (NORMATIVE, ADDITIVE):\n  Three independent inclusive time-window filters are available,\n  each bound to a specific timestamp field on the reservation\n  entity. All bounds are ISO 8601 date-time strings. All are\n  additive parameters: servers that don't recognize them MUST\n  ignore without error.\n\n  - `from` / `to` (revision 2026-05-21) — bound on\n    `created_at_ms`. The original window filter; matches\n    the family-wide convention on `listAuditLogs`,\n    `listEvents`, and `listWebhookDeliveries`. Always binds\n    to `created_at_ms` regardless of `sort_by`.\n\n  - `expires_from` / `expires_to` — bound on `expires_at_ms`.\n    Primary use case: locate reservations that have expired\n    (or will expire) within a window — e.g. cleanup sweepers\n    that need to discover abandoned ACTIVE reservations. The\n    field is required on every `ReservationSummary` /\n    `ReservationDetail`, so this filter applies to every\n    row regardless of `status`.\n\n  - `finalized_from` / `finalized_to` — bound on\n    `finalized_at_ms`. The `finalized_at_ms` field is OPTIONAL\n    on `ReservationSummary` / `ReservationDetail` and is\n    populated ONLY on COMMITTED and RELEASED rows (absent on\n    ACTIVE and EXPIRED). Rows where the field is absent MUST\n    be excluded from results when either `finalized_from` or\n    `finalized_to` is supplied — the predicate naturally fails\n    on field-absent rows. Callers who want a window over\n    EXPIRED rows should use `expires_from` / `expires_to`\n    against `expires_at_ms`, which is required on every row.\n\n  Validation (applies to all three pairs):\n  - For each pair, `from > to` MUST return 400 INVALID_REQUEST\n    (e.g., `expires_from > expires_to`).\n  - Either side may be supplied alone (open interval).\n  - Blank-string values for any window bound MUST be treated\n    as unset (NORMATIVE). A client sending\n    `?expires_from=&finalized_to=` MUST be handled identically\n    to one omitting both parameters entirely. This applies to\n    all six bounds (`from`, `to`, `expires_from`, `expires_to`,\n    `finalized_from`, `finalized_to`). Servers MUST NOT 400 on\n    empty-string values despite the `format: date-time`\n    declaration. Rationale: clients commonly emit unconditional\n    query strings whose values come from possibly-unset\n    variables (e.g., `?from=${maybeUnset}&to=${maybeUnset}`),\n    and an unset variable rendering as `\"\"` is the common\n    failure mode; rejecting these as malformed surfaces a\n    cryptic 400 that adds nothing over treating them as unset.\n  - The three pairs combine with AND semantics: a row must\n    satisfy every supplied window predicate to be returned.\n\n  Cursor invalidation: sorted-path cursors fold the supplied\n  window bounds into the canonical filter hash. Reusing a\n  sorted cursor under a different `(from, to, expires_from,\n  expires_to, finalized_from, finalized_to)` tuple returns\n  400 INVALID_REQUEST. Legacy SCAN cursors do not carry filter\n  state; callers paginating without `sort_by` must keep all\n  window bounds stable across pages, matching the legacy\n  path's treatment of every other filter.\n\nTENANCY (NORMATIVE):\n  - Under ApiKeyAuth: the server MUST scope results to the effective\n    tenant derived from auth. If the tenant query parameter is\n    provided, it is validation-only and MUST match the effective\n    tenant; otherwise the server MUST return 403 FORBIDDEN. If\n    tenant is omitted, the effective tenant is used.\n  - Under AdminKeyAuth (added 2026-04-13): the admin caller has\n    no effective tenant, so the tenant query parameter is REQUIRED\n    and used as a FILTER (not validation). Omitting it MUST return\n    400 INVALID_REQUEST with message \"tenant query parameter is\n    required when using admin key authentication\". This matches\n    the existing dual-auth pattern on listBudgets / listPolicies\n    in the governance-admin spec — same single param, semantics\n    keyed on auth type.",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "AdminKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "idempotency_key",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            },
            "description": "Lookup handle to recover the reservation_id from a prior createReservation call."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Reservation lifecycle state (v1+ may add additional terminal states).",
              "enum": [
                "ACTIVE",
                "COMMITTED",
                "RELEASED",
                "EXPIRED"
              ]
            },
            "description": "Filter by reservation status (e.g., ACTIVE)."
          },
          {
            "name": "tenant",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "app",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workflow",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "agent",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "toolset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on reservation creation time. ISO 8601 date-time. When set, the server MUST return only reservations whose `created_at_ms` is greater than or equal to this timestamp. The filter ALWAYS binds to `created_at_ms`, independent of `sort_by`. May be supplied alone (no upper bound) or paired with `to`. Servers MUST reject `from > to` with HTTP 400 INVALID_REQUEST.\nAdditive parameter — servers that don't recognize it MUST ignore without error (additive-parameter guarantee). Matches the `from` / `to` convention on `listAuditLogs`, `listEvents`, and `listWebhookDeliveries` in the governance spec family.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on reservation creation time. ISO 8601 date-time. When set, the server MUST return only reservations whose `created_at_ms` is less than or equal to this timestamp. The filter ALWAYS binds to `created_at_ms`, independent of `sort_by`. May be supplied alone (no lower bound) or paired with `from`. Servers MUST reject `from > to` with HTTP 400 INVALID_REQUEST.\nAdditive parameter — servers that don't recognize it MUST ignore without error.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "expires_from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on reservation expiry time. ISO 8601 date-time. When set, the server MUST return only reservations whose `expires_at_ms` is greater than or equal to this timestamp. The filter ALWAYS binds to `expires_at_ms`, independent of `sort_by` and independent of the `from`/`to` window on `created_at_ms`. May be supplied alone (no upper bound) or paired with `expires_to`. Servers MUST reject `expires_from > expires_to` with HTTP 400 INVALID_REQUEST.\nUse case: cleanup sweepers locating reservations that have expired or will expire within a window. Applies to all rows regardless of `status` since `expires_at_ms` is required.\nAdditive parameter — servers that don't recognize it MUST ignore without error.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "expires_to",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on reservation expiry time. ISO 8601 date-time. When set, the server MUST return only reservations whose `expires_at_ms` is less than or equal to this timestamp. Same binding and open-interval rules as `expires_from`. Servers MUST reject `expires_from > expires_to` with HTTP 400 INVALID_REQUEST.\nAdditive parameter — servers that don't recognize it MUST ignore without error.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "finalized_from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on reservation finalization time. ISO 8601 date-time. When set, the server MUST return only reservations whose `finalized_at_ms` is greater than or equal to this timestamp. The filter ALWAYS binds to `finalized_at_ms`, independent of `sort_by`. May be supplied alone (no upper bound) or paired with `finalized_to`. Servers MUST reject `finalized_from > finalized_to` with HTTP 400 INVALID_REQUEST.\nBehavior on rows without `finalized_at_ms` (NORMATIVE): the field is OPTIONAL on reservation responses 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. Callers who want a window over EXPIRED rows should use `expires_from` / `expires_to` against `expires_at_ms`.\nAdditive parameter — servers that don't recognize it MUST ignore without error.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "finalized_to",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on reservation finalization time. ISO 8601 date-time. When set, the server MUST return only reservations whose `finalized_at_ms` is less than or equal to this timestamp. Same binding, open-interval, and ACTIVE-row-exclusion rules as `finalized_from`. Servers MUST reject `finalized_from > finalized_to` with HTTP 400 INVALID_REQUEST.\nAdditive parameter — servers that don't recognize it MUST ignore without error.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "sort_by",
            "in": "query",
            "required": false,
            "description": "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.",
            "schema": {
              "type": "string",
              "enum": [
                "reservation_id",
                "tenant",
                "scope_path",
                "status",
                "reserved",
                "created_at_ms",
                "expires_at_ms"
              ],
              "default": "created_at_ms"
            }
          },
          {
            "name": "sort_dir",
            "in": "query",
            "required": false,
            "description": "Sort direction. Default descending.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc",
              "description": "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."
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "FIELD PROJECTION (NORMATIVE, ADDITIVE). Comma-separated list of OPTIONAL heavy fields to project onto each `ReservationSummary` in the response. By default the list projection omits the arbitrary-size, possibly-PII metadata maps to keep list payloads lean; a caller that needs them (e.g. an aggregate audit / export view) opts in explicitly. Added in revision 2026-06-19.\nRecognized tokens:\n  - `metadata` — populate `ReservationSummary.metadata`\n    (RESERVE-time metadata) on rows that carry it.\n  - `committed_metadata` — populate\n    `ReservationSummary.committed_metadata` (COMMIT-time\n    metadata) on COMMITTED rows whose commit carried metadata.\n  - `evidence` — populate `ReservationSummary.evidence` (the\n    CyclesEvidence references emitted for this reservation's\n    reserve / commit / release operations) on rows that have\n    recorded evidence. Added in revision 2026-06-22.\n\nSemantics (NORMATIVE):\n  - Unrecognized tokens MUST be ignored without error\n    (forward/backward compatible — a client MAY request a\n    field a given server version does not know, and an\n    additive-parameter-unaware server simply never populates\n    these maps).\n  - Tokens are comma-separated; surrounding whitespace and\n    empty tokens (e.g. a trailing comma, or `include=`) MUST\n    be ignored, never 400.\n  - PROJECTION-ONLY: `include` selects which fields are\n    serialized; it does NOT affect which rows match, their\n    ordering, or pagination. It therefore MUST NOT participate\n    in cursor / canonical-filter-hash binding — a cursor\n    minted under one `include` value remains valid when the\n    next page is fetched under a different (or absent)\n    `include`, and changing `include` mid-pagination MUST NOT\n    return 400. (Contrast the window filters above, which DO\n    bind the cursor.)\n  - `committed` is NOT gated by `include`; it is always\n    projected (see `ReservationSummary.committed`).",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Maximum number of results to return"
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from previous response"
          }
        ],
        "responses": {
          "200": {
            "description": "Reservations list",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "X-Cycles-Tenant": {
                "description": "Effective tenant identifier derived from auth context (optional in v0)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "reservations"
                  ],
                  "properties": {
                    "reservations": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "reservation_id",
                          "status",
                          "subject",
                          "action",
                          "reserved",
                          "created_at_ms",
                          "expires_at_ms",
                          "scope_path",
                          "affected_scopes"
                        ],
                        "properties": {
                          "reservation_id": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "description": "Reservation lifecycle state (v1+ may add additional terminal states).",
                            "enum": [
                              "ACTIVE",
                              "COMMITTED",
                              "RELEASED",
                              "EXPIRED"
                            ]
                          },
                          "idempotency_key": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 256
                          },
                          "subject": {
                            "type": "object",
                            "description": "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.\nCHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset)\n  SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope\n  paths use \":\" and \"/\" as structural delimiters, so delimiter,\n  whitespace, and control characters have no stable canonical encoding.\n- Servers MAY reject standard-field values outside this pattern with\n  400 INVALID_REQUEST (the reference implementation does). Clients\n  MUST NOT rely on out-of-pattern values being accepted, and portable\n  clients SHOULD restrict themselves to the pattern above.\n\nEXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies\n  (e.g., cost_center/department/project) and policy/reporting.\n- 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.\n",
                            "additionalProperties": false,
                            "minProperties": 1,
                            "anyOf": [
                              {
                                "required": [
                                  "tenant"
                                ]
                              },
                              {
                                "required": [
                                  "workspace"
                                ]
                              },
                              {
                                "required": [
                                  "app"
                                ]
                              },
                              {
                                "required": [
                                  "workflow"
                                ]
                              },
                              {
                                "required": [
                                  "agent"
                                ]
                              },
                              {
                                "required": [
                                  "toolset"
                                ]
                              }
                            ],
                            "properties": {
                              "tenant": {
                                "type": "string",
                                "maxLength": 128
                              },
                              "workspace": {
                                "type": "string",
                                "maxLength": 128
                              },
                              "app": {
                                "type": "string",
                                "maxLength": 128
                              },
                              "workflow": {
                                "type": "string",
                                "maxLength": 128
                              },
                              "agent": {
                                "type": "string",
                                "maxLength": 128
                              },
                              "toolset": {
                                "type": "string",
                                "maxLength": 128
                              },
                              "dimensions": {
                                "type": "object",
                                "description": "Optional custom dimensions for enterprise taxonomies and policy/reporting.",
                                "additionalProperties": {
                                  "type": "string",
                                  "maxLength": 256
                                },
                                "maxProperties": 16
                              }
                            }
                          },
                          "action": {
                            "type": "object",
                            "required": [
                              "kind",
                              "name"
                            ],
                            "additionalProperties": false,
                            "properties": {
                              "kind": {
                                "type": "string",
                                "maxLength": 64,
                                "description": "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\n"
                              },
                              "name": {
                                "type": "string",
                                "maxLength": 256,
                                "description": "Provider/model/tool identifier (e.g., \"openai:gpt-4o-mini\", \"web.search\")"
                              },
                              "tags": {
                                "type": "array",
                                "maxItems": 10,
                                "items": {
                                  "type": "string",
                                  "maxLength": 64
                                },
                                "description": "Optional policy tags (e.g., [\"prod\",\"customer-facing\"])"
                              }
                            }
                          },
                          "reserved": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "created_at_ms": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 0
                          },
                          "expires_at_ms": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 0
                          },
                          "finalized_at_ms": {
                            "type": "integer",
                            "format": "int64",
                            "minimum": 0,
                            "description": "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": {
                            "type": "string"
                          },
                          "affected_scopes": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "committed": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "metadata": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "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": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "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": {
                            "type": "object",
                            "additionalProperties": false,
                            "description": "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.",
                            "properties": {
                              "reserve": {
                                "type": "object",
                                "additionalProperties": false,
                                "description": "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.\n\nTRANSPORT 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.",
                                "required": [
                                  "evidence_id",
                                  "cycles_evidence_url"
                                ],
                                "properties": {
                                  "evidence_id": {
                                    "type": "string",
                                    "pattern": "^[0-9a-f]{64}$",
                                    "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                                  },
                                  "cycles_evidence_url": {
                                    "type": "string",
                                    "format": "uri",
                                    "description": "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": {
                                "type": "object",
                                "additionalProperties": false,
                                "description": "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.\n\nTRANSPORT 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.",
                                "required": [
                                  "evidence_id",
                                  "cycles_evidence_url"
                                ],
                                "properties": {
                                  "evidence_id": {
                                    "type": "string",
                                    "pattern": "^[0-9a-f]{64}$",
                                    "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                                  },
                                  "cycles_evidence_url": {
                                    "type": "string",
                                    "format": "uri",
                                    "description": "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": {
                                "type": "object",
                                "additionalProperties": false,
                                "description": "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.\n\nTRANSPORT 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.",
                                "required": [
                                  "evidence_id",
                                  "cycles_evidence_url"
                                ],
                                "properties": {
                                  "evidence_id": {
                                    "type": "string",
                                    "pattern": "^[0-9a-f]{64}$",
                                    "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                                  },
                                  "cycles_evidence_url": {
                                    "type": "string",
                                    "format": "uri",
                                    "description": "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": {
                      "type": "string",
                      "description": "Opaque cursor for next page (if any)."
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "True if next_cursor is present and more results may be available."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/reservations/{reservation_id}": {
      "get": {
        "tags": [
          "Reservations"
        ],
        "operationId": "getReservation",
        "x-conformance": "normative",
        "summary": "Get reservation details (optional, for debugging)",
        "description": "Retrieve current status and details of a reservation by ID. Useful for debugging and monitoring long-running operations.\nEXPIRY (NORMATIVE, revision 2026-07-03):\n  - If the reservation exists but its status is EXPIRED, the server MUST\n    return 410 with error=RESERVATION_EXPIRED (this operation's declared\n    410 response). A reservation that never existed returns 404 NOT_FOUND.\n  - EXPIRED reservations remain discoverable via listReservations, which\n    returns them as normal 200 rows with status=EXPIRED — the 410 applies\n    only to this single-resource GET. This codifies the reference\n    implementation's settled behavior; the pre-revision text enumerated\n    only commit/release/extend in the 410 rule, leaving GET ambiguous.\n\nTENANCY (NORMATIVE):\n  - Under ApiKeyAuth: if the reservation exists but is owned by a\n    different effective tenant, the server MUST return 403 FORBIDDEN.\n  - Under AdminKeyAuth (added 2026-04-13): admin operators can\n    read any reservation regardless of owning tenant; reservation_id\n    already pins the owner so no extra parameter is needed.",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "AdminKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "reservation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reservation details",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "X-Cycles-Tenant": {
                "description": "Effective tenant identifier derived from auth context (optional in v0)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Reservation details returned by GET /v1/reservations/{reservation_id}. Useful for debugging.\n",
                  "required": [
                    "reservation_id",
                    "status",
                    "subject",
                    "action",
                    "reserved",
                    "created_at_ms",
                    "expires_at_ms",
                    "scope_path",
                    "affected_scopes"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "reservation_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "description": "Reservation lifecycle state (v1+ may add additional terminal states).",
                      "enum": [
                        "ACTIVE",
                        "COMMITTED",
                        "RELEASED",
                        "EXPIRED"
                      ]
                    },
                    "idempotency_key": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 256
                    },
                    "subject": {
                      "type": "object",
                      "description": "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.\nCHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset)\n  SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope\n  paths use \":\" and \"/\" as structural delimiters, so delimiter,\n  whitespace, and control characters have no stable canonical encoding.\n- Servers MAY reject standard-field values outside this pattern with\n  400 INVALID_REQUEST (the reference implementation does). Clients\n  MUST NOT rely on out-of-pattern values being accepted, and portable\n  clients SHOULD restrict themselves to the pattern above.\n\nEXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies\n  (e.g., cost_center/department/project) and policy/reporting.\n- 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.\n",
                      "additionalProperties": false,
                      "minProperties": 1,
                      "anyOf": [
                        {
                          "required": [
                            "tenant"
                          ]
                        },
                        {
                          "required": [
                            "workspace"
                          ]
                        },
                        {
                          "required": [
                            "app"
                          ]
                        },
                        {
                          "required": [
                            "workflow"
                          ]
                        },
                        {
                          "required": [
                            "agent"
                          ]
                        },
                        {
                          "required": [
                            "toolset"
                          ]
                        }
                      ],
                      "properties": {
                        "tenant": {
                          "type": "string",
                          "maxLength": 128
                        },
                        "workspace": {
                          "type": "string",
                          "maxLength": 128
                        },
                        "app": {
                          "type": "string",
                          "maxLength": 128
                        },
                        "workflow": {
                          "type": "string",
                          "maxLength": 128
                        },
                        "agent": {
                          "type": "string",
                          "maxLength": 128
                        },
                        "toolset": {
                          "type": "string",
                          "maxLength": 128
                        },
                        "dimensions": {
                          "type": "object",
                          "description": "Optional custom dimensions for enterprise taxonomies and policy/reporting.",
                          "additionalProperties": {
                            "type": "string",
                            "maxLength": 256
                          },
                          "maxProperties": 16
                        }
                      }
                    },
                    "action": {
                      "type": "object",
                      "required": [
                        "kind",
                        "name"
                      ],
                      "additionalProperties": false,
                      "properties": {
                        "kind": {
                          "type": "string",
                          "maxLength": 64,
                          "description": "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\n"
                        },
                        "name": {
                          "type": "string",
                          "maxLength": 256,
                          "description": "Provider/model/tool identifier (e.g., \"openai:gpt-4o-mini\", \"web.search\")"
                        },
                        "tags": {
                          "type": "array",
                          "maxItems": 10,
                          "items": {
                            "type": "string",
                            "maxLength": 64
                          },
                          "description": "Optional policy tags (e.g., [\"prod\",\"customer-facing\"])"
                        }
                      }
                    },
                    "reserved": {
                      "type": "object",
                      "required": [
                        "unit",
                        "amount"
                      ],
                      "additionalProperties": false,
                      "example": {
                        "unit": "USD_MICROCENTS",
                        "amount": 500000
                      },
                      "properties": {
                        "unit": {
                          "type": "string",
                          "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                          "enum": [
                            "USD_MICROCENTS",
                            "TOKENS",
                            "CREDITS",
                            "RISK_POINTS"
                          ]
                        },
                        "amount": {
                          "type": "integer",
                          "format": "int64",
                          "minimum": 0
                        }
                      }
                    },
                    "committed": {
                      "type": "object",
                      "required": [
                        "unit",
                        "amount"
                      ],
                      "additionalProperties": false,
                      "example": {
                        "unit": "USD_MICROCENTS",
                        "amount": 500000
                      },
                      "properties": {
                        "unit": {
                          "type": "string",
                          "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                          "enum": [
                            "USD_MICROCENTS",
                            "TOKENS",
                            "CREDITS",
                            "RISK_POINTS"
                          ]
                        },
                        "amount": {
                          "type": "integer",
                          "format": "int64",
                          "minimum": 0
                        }
                      }
                    },
                    "created_at_ms": {
                      "type": "integer",
                      "format": "int64"
                    },
                    "expires_at_ms": {
                      "type": "integer",
                      "format": "int64"
                    },
                    "finalized_at_ms": {
                      "type": "integer",
                      "format": "int64",
                      "description": "Wall-clock time at which the reservation reached a terminal state. Populated on COMMITTED and RELEASED rows only; absent on ACTIVE and EXPIRED rows. The `finalized_from` / `finalized_to` window filter on `listReservations` (revision 2026-05-22) operates against this same field with the same population semantics."
                    },
                    "scope_path": {
                      "type": "string",
                      "description": "Canonical scope path (server-derived)"
                    },
                    "affected_scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Canonical scope identifiers impacted by this reservation, in canonical order."
                    },
                    "metadata": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "Metadata supplied at RESERVE time (on the create-reservation request), if any. For metadata attached at COMMIT time see `committed_metadata`."
                    },
                    "committed_metadata": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "Metadata supplied on the COMMIT request (`CommitRequest.metadata`), preserved on the reservation record. Present on COMMITTED rows whose commit carried metadata; absent otherwise. Distinct from `metadata`, which is captured at reserve time."
                    },
                    "evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.",
                      "properties": {
                        "reserve": {
                          "type": "object",
                          "additionalProperties": false,
                          "description": "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.\n\nTRANSPORT 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.",
                          "required": [
                            "evidence_id",
                            "cycles_evidence_url"
                          ],
                          "properties": {
                            "evidence_id": {
                              "type": "string",
                              "pattern": "^[0-9a-f]{64}$",
                              "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                            },
                            "cycles_evidence_url": {
                              "type": "string",
                              "format": "uri",
                              "description": "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": {
                          "type": "object",
                          "additionalProperties": false,
                          "description": "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.\n\nTRANSPORT 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.",
                          "required": [
                            "evidence_id",
                            "cycles_evidence_url"
                          ],
                          "properties": {
                            "evidence_id": {
                              "type": "string",
                              "pattern": "^[0-9a-f]{64}$",
                              "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                            },
                            "cycles_evidence_url": {
                              "type": "string",
                              "format": "uri",
                              "description": "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": {
                          "type": "object",
                          "additionalProperties": false,
                          "description": "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.\n\nTRANSPORT 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.",
                          "required": [
                            "evidence_id",
                            "cycles_evidence_url"
                          ],
                          "properties": {
                            "evidence_id": {
                              "type": "string",
                              "pattern": "^[0-9a-f]{64}$",
                              "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                            },
                            "cycles_evidence_url": {
                              "type": "string",
                              "format": "uri",
                              "description": "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`."
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "RESERVATION_EXPIRED — the reservation exists but its status is EXPIRED. See the EXPIRY note in this operation's description (revision 2026-07-03); EXPIRED rows remain visible via listReservations.",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "X-Cycles-Tenant": {
                "description": "Effective tenant identifier derived from auth context (optional in v0)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/reservations/{reservation_id}/commit": {
      "post": {
        "tags": [
          "Reservations"
        ],
        "operationId": "commitReservation",
        "x-conformance": "normative",
        "summary": "Commit actual spend for a reservation (auto-releases delta)",
        "description": "Commits actual spend. If actual < reserved, delta is released automatically. If actual > reserved, behavior is controlled by the reservation's overage_policy.\nIDEMPOTENCY (NORMATIVE): - On replay with the same idempotency_key, the server MUST return the original successful response payload.\nTENANCY (NORMATIVE):  - If the reservation exists but is owned by a different effective tenant, the server MUST return 403 FORBIDDEN.",
        "parameters": [
          {
            "name": "reservation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "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.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "idempotency_key",
                  "actual"
                ],
                "additionalProperties": false,
                "example": {
                  "idempotency_key": "idem_20260412_run42_step1_commit",
                  "actual": {
                    "unit": "USD_MICROCENTS",
                    "amount": 423000
                  },
                  "metrics": {
                    "tokens_input": 1500,
                    "tokens_output": 800,
                    "latency_ms": 2340,
                    "model_version": "claude-sonnet-4-20250514"
                  }
                },
                "properties": {
                  "idempotency_key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "actual": {
                    "type": "object",
                    "required": [
                      "unit",
                      "amount"
                    ],
                    "additionalProperties": false,
                    "example": {
                      "unit": "USD_MICROCENTS",
                      "amount": 500000
                    },
                    "properties": {
                      "unit": {
                        "type": "string",
                        "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                        "enum": [
                          "USD_MICROCENTS",
                          "TOKENS",
                          "CREDITS",
                          "RISK_POINTS"
                        ]
                      },
                      "amount": {
                        "type": "integer",
                        "format": "int64",
                        "minimum": 0
                      }
                    }
                  },
                  "metrics": {
                    "type": "object",
                    "description": "Optional standard metrics for commit/event. All fields are optional. Recommended for consistency across clients. For custom fields, use `custom`.\n",
                    "additionalProperties": false,
                    "properties": {
                      "tokens_input": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Input tokens consumed"
                      },
                      "tokens_output": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Output tokens generated"
                      },
                      "latency_ms": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Total operation latency in milliseconds"
                      },
                      "model_version": {
                        "type": "string",
                        "maxLength": 128,
                        "description": "Actual model/tool version used"
                      },
                      "custom": {
                        "type": "object",
                        "additionalProperties": true,
                        "description": "Arbitrary additional metrics"
                      }
                    }
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Commit succeeded",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "X-Cycles-Tenant": {
                "description": "Effective tenant identifier derived from auth context (optional in v0)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "charged"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "COMMITTED"
                      ]
                    },
                    "charged": {
                      "type": "object",
                      "required": [
                        "unit",
                        "amount"
                      ],
                      "additionalProperties": false,
                      "example": {
                        "unit": "USD_MICROCENTS",
                        "amount": 500000
                      },
                      "properties": {
                        "unit": {
                          "type": "string",
                          "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                          "enum": [
                            "USD_MICROCENTS",
                            "TOKENS",
                            "CREDITS",
                            "RISK_POINTS"
                          ]
                        },
                        "amount": {
                          "type": "integer",
                          "format": "int64",
                          "minimum": 0
                        }
                      }
                    },
                    "released": {
                      "type": "object",
                      "required": [
                        "unit",
                        "amount"
                      ],
                      "additionalProperties": false,
                      "example": {
                        "unit": "USD_MICROCENTS",
                        "amount": 500000
                      },
                      "properties": {
                        "unit": {
                          "type": "string",
                          "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                          "enum": [
                            "USD_MICROCENTS",
                            "TOKENS",
                            "CREDITS",
                            "RISK_POINTS"
                          ]
                        },
                        "amount": {
                          "type": "integer",
                          "format": "int64",
                          "minimum": 0
                        }
                      }
                    },
                    "balances": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "scope",
                          "scope_path",
                          "remaining"
                        ],
                        "additionalProperties": false,
                        "example": {
                          "scope": "tenant:acme-corp/workspace:prod",
                          "scope_path": "tenant:acme-corp/workspace:prod",
                          "remaining": {
                            "unit": "USD_MICROCENTS",
                            "amount": 4577000
                          },
                          "reserved": {
                            "unit": "USD_MICROCENTS",
                            "amount": 500000
                          },
                          "spent": {
                            "unit": "USD_MICROCENTS",
                            "amount": 423000
                          },
                          "allocated": {
                            "unit": "USD_MICROCENTS",
                            "amount": 5500000
                          }
                        },
                        "description": "Ledger state for a single (scope, unit) balance.\nUNIT CONSISTENCY (NORMATIVE):\n  - All Amount fields within a Balance (remaining/reserved/spent/allocated/debt/overdraft_limit), if present, MUST share the same unit.\n  - Servers MUST NOT emit a Balance with mixed units; clients MAY treat such responses as invalid.\n\nLEDGER INVARIANT (NORMATIVE):\n   - If allocated, spent, reserved, and debt are all present, then remaining MUST satisfy: \n     remaining.amount = allocated.amount - spent.amount - reserved.amount - debt.amount (same unit).\n   - Note that remaining.amount can be negative when debt exceeds available budget.\n   - If allocated is absent, remaining is authoritative and clients MUST treat it as opaque (i.e., MUST NOT derive it from other fields).\n   - Servers MUST NOT emit a Balance where the invariant is violated when all fields are present.\n\nDEBT SEMANTICS (NORMATIVE):\n   - debt represents actual consumption that occurred when insufficient budget was available to cover a commit overage.\n   - debt is created only when overage_policy=ALLOW_WITH_OVERDRAFT and (remaining + released_reservation) < actual at commit time.\n   - When debt > 0, new reservations MUST be rejected with 409 DEBT_OUTSTANDING (unless explicitly allowed by policy).\n   - debt MUST be repaid via budget funding operations (out-of-scope for this API) before new reservations are permitted.\n   - When budget is added to a scope with debt > 0, debt MUST be repaid first, then remaining funds added to remaining.\n\nOVERDRAFT LIMIT (NORMATIVE):\n   - overdraft_limit defines the target maximum debt for this scope.\n   - If overdraft_limit is absent or amount=0, no overdraft is permitted (behaves as ALLOW_IF_AVAILABLE).\n   - The overdraft_limit is enforced per-commit but is NOT atomic across concurrent commits.\n     Multiple concurrent commits MAY cause cumulative debt to briefly exceed overdraft_limit.\n   - Individual commits are rejected with 409 OVERDRAFT_LIMIT_EXCEEDED only if (current_debt + delta) > overdraft_limit \n     at the moment of that specific commit.\n   - When debt > overdraft_limit after reconciliation (due to concurrent commits), the scope enters an \"over-limit\" state:\n       * The server MUST set is_over_limit=true on the Balance\n       * ALL new reservations against that scope MUST be rejected with 409 OVERDRAFT_LIMIT_EXCEEDED (not DEBT_OUTSTANDING)\n       * Existing active reservations MAY be committed or released normally\n       * The server SHOULD emit alerts/events for operator intervention\n   - Once debt is repaid below overdraft_limit, is_over_limit returns to false and normal reservation operation resumes.",
                        "properties": {
                          "scope": {
                            "type": "string",
                            "description": "Canonical scope identifier (server-derived), e.g. \"tenant:t1\", \"workflow:run123\""
                          },
                          "scope_path": {
                            "type": "string",
                            "description": "Canonical hierarchical path (server-derived)"
                          },
                          "remaining": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": -120000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
                              }
                            }
                          },
                          "reserved": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "spent": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "debt": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "allocated": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "overdraft_limit": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "is_over_limit": {
                            "type": "boolean",
                            "description": "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": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/reservations/{reservation_id}/release": {
      "post": {
        "tags": [
          "Reservations"
        ],
        "operationId": "releaseReservation",
        "x-conformance": "normative",
        "summary": "Release an unused reservation",
        "description": "Releases reserved amount back to remaining budget.\nIDEMPOTENCY (NORMATIVE): - On replay with the same idempotency_key, the server MUST return the original successful response payload.\nTENANCY (NORMATIVE):\n  - Under ApiKeyAuth: if the reservation exists but is owned by\n    a different effective tenant, the server MUST return 403 FORBIDDEN.\n  - Under AdminKeyAuth (added 2026-04-13): admin operators can\n    release any reservation regardless of owning tenant — the\n    ops use case is \"force-expire a hung reservation\" during\n    incident response. reservation_id pins the owner so no\n    extra parameter is needed.\n\nAUDIT (NORMATIVE, AdminKeyAuth path):\n  - The audit-log entry for an admin-driven release MUST record\n    actor_type=admin_on_behalf_of (existing audit field, value\n    already used on createBudget / createPolicy / updatePolicy\n    in the governance-admin spec). This lets security review\n    distinguish admin-driven releases from tenant self-service\n    without joining to the keys table.\n  - The entry MUST be discoverable via the governance plane's\n    audit-query surface (GET /v1/admin/audit/logs in the\n    governance-admin spec). Writing the entry to a store the\n    governance audit-query endpoint cannot read from does not\n    satisfy this requirement — the operational intent is that\n    admin-driven release actions surface in the same admin-\n    facing audit view that shows governance-plane audit\n    entries. How servers achieve this is an implementation\n    concern and out of scope for the spec.\n  - Callers SHOULD populate the optional `reason` body field\n    with a structured tag (e.g. \"[INCIDENT_FORCE_RELEASE]\")\n    for grep-ability in the audit log.",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "AdminKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "reservation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "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.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "idempotency_key"
                ],
                "additionalProperties": false,
                "properties": {
                  "idempotency_key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 256
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Release succeeded",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "X-Cycles-Tenant": {
                "description": "Effective tenant identifier derived from auth context (optional in v0)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "released"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "RELEASED"
                      ]
                    },
                    "released": {
                      "type": "object",
                      "required": [
                        "unit",
                        "amount"
                      ],
                      "additionalProperties": false,
                      "example": {
                        "unit": "USD_MICROCENTS",
                        "amount": 500000
                      },
                      "properties": {
                        "unit": {
                          "type": "string",
                          "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                          "enum": [
                            "USD_MICROCENTS",
                            "TOKENS",
                            "CREDITS",
                            "RISK_POINTS"
                          ]
                        },
                        "amount": {
                          "type": "integer",
                          "format": "int64",
                          "minimum": 0
                        }
                      }
                    },
                    "balances": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "scope",
                          "scope_path",
                          "remaining"
                        ],
                        "additionalProperties": false,
                        "example": {
                          "scope": "tenant:acme-corp/workspace:prod",
                          "scope_path": "tenant:acme-corp/workspace:prod",
                          "remaining": {
                            "unit": "USD_MICROCENTS",
                            "amount": 4577000
                          },
                          "reserved": {
                            "unit": "USD_MICROCENTS",
                            "amount": 500000
                          },
                          "spent": {
                            "unit": "USD_MICROCENTS",
                            "amount": 423000
                          },
                          "allocated": {
                            "unit": "USD_MICROCENTS",
                            "amount": 5500000
                          }
                        },
                        "description": "Ledger state for a single (scope, unit) balance.\nUNIT CONSISTENCY (NORMATIVE):\n  - All Amount fields within a Balance (remaining/reserved/spent/allocated/debt/overdraft_limit), if present, MUST share the same unit.\n  - Servers MUST NOT emit a Balance with mixed units; clients MAY treat such responses as invalid.\n\nLEDGER INVARIANT (NORMATIVE):\n   - If allocated, spent, reserved, and debt are all present, then remaining MUST satisfy: \n     remaining.amount = allocated.amount - spent.amount - reserved.amount - debt.amount (same unit).\n   - Note that remaining.amount can be negative when debt exceeds available budget.\n   - If allocated is absent, remaining is authoritative and clients MUST treat it as opaque (i.e., MUST NOT derive it from other fields).\n   - Servers MUST NOT emit a Balance where the invariant is violated when all fields are present.\n\nDEBT SEMANTICS (NORMATIVE):\n   - debt represents actual consumption that occurred when insufficient budget was available to cover a commit overage.\n   - debt is created only when overage_policy=ALLOW_WITH_OVERDRAFT and (remaining + released_reservation) < actual at commit time.\n   - When debt > 0, new reservations MUST be rejected with 409 DEBT_OUTSTANDING (unless explicitly allowed by policy).\n   - debt MUST be repaid via budget funding operations (out-of-scope for this API) before new reservations are permitted.\n   - When budget is added to a scope with debt > 0, debt MUST be repaid first, then remaining funds added to remaining.\n\nOVERDRAFT LIMIT (NORMATIVE):\n   - overdraft_limit defines the target maximum debt for this scope.\n   - If overdraft_limit is absent or amount=0, no overdraft is permitted (behaves as ALLOW_IF_AVAILABLE).\n   - The overdraft_limit is enforced per-commit but is NOT atomic across concurrent commits.\n     Multiple concurrent commits MAY cause cumulative debt to briefly exceed overdraft_limit.\n   - Individual commits are rejected with 409 OVERDRAFT_LIMIT_EXCEEDED only if (current_debt + delta) > overdraft_limit \n     at the moment of that specific commit.\n   - When debt > overdraft_limit after reconciliation (due to concurrent commits), the scope enters an \"over-limit\" state:\n       * The server MUST set is_over_limit=true on the Balance\n       * ALL new reservations against that scope MUST be rejected with 409 OVERDRAFT_LIMIT_EXCEEDED (not DEBT_OUTSTANDING)\n       * Existing active reservations MAY be committed or released normally\n       * The server SHOULD emit alerts/events for operator intervention\n   - Once debt is repaid below overdraft_limit, is_over_limit returns to false and normal reservation operation resumes.",
                        "properties": {
                          "scope": {
                            "type": "string",
                            "description": "Canonical scope identifier (server-derived), e.g. \"tenant:t1\", \"workflow:run123\""
                          },
                          "scope_path": {
                            "type": "string",
                            "description": "Canonical hierarchical path (server-derived)"
                          },
                          "remaining": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": -120000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
                              }
                            }
                          },
                          "reserved": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "spent": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "debt": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "allocated": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "overdraft_limit": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "is_over_limit": {
                            "type": "boolean",
                            "description": "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": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/reservations/{reservation_id}/extend": {
      "post": {
        "tags": [
          "Reservations"
        ],
        "operationId": "extendReservation",
        "x-conformance": "normative",
        "summary": "Extend reservation TTL (lease refresh / heartbeat)",
        "description": "Extends the expiry of an ACTIVE reservation to support long-running agent workflows.\nSEMANTICS (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\n  (i.e., server time ≤ expires_at_ms). If the reservation is expired, server MUST return 410 with error=RESERVATION_EXPIRED.\n\nIDEMPOTENCY (NORMATIVE): - On replay with the same idempotency_key, the server MUST return the original successful response outcome\n  and payload — all fields VERBATIM — except that remaining_ttl_ms is a volatile observation and, when\n  emitted, MUST be recomputed from the original expires_at_ms and current authoritative server time while\n  constructing the replay response. It MUST NOT be copied from the originally stored response and MUST be\n  0 if the reservation is no longer ACTIVE. (Rationale: a heartbeat retrying a lost response with the same\n  idempotency key schedules from the replayed body; a cached value is stale by the retry delay.)\n\nTENANCY (NORMATIVE): - If the reservation exists but is owned by a different effective tenant, the server MUST return 403 FORBIDDEN.\nERROR SEMANTICS (NORMATIVE): - If the owning tenant's status is CLOSED (deployments with a governance plane),\n  server MUST return 409 with error=TENANT_CLOSED, taking precedence over the\n  reservation-state errors below for non-replay requests — see the closed-tenant\n  binding in this document's top-level ERROR SEMANTICS.\n- 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.\nHEARTBEAT GUIDANCE: - extend_by_ms is RELATIVE to the reservation's current expires_at_ms, not to request time. A keep-alive\n  client that extends by ttl_ms on every sub-TTL beat (e.g. every ttl/2) therefore drifts expiry outward\n  by ttl/2 per beat — an effectively unbounded zombie-reservation window (bounded only by a server's\n  extension limits) that a crashed client can leave holding budget, working against the same\n  zombie-reservation rationale as the grace_period_ms ceiling — and it consumes any server-side\n  extension budget (MAX_EXTENSIONS_EXCEEDED) twice as fast as necessary.\n- Servers MAY constrain extensions in two DISTINCT ways with different client-visible\n  signatures. (1) Per-extend GRANT clamping: the granted TTL is capped below the requested\n  ttl_ms (e.g. a max_reservation_ttl_ms ceiling in deployments with a governance plane: a 24h\n  request may be silently capped to 1h — or to 1s). (2) MAXIMUM-LEAD clamping: the effective\n  new expiry is clamped to at most a policy lead L beyond server time, so the server holds\n  expiry ≈ now + L no matter what is requested. The returned expires_at_ms is AUTHORITATIVE in\n  both cases. This revision adds the OPTIONAL remaining_ttl_ms field to both\n  ReservationCreateResponse and ReservationExtendResponse precisely because, when it is\n  absent, a client can compute neither its true initial lead nor which clamp regime it faces\n  (see the FALLBACK limits below).\n- PRIMARY ALGORITHM — NORMATIVE whenever the response carries remaining_ttl_ms (servers\n  SHOULD emit it; clients MUST treat it as optional for backward compatibility). On EVERY\n  successful create or extend response, recompute the schedule from that response alone —\n  never accumulate expiry differences:\n  Clients MUST record monotonic attempt_sent immediately before each HTTP attempt and\n  response_received after its complete, schema-valid response body is received.\n  rtt = monotonic(response_received) − monotonic(attempt_sent);\n  lead_floor = max(0, remaining_ttl_ms − rtt);\n  request_timeout_budget = the client's enforced finite upper bound for one complete extend attempt\n  (connect, write, read, and failure detection);\n  attempt_budget = max(request_timeout_budget, 1000 ms, 2 × maximum observed rtt);\n  safety_margin = max(1000 ms, 2 × maximum observed rtt);\n  retry_reserve = 2 × attempt_budget + safety_margin;\n  next_delay = max(0, lead_floor − retry_reserve);\n  All duration arithmetic MUST use milliseconds and MUST be overflow-safe; positive overflow\n  is treated as positive infinity, never wraparound. When clock or timer resolution requires\n  rounding, clients MUST round attempt budgets and safety margins up, and lead lower bounds\n  and scheduling/retry delays down, so rounding cannot consume the reserved margin.\n  schedule the next extension next_delay after response receipt. retry_reserve covers one\n  failed attempt, one same-key retry, and scheduling/network margin; when the lease is too\n  short to hold that reserve, next_delay is 0 rather than an unsafe positive delay. A zero\n  delay starts a best-effort extension immediately; it does not by itself prove that a\n  recovery retry will still fit if that attempt consumes its full budget.\n  A positive next_delay is possible only when lead_floor > retry_reserve. Clients SHOULD\n  therefore configure both timeout and expected-network-latency bounds so\n  attempt_budget < (minimum expected lead_floor − safety_margin) / 2; in particular, the\n  enforced per-extend request_timeout_budget SHOULD satisfy that inequality whenever it\n  dominates attempt_budget.\n  For example, with a returned lead near the default requested ttl_ms of 60000 ms, a\n  10000 ms request timeout and 1500 ms maximum observed rtt give attempt_budget = 10000 ms,\n  safety_margin = 3000 ms, retry_reserve = 23000 ms, and next_delay ≈ 37000 ms. A 30000 ms\n  timeout instead makes retry_reserve at least 61000 ms, so a 60000 ms lead produces\n  next_delay = 0. An additive-delta server MAY establish positive lead after the immediate\n  extension, but a server clamped to a 60000 ms maximum lead cannot; client timeout defaults\n  SHOULD be chosen accordingly.\n  If attempt timing is unavailable or unreliable, the client MUST set lead_floor and\n  next_delay to 0. If the request timeout is unknown or unbounded, treat attempt_budget as\n  positive infinity, which likewise makes next_delay 0. Unknown timing MUST NOT be treated as\n  zero elapsed time. A client without reliable monotonic attempt timing therefore cannot\n  establish a safe primary-path delay: the two-consecutive-zero-delay guard below permits at\n  most one immediate fresh extension after a successful response and then requires the client\n  to stop. It MUST NOT switch to the fieldless fallback merely because its local timing is\n  unavailable. The first beat derives from the CREATE response's\n  remaining_ttl_ms in exactly the same way, so no immediate priming is needed when a positive\n  safe delay can be established. Extend by the requested ttl_ms; the returned expires_at_ms\n  and remaining_ttl_ms are authoritative.\n  Replay awareness: same-key CREATE and EXTEND retries are safe to schedule from because the\n  server recomputes remaining_ttl_ms at replay-response construction time (see IDEMPOTENCY\n  above). The client still measures rtt for the individual HTTP attempt that produced the\n  response; it MUST NOT substitute timing from an earlier attempt.\n  Only a schema-valid HTTP 200 ReservationCreateResponse (for the initial create) or\n  ReservationExtendResponse (for an extension) counts as an observed success. A different or\n  malformed 2xx response is ambiguous and MUST NOT be used to schedule from stale state.\n  This response-success predicate applies on BOTH the primary and fallback paths; the fallback\n  changes only how the next delay is chosen after a valid success, not what proves success.\n  After a CREATE timeout, connection error, 5xx, or ambiguous 2xx, where no valid lead_floor\n  exists, the client SHOULD make one immediate retry with the SAME idempotency_key and then\n  stop and surface the ambiguity if that retry also fails to produce a schema-valid result.\n  On an EXTEND transient failure (timeout, connection error, 5xx, 429 LIMIT_EXCEEDED, or an\n  ambiguous 2xx), recompute current_lead_estimate = max(0, last lead_floor − monotonic elapsed\n  since the schema-valid response that established last lead_floor was received). Define\n  retry_window = current_lead_estimate − attempt_budget − safety_margin, WITHOUT clamping.\n  If retry_window < 0, no complete retry plus margin is provably safe: the client MUST NOT\n  retry and MUST stop and surface the failure. Otherwise, for timeout, connection error, 5xx,\n  and ambiguous 2xx, retry with the SAME idempotency_key after\n  retry_delay = min(30000 ms, current_lead_estimate/4, retry_window). For 429, first convert\n  the non-negative Retry-After seconds to retry_after_ms = 1000 × Retry-After using\n  overflow-safe arithmetic; retry_delay equals retry_after_ms only when retry_after_ms ≤\n  retry_window. If Retry-After is missing, invalid, overflows, or exceeds retry_window after\n  conversion, the client MUST NOT invent an earlier retry that violates throttling and SHOULD\n  stop and surface that the lease cannot be safely renewed. The retry_window cap leaves one\n  complete retry plus margin; retry_window = 0 means retry immediately, while a negative value\n  means stop. After EVERY failed or ambiguous recovery attempt, the client MUST recompute\n  current_lead_estimate and retry_window from the same last schema-valid response and the new\n  monotonic elapsed time before deciding again. It MAY continue recovery with the SAME\n  idempotency_key while the freshly recomputed retry_window is positive; elapsed time from all\n  prior attempts has already been deducted, and each decision reserves one complete\n  attempt_budget plus safety_margin for the prospective retry. If retry_window = 0, the client\n  MAY make one immediate recovery retry; if that retry also fails or is ambiguous before an\n  intervening success, it MUST stop even if coarse clock resolution still reports zero. The\n  client MUST also stop if neither monotonic elapsed nor retry_window decreases between\n  consecutive failures, preventing a zero-time recovery loop.\n  On any other 4xx response, the client SHOULD stop and surface the request or authorization\n  failure; it MUST NOT automatically rotate the idempotency key and retry an unchanged request.\n  A client MUST NOT enter an unbounded zero-delay loop. When a schema-valid success produces\n  next_delay = 0, it MAY make one immediate fresh extension attempt with a new idempotency_key.\n  If that success also produces next_delay = 0, the client MUST stop and surface that the lease\n  is shorter than its retry-safety budget; this still permits an additive-delta server's first\n  immediate extension to establish a larger positive lead without burning a maximum-lead\n  server's extension budget in a tight loop.\n- FALLBACK (NON-NORMATIVE) — only for servers that do not emit remaining_ttl_ms. A\n  measured-grant scheme, with its limits stated honestly below; no refinement of it removes\n  them.\n  Maintain a conservative LOWER BOUND on lead from same-frame differences (no cross-clock\n  arithmetic anywhere): lead_min = (sum of measured grants) −\n  monotonic_elapsed_since_reserve_response, starting at 0 at the reserve response, where each\n  measured grant is the difference between successive returned expires_at_ms values (the SAME\n  server frame, so differencing them is legitimate); lead_min never overstates the true lead.\n  The FIRST extension MAY fire immediately — as a TRADEOFF, not a free action. No\n  create-response field bounds the effective initial lease, so immediate priming minimizes\n  the lapse risk of an unknown small initial lease (a 24h request silently capped to 1s is\n  long expired when a 30s first beat arrives). But under maximum-lead clamping the value of\n  an extension is SCHEDULE-DEPENDENT: with a 60s maximum lead, an immediate extend measures a\n  grant ≈ 0 where the same extend fired 30s later would have measured ≈ 30s — so total\n  protected runtime and the sum of grants are NOT schedule-invariant, and immediate priming\n  can spend one extension for zero added lifetime. (An earlier revision of this guidance\n  claimed \"nothing is lost but the count\"; that claim is false under maximum-lead clamping.)\n  Cadence heuristic: compare each measured grant to the monotonic elapsed time since the\n  previous successful extension. Treat grant ≥ ~0.9×requested ttl_ms as the normal regime and\n  beat at ~grant/2 (bounded sensibly, e.g. no shorter than 1s) — against a per-extend-DELTA\n  clamp a smaller grant is a genuinely smaller lease, so tightening is correct. Treat\n  grant ≤ 0, or a grant inside a band around elapsed (e.g. 0.75×elapsed ≤ grant ≤\n  1.25×elapsed), as maximum-lead clamping: hold a fixed bounded cadence (e.g. min(requested\n  ttl_ms/2, 30s)), do not tighten toward a floor, surface that the extension budget (default\n  10) will deplete, and rely on MAX_EXTENSIONS_EXCEEDED as the terminal signal. Skip a beat\n  when lead_min ≥ 1.5×last_grant.\n- LIMITS of the fallback, stated plainly:\n  (a) It is only sound against servers that clamp the per-extend DELTA; under maximum-lead\n  clamping successive expires_at_ms differences measure the elapsed time BETWEEN calls, not\n  the lease size, so every grant-derived signal merely echoes the client's own call spacing.\n  (b) Regime detection from the observables (grant, elapsed) is UNDECIDABLE in general — the\n  band heuristic above is best-effort, not sound. Counterexample: requested ttl 24s,\n  per-extend grant cap 10s. After a lead_min skip the grant is measured across a doubled gap\n  (ratio ≈ 1, inside the band), the client falls back to the held cadence min(24s/2, 30s) =\n  12s, and at that cadence the ratio is 10/12 ≈ 0.833 — inside [0.75, 1.25] FOREVER while\n  the lease erodes 2s per cycle to a lapse. Any true per-extend grant in\n  [0.75×min(requested ttl_ms/2, 30s), 0.9×requested ttl_ms) stays misclassified permanently\n  once the held cadence is adopted; and grant ≥ 0.9×requested ttl_ms does not prove the\n  normal regime either — a maximum-lead clamp echoes ANY call spacing, including one that\n  approaches the requested ttl.\n  (c) Consequently NO portable, safe, extension-efficient heartbeat exists when servers may\n  both cap the initial TTL and clamp extensions to a maximum lead without returning the\n  remaining lifetime. That is WHY remaining_ttl_ms exists. Fallback clients SHOULD prefer\n  over-beating (bounded cadence, budget burn, MAX_EXTENSIONS_EXCEEDED as the stop) over\n  risking lease lapse.\n  The primary and fallback prescriptions are intentionally asymmetric. A field-bearing client\n  with reliable timing has a conservative lead lower bound and stops when it can prove that no\n  complete attempt plus margin fits. A fieldless client cannot make that proof in either\n  direction, so bounded over-beating is a best-effort choice that favors avoiding an\n  unobservable lapse at the acknowledged cost of extension-budget burn.\n- A blind alternate-beat cadence (extend on every SECOND beat WITHOUT lead tracking) leaves zero\n  margin after any single failed beat — at steady state the retry lands at the expiry instant — and\n  cannot keep up when the beat interval exceeds ttl/2.\n- On both paths, whenever a failed or ambiguous attempt is retried, the client MUST use the\n  SAME idempotency_key so a lost-response extend is not applied twice. Before every additional\n  primary-path recovery, the client MUST apply the freshly recomputed retry_window and progress\n  guards above.\n- On RESERVATION_EXPIRED, RESERVATION_FINALIZED, MAX_EXTENSIONS_EXCEEDED, TENANT_CLOSED (tenant\n  closure is irreversible), or NOT_FOUND the heartbeat SHOULD stop — these conditions are permanent\n  for the reservation.",
        "parameters": [
          {
            "name": "reservation_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "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.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "idempotency_key",
                  "extend_by_ms"
                ],
                "additionalProperties": false,
                "properties": {
                  "idempotency_key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "extend_by_ms": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 86400000,
                    "description": "Amount of time (ms) to extend the reservation expiry relative to its current expires_at_ms (i.e., not relative to request time). This does NOT change reserved amount. Server MAY clamp the effective extension to policy limits; the returned expires_at_ms is authoritative. Keep-alive clients SHOULD NOT extend by ttl_ms on every sub-TTL beat — because extension is relative to current expires_at_ms, that drifts expiry unboundedly outward; see HEARTBEAT GUIDANCE on the extendReservation operation.\n"
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Optional debugging/audit metadata."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reservation expiry extended",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "X-Cycles-Tenant": {
                "description": "Effective tenant identifier derived from auth context (optional in v0)",
                "schema": {
                  "type": "string"
                }
              },
              "Date": {
                "description": "Standard HTTP origination timestamp (RFC 9110 §6.6.1, IMF-fixdate per §5.6.7). Declared here — OPTIONAL — purely as documentation of standard HTTP behavior on the reservation responses. It plays NO role in the HEARTBEAT GUIDANCE on extendReservation and MUST NOT be used for lease arithmetic or heartbeat scheduling: Date has whole-second resolution, is a best-effort timestamp that may be generated at any point during message origination or replaced by intermediaries, and a server may derive `expires_at_ms` and Date from DIFFERENT clocks (e.g. a data store's clock vs the HTTP container's), so `expires_at_ms − Date` is not a same-frame measurement.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "expires_at_ms"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ACTIVE"
                      ],
                      "description": "Reservation remains ACTIVE after a successful extension."
                    },
                    "expires_at_ms": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0,
                      "description": "New server-authoritative expiry timestamp (ms)."
                    },
                    "remaining_ttl_ms": {
                      "type": "integer",
                      "format": "int64",
                      "minimum": 0,
                      "description": "Volatile observation of remaining reservation lifetime in milliseconds while this response payload is constructed, computed as max(0, expires_at_ms - current authoritative server time) using the same authoritative clock used for reservation expiry decisions. Servers SHOULD emit it on every successful extension response. On a same-key idempotent replay, a server that emits this field MUST recompute it at replay-response construction time and MUST NOT copy its originally stored value, while ALL OTHER fields replay verbatim from the original response. Rationale: a heartbeat retrying a lost extend with the same idempotency key schedules its next beat from the replayed body, and a cached remaining_ttl_ms would be stale by at least the retry delay — scheduling from it would overshoot the real lease. It MAY therefore be 0 even though the original extension succeeded, and MUST be 0 if the reservation is no longer ACTIVE. It MAY conservatively understate current lead if a later, separately keyed extension moved expiry outward. Added in 0.1.25.16 (additive); clients MUST treat it as optional for backward compatibility. When present it is the NORMATIVE input to heartbeat scheduling — see HEARTBEAT GUIDANCE on extendReservation.\n"
                    },
                    "balances": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "scope",
                          "scope_path",
                          "remaining"
                        ],
                        "additionalProperties": false,
                        "example": {
                          "scope": "tenant:acme-corp/workspace:prod",
                          "scope_path": "tenant:acme-corp/workspace:prod",
                          "remaining": {
                            "unit": "USD_MICROCENTS",
                            "amount": 4577000
                          },
                          "reserved": {
                            "unit": "USD_MICROCENTS",
                            "amount": 500000
                          },
                          "spent": {
                            "unit": "USD_MICROCENTS",
                            "amount": 423000
                          },
                          "allocated": {
                            "unit": "USD_MICROCENTS",
                            "amount": 5500000
                          }
                        },
                        "description": "Ledger state for a single (scope, unit) balance.\nUNIT CONSISTENCY (NORMATIVE):\n  - All Amount fields within a Balance (remaining/reserved/spent/allocated/debt/overdraft_limit), if present, MUST share the same unit.\n  - Servers MUST NOT emit a Balance with mixed units; clients MAY treat such responses as invalid.\n\nLEDGER INVARIANT (NORMATIVE):\n   - If allocated, spent, reserved, and debt are all present, then remaining MUST satisfy: \n     remaining.amount = allocated.amount - spent.amount - reserved.amount - debt.amount (same unit).\n   - Note that remaining.amount can be negative when debt exceeds available budget.\n   - If allocated is absent, remaining is authoritative and clients MUST treat it as opaque (i.e., MUST NOT derive it from other fields).\n   - Servers MUST NOT emit a Balance where the invariant is violated when all fields are present.\n\nDEBT SEMANTICS (NORMATIVE):\n   - debt represents actual consumption that occurred when insufficient budget was available to cover a commit overage.\n   - debt is created only when overage_policy=ALLOW_WITH_OVERDRAFT and (remaining + released_reservation) < actual at commit time.\n   - When debt > 0, new reservations MUST be rejected with 409 DEBT_OUTSTANDING (unless explicitly allowed by policy).\n   - debt MUST be repaid via budget funding operations (out-of-scope for this API) before new reservations are permitted.\n   - When budget is added to a scope with debt > 0, debt MUST be repaid first, then remaining funds added to remaining.\n\nOVERDRAFT LIMIT (NORMATIVE):\n   - overdraft_limit defines the target maximum debt for this scope.\n   - If overdraft_limit is absent or amount=0, no overdraft is permitted (behaves as ALLOW_IF_AVAILABLE).\n   - The overdraft_limit is enforced per-commit but is NOT atomic across concurrent commits.\n     Multiple concurrent commits MAY cause cumulative debt to briefly exceed overdraft_limit.\n   - Individual commits are rejected with 409 OVERDRAFT_LIMIT_EXCEEDED only if (current_debt + delta) > overdraft_limit \n     at the moment of that specific commit.\n   - When debt > overdraft_limit after reconciliation (due to concurrent commits), the scope enters an \"over-limit\" state:\n       * The server MUST set is_over_limit=true on the Balance\n       * ALL new reservations against that scope MUST be rejected with 409 OVERDRAFT_LIMIT_EXCEEDED (not DEBT_OUTSTANDING)\n       * Existing active reservations MAY be committed or released normally\n       * The server SHOULD emit alerts/events for operator intervention\n   - Once debt is repaid below overdraft_limit, is_over_limit returns to false and normal reservation operation resumes.",
                        "properties": {
                          "scope": {
                            "type": "string",
                            "description": "Canonical scope identifier (server-derived), e.g. \"tenant:t1\", \"workflow:run123\""
                          },
                          "scope_path": {
                            "type": "string",
                            "description": "Canonical hierarchical path (server-derived)"
                          },
                          "remaining": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": -120000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
                              }
                            }
                          },
                          "reserved": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "spent": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "debt": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "allocated": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "overdraft_limit": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "is_over_limit": {
                            "type": "boolean",
                            "description": "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."
                          }
                        }
                      },
                      "description": "Optional updated balances snapshot after extension (nice-to-have)."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; retry only after the indicated delay if the reservation can remain live.",
            "headers": {
              "Retry-After": {
                "description": "On 429, the non-negative delta-seconds a client SHOULD wait before retrying (optional in v0). This API intentionally supports only the delta-seconds form of HTTP Retry-After; the HTTP-date form is invalid for this contract because converting it to a safe monotonic delay would require wall-clock assumptions that lease scheduling forbids.",
                "schema": {
                  "type": "integer",
                  "format": "int64",
                  "minimum": 0
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) when rate limit resets (optional in v0)",
                "schema": {
                  "type": "integer",
                  "format": "int64"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/evidence/{evidence_id}": {
      "get": {
        "tags": [
          "Evidence"
        ],
        "operationId": "getEvidence",
        "x-conformance": "normative",
        "summary": "Fetch a signed CyclesEvidence envelope by content id",
        "description": "Retrieve the signed CyclesEvidence envelope identified by `evidence_id` (the sha256 content hash carried on a receipt's `cycles_evidence` reference). Returns the envelope exactly as emitted — JCS-canonicalizable and Ed25519-signed per cycles-evidence-v0.2 — so any holder can verify it offline without trusting this server.\nAUTH (NORMATIVE): this endpoint is PUBLIC (no ApiKeyAuth). The `evidence_id` is an unguessable sha256 content hash that acts as a capability — only a party already holding the receipt (which carries the id) can fetch the envelope. Because the envelope is content-addressed and signature-verifiable, public read cannot forge or alter it. Servers SHOULD rate-limit this endpoint and SHOULD serve it with `Cache-Control: public, immutable` (the content never changes).",
        "security": [],
        "parameters": [
          {
            "name": "evidence_id",
            "in": "path",
            "required": true,
            "description": "sha256 content hash of the CyclesEvidence envelope (64 lowercase hex chars).",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The signed CyclesEvidence envelope.",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "Cache-Control": {
                "description": "Caching directive. For content-addressed responses (e.g. getEvidence) the server SHOULD return `public, immutable` — the body never changes.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "A signed CyclesEvidence envelope returned by `getEvidence`. The NORMATIVE definition — field semantics and the JCS content-hash + Ed25519 signature derivation — lives in `cycles-evidence-v0.2.yaml`; this schema mirrors its required shape for the serving endpoint. The envelope is content-addressed (`evidence_id` = sha256 of the JCS-canonical bytes with `evidence_id` and `signature` emptied) and self-verifying, so a consumer can check it offline without trusting this server.",
                  "required": [
                    "schema_version",
                    "artifact_type",
                    "server_id",
                    "signer_did",
                    "issued_at_ms",
                    "payload",
                    "evidence_id",
                    "signature"
                  ],
                  "properties": {
                    "schema_version": {
                      "type": "string",
                      "const": "cycles-evidence/v0.1"
                    },
                    "artifact_type": {
                      "type": "string",
                      "enum": [
                        "decide",
                        "reserve",
                        "commit",
                        "release",
                        "error"
                      ]
                    },
                    "server_id": {
                      "type": "string",
                      "description": "Stable URI of the issuing Cycles server."
                    },
                    "signer_did": {
                      "type": "string",
                      "description": "Ed25519 public key (v0.1 hex) the signature verifies against."
                    },
                    "issued_at_ms": {
                      "type": "integer",
                      "format": "int64"
                    },
                    "trace_id": {
                      "type": "string"
                    },
                    "payload": {
                      "type": "object",
                      "description": "One key matching artifact_type (decide/reserve/commit/release/error)."
                    },
                    "evidence_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{64}$"
                    },
                    "signature": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{128}$"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Public endpoints SHOULD be throttled; clients retry after the indicated delay.",
            "headers": {
              "Retry-After": {
                "description": "On 429, the non-negative delta-seconds a client SHOULD wait before retrying (optional in v0). This API intentionally supports only the delta-seconds form of HTTP Retry-After; the HTTP-date form is invalid for this contract because converting it to a safe monotonic delay would require wall-clock assumptions that lease scheduling forbids.",
                "schema": {
                  "type": "integer",
                  "format": "int64",
                  "minimum": 0
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) when rate limit resets (optional in v0)",
                "schema": {
                  "type": "integer",
                  "format": "int64"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/.well-known/cycles-jwks.json": {
      "get": {
        "tags": [
          "Evidence"
        ],
        "operationId": "getEvidenceJwks",
        "x-conformance": "normative",
        "summary": "Fetch the signer's CyclesEvidence JWK Set (signer-key resolution)",
        "description": "Return the issuing server's Ed25519 verification keys as a JWK Set, so a consumer can resolve a `did:cycles` `signer_did` (or confirm a raw-hex one) to a public key and establish signer AUTHORITY — not merely signature validity. This is the publication half of the ADDITIVE signer-key-resolution layer (cycles-evidence v0.2); the resolvable `signer_did` form, the NORMATIVE key-selection + validity-window rules, and the verification dispositions live in `cycles-evidence-v0.2.yaml` (`CyclesEvidenceJwks`).\nLocated API-base-relative: the spec path is `{server_id}/.well-known/cycles-jwks.json`, and `server_id` already includes `/v1` (e.g. `https://cycles.example.com/v1`), so it resolves to `…/v1/.well-known/cycles-jwks.json` — deliberately NOT origin-rooted, so key authority stays anchored to the exact base the `did:cycles` hash commits to. The literal `.well-known` segment marks this as metadata, not a routed API resource.\nOPTIONAL TO PUBLISH: a server that does not participate in signer-key resolution need not serve this (it returns 404, and consumers fall back to raw-hex `signer_did` + `expected_signer` pinning — the `binding_only` posture). A server that DOES publish MUST serve it at this path with this shape and the selection/window semantics in `cycles-evidence-v0.2.yaml`.\nAUTH (NORMATIVE): PUBLIC (no ApiKeyAuth). A JWK Set is public keys only — the standard posture for a verification key set — and is itself the trust anchor consumers resolve, so it must be reachable without credentials. The private signing key is NEVER served by this or any endpoint. Servers SHOULD rate-limit and SHOULD serve with a short `Cache-Control: public` max-age (the set changes only on key rotation), NOT `immutable`.",
        "security": [],
        "responses": {
          "200": {
            "description": "The signer's JWK Set.",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "Cache-Control": {
                "description": "Caching directive. For content-addressed responses (e.g. getEvidence) the server SHOULD return `public, immutable` — the body never changes.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "The issuing server's signer key set, returned by `getEvidenceJwks`. An RFC 7517 JWK Set (extra top-level members allowed and ignored). The NORMATIVE resolution algorithm — `did:cycles` binding, the validity-window key selection (pick the key whose `[cycles_nbf_ms, cycles_exp_ms)` covers the envelope's `issued_at_ms`, never \"the current key\"), the deterministic-selection rules, and the verification dispositions — lives in `cycles-evidence-v0.2.yaml`; this schema mirrors its required shape for the serving endpoint.",
                  "required": [
                    "keys"
                  ],
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true,
                        "description": "One Ed25519 signing key with a Cycles validity window. RFC 7517 / RFC 8037 (OKP/Ed25519) JWK plus the `cycles_*` members. A retired key MUST remain in the set so envelopes signed before a rotation still verify. See `cycles-evidence-v0.2.yaml` for full semantics.",
                        "required": [
                          "kty",
                          "crv",
                          "x",
                          "kid",
                          "cycles_nbf_ms"
                        ],
                        "properties": {
                          "kty": {
                            "type": "string",
                            "const": "OKP"
                          },
                          "crv": {
                            "type": "string",
                            "const": "Ed25519"
                          },
                          "alg": {
                            "type": "string",
                            "const": "EdDSA",
                            "description": "Optional (RFC 7517 §4.4); if present MUST be `EdDSA`. A present-but-different `alg` makes the JWK invalid (excluded)."
                          },
                          "x": {
                            "type": "string",
                            "description": "base64url (unpadded) of the raw 32-byte Ed25519 public key. Equals `base64url(hex-decode(signer_did))` for a raw-hex `signer_did`."
                          },
                          "kid": {
                            "type": "string",
                            "description": "Stable key id; equals the `#<kid>` fragment of a `did:cycles` `signer_did`."
                          },
                          "cycles_nbf_ms": {
                            "type": "integer",
                            "format": "int64",
                            "description": "Valid-from, epoch ms, INCLUSIVE."
                          },
                          "cycles_exp_ms": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "format": "int64",
                            "description": "Valid-until, epoch ms, EXCLUSIVE. Absent/null ⇒ active (open-ended)."
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "active",
                              "retired"
                            ],
                            "description": "Advisory only. Selection is by validity WINDOW, never by `status`."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "This server does not publish a JWK Set (signer-key resolution not enabled, or the evidence signing identity is unconfigured).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Public endpoints SHOULD be throttled; clients retry after the indicated delay.",
            "headers": {
              "Retry-After": {
                "description": "On 429, the non-negative delta-seconds a client SHOULD wait before retrying (optional in v0). This API intentionally supports only the delta-seconds form of HTTP Retry-After; the HTTP-date form is invalid for this contract because converting it to a safe monotonic delay would require wall-clock assumptions that lease scheduling forbids.",
                "schema": {
                  "type": "integer",
                  "format": "int64",
                  "minimum": 0
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix timestamp (seconds) when rate limit resets (optional in v0)",
                "schema": {
                  "type": "integer",
                  "format": "int64"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/balances": {
      "get": {
        "tags": [
          "Balances"
        ],
        "operationId": "getBalances",
        "x-conformance": "normative",
        "summary": "Query current budget balances across scopes (nice-to-have)",
        "description": "Returns balances for scopes matching the provided subject filter. include_children MAY be ignored by v0 implementations.\nSUBJECT 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.\nTENANCY (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. \n",
        "parameters": [
          {
            "name": "tenant",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "app",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workflow",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "agent",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "toolset",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_children",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Maximum number of results to return"
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from previous response"
          }
        ],
        "responses": {
          "200": {
            "description": "Balance response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "X-Cycles-Tenant": {
                "description": "Effective tenant identifier derived from auth context (optional in v0)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "balances"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "balances": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "scope",
                          "scope_path",
                          "remaining"
                        ],
                        "additionalProperties": false,
                        "example": {
                          "scope": "tenant:acme-corp/workspace:prod",
                          "scope_path": "tenant:acme-corp/workspace:prod",
                          "remaining": {
                            "unit": "USD_MICROCENTS",
                            "amount": 4577000
                          },
                          "reserved": {
                            "unit": "USD_MICROCENTS",
                            "amount": 500000
                          },
                          "spent": {
                            "unit": "USD_MICROCENTS",
                            "amount": 423000
                          },
                          "allocated": {
                            "unit": "USD_MICROCENTS",
                            "amount": 5500000
                          }
                        },
                        "description": "Ledger state for a single (scope, unit) balance.\nUNIT CONSISTENCY (NORMATIVE):\n  - All Amount fields within a Balance (remaining/reserved/spent/allocated/debt/overdraft_limit), if present, MUST share the same unit.\n  - Servers MUST NOT emit a Balance with mixed units; clients MAY treat such responses as invalid.\n\nLEDGER INVARIANT (NORMATIVE):\n   - If allocated, spent, reserved, and debt are all present, then remaining MUST satisfy: \n     remaining.amount = allocated.amount - spent.amount - reserved.amount - debt.amount (same unit).\n   - Note that remaining.amount can be negative when debt exceeds available budget.\n   - If allocated is absent, remaining is authoritative and clients MUST treat it as opaque (i.e., MUST NOT derive it from other fields).\n   - Servers MUST NOT emit a Balance where the invariant is violated when all fields are present.\n\nDEBT SEMANTICS (NORMATIVE):\n   - debt represents actual consumption that occurred when insufficient budget was available to cover a commit overage.\n   - debt is created only when overage_policy=ALLOW_WITH_OVERDRAFT and (remaining + released_reservation) < actual at commit time.\n   - When debt > 0, new reservations MUST be rejected with 409 DEBT_OUTSTANDING (unless explicitly allowed by policy).\n   - debt MUST be repaid via budget funding operations (out-of-scope for this API) before new reservations are permitted.\n   - When budget is added to a scope with debt > 0, debt MUST be repaid first, then remaining funds added to remaining.\n\nOVERDRAFT LIMIT (NORMATIVE):\n   - overdraft_limit defines the target maximum debt for this scope.\n   - If overdraft_limit is absent or amount=0, no overdraft is permitted (behaves as ALLOW_IF_AVAILABLE).\n   - The overdraft_limit is enforced per-commit but is NOT atomic across concurrent commits.\n     Multiple concurrent commits MAY cause cumulative debt to briefly exceed overdraft_limit.\n   - Individual commits are rejected with 409 OVERDRAFT_LIMIT_EXCEEDED only if (current_debt + delta) > overdraft_limit \n     at the moment of that specific commit.\n   - When debt > overdraft_limit after reconciliation (due to concurrent commits), the scope enters an \"over-limit\" state:\n       * The server MUST set is_over_limit=true on the Balance\n       * ALL new reservations against that scope MUST be rejected with 409 OVERDRAFT_LIMIT_EXCEEDED (not DEBT_OUTSTANDING)\n       * Existing active reservations MAY be committed or released normally\n       * The server SHOULD emit alerts/events for operator intervention\n   - Once debt is repaid below overdraft_limit, is_over_limit returns to false and normal reservation operation resumes.",
                        "properties": {
                          "scope": {
                            "type": "string",
                            "description": "Canonical scope identifier (server-derived), e.g. \"tenant:t1\", \"workflow:run123\""
                          },
                          "scope_path": {
                            "type": "string",
                            "description": "Canonical hierarchical path (server-derived)"
                          },
                          "remaining": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": -120000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
                              }
                            }
                          },
                          "reserved": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "spent": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "debt": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "allocated": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "overdraft_limit": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "is_over_limit": {
                            "type": "boolean",
                            "description": "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."
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "description": "Opaque cursor for next page (if any)."
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "True if next_cursor is present and more results may be available."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/events": {
      "post": {
        "tags": [
          "Events"
        ],
        "operationId": "createEvent",
        "x-conformance": "normative",
        "summary": "Optional post-only accounting when pre-estimation is not available",
        "description": "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.\nIDEMPOTENCY (NORMATIVE):  - On replay with the same idempotency_key, the server MUST return the original successful response payload.\nTENANCY (NORMATIVE):  - subject.tenant MUST match the effective tenant derived from auth; otherwise the server MUST return 403 FORBIDDEN.",
        "parameters": [
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "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.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "idempotency_key",
                  "subject",
                  "action",
                  "actual"
                ],
                "additionalProperties": false,
                "properties": {
                  "idempotency_key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "subject": {
                    "type": "object",
                    "description": "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.\nCHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset)\n  SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope\n  paths use \":\" and \"/\" as structural delimiters, so delimiter,\n  whitespace, and control characters have no stable canonical encoding.\n- Servers MAY reject standard-field values outside this pattern with\n  400 INVALID_REQUEST (the reference implementation does). Clients\n  MUST NOT rely on out-of-pattern values being accepted, and portable\n  clients SHOULD restrict themselves to the pattern above.\n\nEXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies\n  (e.g., cost_center/department/project) and policy/reporting.\n- 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.\n",
                    "additionalProperties": false,
                    "minProperties": 1,
                    "anyOf": [
                      {
                        "required": [
                          "tenant"
                        ]
                      },
                      {
                        "required": [
                          "workspace"
                        ]
                      },
                      {
                        "required": [
                          "app"
                        ]
                      },
                      {
                        "required": [
                          "workflow"
                        ]
                      },
                      {
                        "required": [
                          "agent"
                        ]
                      },
                      {
                        "required": [
                          "toolset"
                        ]
                      }
                    ],
                    "properties": {
                      "tenant": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "workspace": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "app": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "workflow": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "agent": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "toolset": {
                        "type": "string",
                        "maxLength": 128
                      },
                      "dimensions": {
                        "type": "object",
                        "description": "Optional custom dimensions for enterprise taxonomies and policy/reporting.",
                        "additionalProperties": {
                          "type": "string",
                          "maxLength": 256
                        },
                        "maxProperties": 16
                      }
                    }
                  },
                  "action": {
                    "type": "object",
                    "required": [
                      "kind",
                      "name"
                    ],
                    "additionalProperties": false,
                    "properties": {
                      "kind": {
                        "type": "string",
                        "maxLength": 64,
                        "description": "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\n"
                      },
                      "name": {
                        "type": "string",
                        "maxLength": 256,
                        "description": "Provider/model/tool identifier (e.g., \"openai:gpt-4o-mini\", \"web.search\")"
                      },
                      "tags": {
                        "type": "array",
                        "maxItems": 10,
                        "items": {
                          "type": "string",
                          "maxLength": 64
                        },
                        "description": "Optional policy tags (e.g., [\"prod\",\"customer-facing\"])"
                      }
                    }
                  },
                  "actual": {
                    "type": "object",
                    "required": [
                      "unit",
                      "amount"
                    ],
                    "additionalProperties": false,
                    "example": {
                      "unit": "USD_MICROCENTS",
                      "amount": 500000
                    },
                    "properties": {
                      "unit": {
                        "type": "string",
                        "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                        "enum": [
                          "USD_MICROCENTS",
                          "TOKENS",
                          "CREDITS",
                          "RISK_POINTS"
                        ]
                      },
                      "amount": {
                        "type": "integer",
                        "format": "int64",
                        "minimum": 0
                      }
                    }
                  },
                  "overage_policy": {
                    "type": "string",
                    "description": "How server handles commits where actual > reserved.\nREJECT: reject commit (client must reserve a buffer or over-estimate).\n  Recommended: add 10-20% buffer to estimates when using REJECT.\n  If the action already happened externally, this creates an unaccounted gap.\n\nALLOW_IF_AVAILABLE (default): commit always succeeds when actual > reserved. If remaining budget supports the full delta, charge it atomically across all derived scopes. Otherwise, cap the delta to available remaining (minimum across all affected scopes, floor 0), charge estimate + capped_delta, and set is_over_limit=true on scopes where the full delta could not be covered. This blocks future reservations until reconciled. Never creates debt. Never rejects a commit — the action already happened.\nALLOW_WITH_OVERDRAFT: if remaining budget supports the delta, commit normally. Otherwise, check if (debt + delta) <= overdraft_limit across all affected scopes.\n  If yes: commit succeeds, add delta to debt, remaining can go negative.\n  If no: commit fails with 409 OVERDRAFT_LIMIT_EXCEEDED.\nThis policy ensures the ledger always reflects reality even when budget is exhausted at commit time.\nCONCURRENCY (NORMATIVE):\n  - The overdraft_limit check is performed per-commit and is NOT atomic across concurrent commits to the same scope.\n  - Multiple commits may each individually pass the (debt + delta) <= limit check but collectively cause debt to exceed overdraft_limit.\n  - This is acceptable: all commits succeed (actions already happened), and the scope enters \"over-limit\" state, \n    blocking future reservations until reconciled.\n  - Servers SHOULD use optimistic locking or compare-and-swap on debt updates to ensure individual commit atomicity, \n    but cross-commit atomicity is not required.\n",
                    "enum": [
                      "REJECT",
                      "ALLOW_IF_AVAILABLE",
                      "ALLOW_WITH_OVERDRAFT"
                    ]
                  },
                  "metrics": {
                    "type": "object",
                    "description": "Optional standard metrics for commit/event. All fields are optional. Recommended for consistency across clients. For custom fields, use `custom`.\n",
                    "additionalProperties": false,
                    "properties": {
                      "tokens_input": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Input tokens consumed"
                      },
                      "tokens_output": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Output tokens generated"
                      },
                      "latency_ms": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Total operation latency in milliseconds"
                      },
                      "model_version": {
                        "type": "string",
                        "maxLength": 128,
                        "description": "Actual model/tool version used"
                      },
                      "custom": {
                        "type": "object",
                        "additionalProperties": true,
                        "description": "Arbitrary additional metrics"
                      }
                    }
                  },
                  "client_time_ms": {
                    "type": "integer",
                    "format": "int64",
                    "minimum": 0,
                    "description": "Optional client-observed timestamp (ms). This value is advisory only and MUST NOT be used for authorization, expiry,  idempotency or budget enforcement decisions. The server's receipt time governs all budget comparisons and invariants.\n"
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                },
                "description": "NOTE: Events MUST be applied atomically across derived scopes (same as reservations), or rejected.\n"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Event created and atomically applied to balances",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "X-Cycles-Tenant": {
                "description": "Effective tenant identifier derived from auth context (optional in v0)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "event_id"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "APPLIED"
                      ]
                    },
                    "event_id": {
                      "type": "string"
                    },
                    "charged": {
                      "type": "object",
                      "required": [
                        "unit",
                        "amount"
                      ],
                      "additionalProperties": false,
                      "example": {
                        "unit": "USD_MICROCENTS",
                        "amount": 500000
                      },
                      "properties": {
                        "unit": {
                          "type": "string",
                          "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                          "enum": [
                            "USD_MICROCENTS",
                            "TOKENS",
                            "CREDITS",
                            "RISK_POINTS"
                          ]
                        },
                        "amount": {
                          "type": "integer",
                          "format": "int64",
                          "minimum": 0
                        }
                      }
                    },
                    "balances": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "scope",
                          "scope_path",
                          "remaining"
                        ],
                        "additionalProperties": false,
                        "example": {
                          "scope": "tenant:acme-corp/workspace:prod",
                          "scope_path": "tenant:acme-corp/workspace:prod",
                          "remaining": {
                            "unit": "USD_MICROCENTS",
                            "amount": 4577000
                          },
                          "reserved": {
                            "unit": "USD_MICROCENTS",
                            "amount": 500000
                          },
                          "spent": {
                            "unit": "USD_MICROCENTS",
                            "amount": 423000
                          },
                          "allocated": {
                            "unit": "USD_MICROCENTS",
                            "amount": 5500000
                          }
                        },
                        "description": "Ledger state for a single (scope, unit) balance.\nUNIT CONSISTENCY (NORMATIVE):\n  - All Amount fields within a Balance (remaining/reserved/spent/allocated/debt/overdraft_limit), if present, MUST share the same unit.\n  - Servers MUST NOT emit a Balance with mixed units; clients MAY treat such responses as invalid.\n\nLEDGER INVARIANT (NORMATIVE):\n   - If allocated, spent, reserved, and debt are all present, then remaining MUST satisfy: \n     remaining.amount = allocated.amount - spent.amount - reserved.amount - debt.amount (same unit).\n   - Note that remaining.amount can be negative when debt exceeds available budget.\n   - If allocated is absent, remaining is authoritative and clients MUST treat it as opaque (i.e., MUST NOT derive it from other fields).\n   - Servers MUST NOT emit a Balance where the invariant is violated when all fields are present.\n\nDEBT SEMANTICS (NORMATIVE):\n   - debt represents actual consumption that occurred when insufficient budget was available to cover a commit overage.\n   - debt is created only when overage_policy=ALLOW_WITH_OVERDRAFT and (remaining + released_reservation) < actual at commit time.\n   - When debt > 0, new reservations MUST be rejected with 409 DEBT_OUTSTANDING (unless explicitly allowed by policy).\n   - debt MUST be repaid via budget funding operations (out-of-scope for this API) before new reservations are permitted.\n   - When budget is added to a scope with debt > 0, debt MUST be repaid first, then remaining funds added to remaining.\n\nOVERDRAFT LIMIT (NORMATIVE):\n   - overdraft_limit defines the target maximum debt for this scope.\n   - If overdraft_limit is absent or amount=0, no overdraft is permitted (behaves as ALLOW_IF_AVAILABLE).\n   - The overdraft_limit is enforced per-commit but is NOT atomic across concurrent commits.\n     Multiple concurrent commits MAY cause cumulative debt to briefly exceed overdraft_limit.\n   - Individual commits are rejected with 409 OVERDRAFT_LIMIT_EXCEEDED only if (current_debt + delta) > overdraft_limit \n     at the moment of that specific commit.\n   - When debt > overdraft_limit after reconciliation (due to concurrent commits), the scope enters an \"over-limit\" state:\n       * The server MUST set is_over_limit=true on the Balance\n       * ALL new reservations against that scope MUST be rejected with 409 OVERDRAFT_LIMIT_EXCEEDED (not DEBT_OUTSTANDING)\n       * Existing active reservations MAY be committed or released normally\n       * The server SHOULD emit alerts/events for operator intervention\n   - Once debt is repaid below overdraft_limit, is_over_limit returns to false and normal reservation operation resumes.",
                        "properties": {
                          "scope": {
                            "type": "string",
                            "description": "Canonical scope identifier (server-derived), e.g. \"tenant:t1\", \"workflow:run123\""
                          },
                          "scope_path": {
                            "type": "string",
                            "description": "Canonical hierarchical path (server-derived)"
                          },
                          "remaining": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "description": "Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.",
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": -120000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "description": "Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved)."
                              }
                            }
                          },
                          "reserved": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "spent": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "debt": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "allocated": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "overdraft_limit": {
                            "type": "object",
                            "required": [
                              "unit",
                              "amount"
                            ],
                            "additionalProperties": false,
                            "example": {
                              "unit": "USD_MICROCENTS",
                              "amount": 500000
                            },
                            "properties": {
                              "unit": {
                                "type": "string",
                                "description": "Standard units.\nUSD_MICROCENTS preserves precision for per-call and batched accounting (int64).\n  - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars\n  - 1 USD = 100 cents = 10^8 USD_MICROCENTS\n  - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B\n\nTOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).\n",
                                "enum": [
                                  "USD_MICROCENTS",
                                  "TOKENS",
                                  "CREDITS",
                                  "RISK_POINTS"
                                ]
                              },
                              "amount": {
                                "type": "integer",
                                "format": "int64",
                                "minimum": 0
                              }
                            }
                          },
                          "is_over_limit": {
                            "type": "boolean",
                            "description": "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."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND — no budget exists at any of the event's derived scopes in any unit. The message field carries the specific \"Budget not found for provided scope: ...\" detail. Distinct from 400 UNIT_MISMATCH (a budget exists at the scope but in a different unit).",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "X-Cycles-Tenant": {
                "description": "Effective tenant identifier derived from auth context (optional in v0)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Persisting-debit denial. error=TENANT_CLOSED when the owning tenant's status is CLOSED (createEvent is a persisting budget debit on the same runtime-plane surface as commit; see ERROR SEMANTICS — there is no dry_run/decide mode on /v1/events, so on a fresh (non-replay) request the closed-tenant outcome is this 409, never a decision=DENY; a same-key replay of a pre-close event still returns its original stored 201 per IDEMPOTENCY). Plus the two 409 codes explicitly specified by EventCreateRequest.overage_policy: error=BUDGET_EXCEEDED under overage_policy=REJECT, and error=OVERDRAFT_LIMIT_EXCEEDED under overage_policy=ALLOW_WITH_OVERDRAFT.",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              },
              "X-Cycles-Tenant": {
                "description": "Effective tenant identifier derived from auth context (optional in v0)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error response",
            "headers": {
              "X-Request-Id": {
                "description": "Unique request identifier for debugging",
                "schema": {
                  "type": "string"
                }
              },
              "X-Cycles-Trace-Id": {
                "description": "W3C Trace Context-compatible correlation identifier echoed on every response (2xx, 4xx, 5xx) on every plane. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field). Links the request, its audit entry, all side-effect events, and any outbound webhook deliveries produced by the request. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for inbound-header precedence, fallback-generation rules, and cross-surface propagation contract.",
                "schema": {
                  "type": "string",
                  "pattern": "^[0-9a-f]{32}$"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message",
                    "request_id"
                  ],
                  "additionalProperties": false,
                  "example": {
                    "error": "BUDGET_EXCEEDED",
                    "message": "Insufficient remaining budget for scope tenant:acme-corp",
                    "request_id": "req_abc123def456",
                    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
                  },
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "INVALID_REQUEST",
                        "UNAUTHORIZED",
                        "FORBIDDEN",
                        "NOT_FOUND",
                        "BUDGET_EXCEEDED",
                        "BUDGET_FROZEN",
                        "BUDGET_CLOSED",
                        "RESERVATION_EXPIRED",
                        "RESERVATION_FINALIZED",
                        "IDEMPOTENCY_MISMATCH",
                        "UNIT_MISMATCH",
                        "OVERDRAFT_LIMIT_EXCEEDED",
                        "DEBT_OUTSTANDING",
                        "MAX_EXTENSIONS_EXCEEDED",
                        "LIMIT_EXCEEDED",
                        "TENANT_CLOSED",
                        "INTERNAL_ERROR"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{32}$",
                      "description": "W3C Trace Context-compatible correlation identifier. 32 lowercase hex characters (128-bit trace ID, matching the W3C Trace Context trace-id field in the `traceparent` header). Links this error response to the request that triggered it and to any events / audit entries emitted before the error was returned. Optional on the wire for forward compatibility with historical responses emitted by servers older than the first release that populated this field, but servers conformant with the CORRELATION AND TRACING contract MUST populate it on every error response. See the \"CORRELATION AND TRACING\" section of this document's `info.description` for header formats and propagation rules."
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "cycles_evidence": {
                      "type": "object",
                      "additionalProperties": false,
                      "description": "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.\n\nTRANSPORT 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.",
                      "required": [
                        "evidence_id",
                        "cycles_evidence_url"
                      ],
                      "properties": {
                        "evidence_id": {
                          "type": "string",
                          "pattern": "^[0-9a-f]{64}$",
                          "description": "sha256 content id of the emitted envelope; resolve via getEvidence."
                        },
                        "cycles_evidence_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "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`."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}