---
title: "Optional post-only accounting when pre-estimation is not available"
method: POST
path: "/v1/events"
tags: ["Events"]
---

# Optional post-only accounting when pre-estimation is not available

`POST /v1/events`

## Request

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

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.
IDEMPOTENCY (NORMATIVE):  - On replay with the same idempotency_key, the server MUST return the original successful response payload.
TENANCY (NORMATIVE):  - subject.tenant MUST match the effective tenant derived from auth; otherwise the server MUST return 403 FORBIDDEN.

## Headers

- `X-Idempotency-Key` string

## Request body

- object: NOTE: Events MUST be applied atomically across derived scopes (same as reservations), or rejected.
  - `idempotency_key` string, required
  - `subject` union, required: Dimension bag for hierarchical budgets. At least one standard field (tenant, workspace, app, workflow, agent, or toolset) MUST be provided. A subject containing only `dimensions` is invalid; server MUST return 400 INVALID_REQUEST. Hierarchy: tenant → workspace → app → workflow → agent → toolset. CHARSET (NORMATIVE, revision 2026-07-03): - Standard-field values (tenant/workspace/app/workflow/agent/toolset) SHOULD match ^[a-zA-Z0-9_.-]+$. Canonical scope identifiers and scope paths use ":" and "/" as structural delimiters, so delimiter, whitespace, and control characters have no stable canonical encoding. - Servers MAY reject standard-field values outside this pattern with 400 INVALID_REQUEST (the reference implementation does). Clients MUST NOT rely on out-of-pattern values being accepted, and portable clients SHOULD restrict themselves to the pattern above. EXTENSIBILITY (v0): - dimensions is an optional, user-defined map for alternative taxonomies (e.g., cost_center/department/project) and policy/reporting. - v0 servers MAY ignore dimensions for budgeting decisions; if they do, they still MUST accept and round-trip it. - Keys SHOULD be lowercase and match ^[a-z0-9_.-]+$ for stable canonicalization; values are opaque strings.
    - object
      - `tenant` string, required
      - `workspace` string
      - `app` string
      - `workflow` string
      - `agent` string
      - `toolset` string
      - `dimensions` object: Optional custom dimensions for enterprise taxonomies and policy/reporting.
    - object
      - `tenant` string
      - `workspace` string, required
      - `app` string
      - `workflow` string
      - `agent` string
      - `toolset` string
      - `dimensions` object: Optional custom dimensions for enterprise taxonomies and policy/reporting.
    - object
      - `tenant` string
      - `workspace` string
      - `app` string, required
      - `workflow` string
      - `agent` string
      - `toolset` string
      - `dimensions` object: Optional custom dimensions for enterprise taxonomies and policy/reporting.
    - object
      - `tenant` string
      - `workspace` string
      - `app` string
      - `workflow` string, required
      - `agent` string
      - `toolset` string
      - `dimensions` object: Optional custom dimensions for enterprise taxonomies and policy/reporting.
    - object
      - `tenant` string
      - `workspace` string
      - `app` string
      - `workflow` string
      - `agent` string, required
      - `toolset` string
      - `dimensions` object: Optional custom dimensions for enterprise taxonomies and policy/reporting.
    - object
      - `tenant` string
      - `workspace` string
      - `app` string
      - `workflow` string
      - `agent` string
      - `toolset` string, required
      - `dimensions` object: Optional custom dimensions for enterprise taxonomies and policy/reporting.
  - `action` object, required
    - `kind` string, required: Action type identifier. Recommended format: <category>.<operation> Examples: llm.completion, llm.embedding, tool.search, tool.calculator, db.query, db.write, http.get, http.post, file.upload, message.email, webhook.outbound
    - `name` string, required: Provider/model/tool identifier (e.g., "openai:gpt-4o-mini", "web.search")
    - `tags` string[]: Optional policy tags (e.g., ["prod","customer-facing"])
  - `actual` object, required
    - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
    - `amount` integer, required
  - `overage_policy` 'REJECT' | 'ALLOW_IF_AVAILABLE' | 'ALLOW_WITH_OVERDRAFT': How server handles commits where actual > reserved. REJECT: reject commit (client must reserve a buffer or over-estimate). Recommended: add 10-20% buffer to estimates when using REJECT. If the action already happened externally, this creates an unaccounted gap. ALLOW_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. ALLOW_WITH_OVERDRAFT: if remaining budget supports the delta, commit normally. Otherwise, check if (debt + delta) <= overdraft_limit across all affected scopes. If yes: commit succeeds, add delta to debt, remaining can go negative. If no: commit fails with 409 OVERDRAFT_LIMIT_EXCEEDED. This policy ensures the ledger always reflects reality even when budget is exhausted at commit time. CONCURRENCY (NORMATIVE): - The overdraft_limit check is performed per-commit and is NOT atomic across concurrent commits to the same scope. - Multiple commits may each individually pass the (debt + delta) <= limit check but collectively cause debt to exceed overdraft_limit. - This is acceptable: all commits succeed (actions already happened), and the scope enters "over-limit" state, blocking future reservations until reconciled. - Servers SHOULD use optimistic locking or compare-and-swap on debt updates to ensure individual commit atomicity, but cross-commit atomicity is not required.
  - `metrics` object: Optional standard metrics for commit/event. All fields are optional. Recommended for consistency across clients. For custom fields, use `custom`.
    - `tokens_input` integer: Input tokens consumed
    - `tokens_output` integer: Output tokens generated
    - `latency_ms` integer: Total operation latency in milliseconds
    - `model_version` string: Actual model/tool version used
    - `custom` object: Arbitrary additional metrics
  - `client_time_ms` integer: 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.
  - `metadata` object

## Response `201`

Event created and atomically applied to balances

- object
  - `status` 'APPLIED', required
  - `event_id` string, required
  - `charged` object
    - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
    - `amount` integer, required
  - `balances` object[]
    - `scope` string, required: Canonical scope identifier (server-derived), e.g. "tenant:t1", "workflow:run123"
    - `scope_path` string, required: Canonical hierarchical path (server-derived)
    - `remaining` object, required: Like Amount, but allows negative values. Used for Balance.remaining which can be negative in overdraft scenarios.
      - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
      - `amount` integer, required: Signed integer amount. Can be negative when representing remaining balance in overdraft state (debt > allocated - spent - reserved).
    - `reserved` object
      - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
      - `amount` integer, required
    - `spent` object
      - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
      - `amount` integer, required
    - `debt` object
      - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
      - `amount` integer, required
    - `allocated` object
      - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
      - `amount` integer, required
    - `overdraft_limit` object
      - `unit` 'USD_MICROCENTS' | 'TOKENS' | 'CREDITS' | 'RISK_POINTS', required: Standard units. USD_MICROCENTS preserves precision for per-call and batched accounting (int64). - 1 USD_MICROCENTS = 10^-6 cents = 10^-8 dollars - 1 USD = 100 cents = 10^8 USD_MICROCENTS - Max int64 ≈ 9.22e18 USD_MICROCENTS ≈ $92.2B TOKENS are integer token counts. CREDITS/RISK_POINTS are generic integer units (optional in v0 implementations).
      - `amount` integer, required
    - `is_over_limit` boolean: True when debt > overdraft_limit due to concurrent commits creating cumulative debt beyond the limit, or when ALLOW_IF_AVAILABLE could not cover the full overage delta and capped the charge. When true, ALL new reservations against this scope are blocked with 409 OVERDRAFT_LIMIT_EXCEEDED until reconciled (debt repaid below overdraft_limit, or is_over_limit cleared by operator). Defaults to false when absent.

## Other responses

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

---

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