Usage
Workspace limits plus the usage ledger, budgets, alerts, and reconciliation.
Usage v1 routes require a workspace bearer API key. 401 / 403 use { ok: false, error: { code, message, retryable }, meta }. Success is { ok: true, data, meta } with meta.contractVersion: "api-contracts.v1".
Mutations require header Idempotency-Key. Replays with the same fingerprint return the stored response; a different body is a conflict.
Session GET /api/usage?workspaceId= returns { usage } without the contract envelope (membership required).
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/usage/limits | Subscription-style usage summary |
| GET | /api/v1/usage/events | List ledger events |
| POST | /api/v1/usage/events | Append a priced event |
| GET | /api/v1/usage/budgets | List budgets |
| PUT or POST | /api/v1/usage/budgets | Upsert a budget |
| GET | /api/v1/usage/alerts | List alerts |
| POST | /api/v1/usage/alerts | Evaluate alerts now |
| GET | /api/v1/usage/reconciliation | Compare ledger vs legacy credits |
Limits
GET /api/v1/usage/limits → data: { usage }. limits[] always use the internal-test caps documented in Usage, budgets, and alerts. Without a billing account to read, usage.plan is "internal-test" and usage.billingEnabled is false.
Viewer keys receive the workspace-safe.v1 aggregate projection without organization billing identifiers,
provider/customer coordinates, or invoice URLs. Keys with workspace.secret.read or workspace.write
retain the full billing summary.
On a database-backed deployment the summary also carries usage.billing, the organization's billing summary, and usage.plan / usage.billingEnabled are taken from it (usage.plan falls back to "internal-test" while no plan has been chosen). usage.billing.state is one of:
| State | Meaning |
|---|---|
trialing | A trial the owner started is running. |
active | A paid plan is current. |
grace | Payment is outstanding; access continues until graceEndsAt. |
restricted | Payment failed past the grace window. |
canceled | The subscription ended. |
operator_comped | Billing is waived by the operator. |
unentitled | Provisioned but not yet entitled: the account exists, nobody comped it, and no plan or trial has been chosen. Self-provisioned organizations start here, and it is not a trial — the owner still starts one through onboarding. |
Events
Query: dateFrom, dateTo (ISO; dateFrom must be earlier than dateTo; dateTo is exclusive), provider, model, usageClass, cursor (base64url { occurredAt, id }), limit (1–100, default 50). groupBy must be provider, model, or usageClass if set.
data: { events, nextCursor, breakdown? }. Bigints serialize as strings; costCredits is a number.
POST required fields: eventKey, provider, model, usageClass, unit, quantity (positive integer string). Optional: inputTokens, outputTokens (non-negative ints, default 0), runId, stepId, requestId, metadata, occurredAt. Server sets costMicros and pricingVersion from the catalog. Mismatched client costMicros / pricingVersion: 400. Duplicate eventKey with the same payload: 200 { event, deduplicated: true }. Different payload: 409 CONFLICT. Created: 201.
Budgets
GET → data: { budgets[] } (limitMicros string, period, warningPercent, enabled).
PUT/POST body: limitMicros (positive bigint string), period (daily | monthly, default monthly), optional warningPercent (1–100 integer, default 80), optional enabled (default false on create). Invalid: 400.
Alerts
GET → data: { alerts[] }. POST (empty body, still needs idempotency) → data: { enabled: true, created[] } for newly opened alerts this evaluation.
Reconciliation
Query legacyCostCredits (required, ≥ 0) and optional dateFrom / dateTo. data includes ledgerCostMicros, legacyCostMicros, deltaMicros (strings), credit floats, and status: balanced or mismatch (tolerance default 0 micros). 1 cost credit = 10_000 micros.
