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 is current: none has been chosen yet, or the subscription was canceled. In both cases usage.billing.currentPlanKey is null and usage.billing.compute.includedCredits is "0"). 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. No plan is current, runs are refused with billing_subscription_canceled, and the owner can choose any plan again through Checkout. |
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. |
Organization costs (session and capability)
The session tRPC query organizationCosts.report reads numeric organization evidence for an
organization Owner or Admin, preserving the organization's MFA checks. Ordinary members cannot
read it. The read-only capability billing-usage.costs provides the same report for the authenticated
workspace's organization, requiring its existing workspace read grant and an acting organization
Owner or Admin. A caller cannot select another organization through capability input.
API-key and OAuth capability calls are additionally limited to the authenticated target workspace;
their workspaceId filter cannot select another workspace. Filter options also remain inside that
workspace, including workflow, user, provider and model names. Organization-wide session/internal-agent
queries still require the acting user's current organization Owner/Admin role and verified MFA assurance
when required by policy.
Inputs: from, to (exclusive ISO UTC midnight boundaries, 1–366 days), optional workspaceId,
workflowId, responsiblePrincipalId, provider, model, nodeId, groupBy
(workflow, user, workspace, node, invocation), metric (usd, mxn, equivalent, compute,
tokens), cursor, optionsSearch (optional name/ID substring, at most 100 characters), and limit (1–100, default 25). Session input additionally requires
organizationId; timeZone is UTC only.
The safe report contains totals, descending ranked rows, daily, name-based filter options,
captureStartedAt, generatedAt, and nextCursor.
Filter options are bounded to 50 per kind on the first page. optionsTruncated signals that a more
specific optionsSearch is needed; continuation pages return empty option lists and do not rerun
that inventory. Search affects the choices only, preserving the report's totals and authorization scope.
Numeric quantities serialize as integer strings;
unknown monetary/token quantities are null, never an inferred zero. USD and MXN costs are in
millionths; equivalentCreditMicros is millionths of an informational credit. Coverage counts include
invocations, measuredInvocations, pricedInvocations, knownMxnInvocations, convertedInvocations, and known-token
counts. MXN coverage is independent of equivalent-credit coverage; the mxn metric sorts by known MXN amounts,
including measured zero before unavailable values. Equivalent credits do not alter billing, budgets, balances or Stripe exports.
The global ranking precedes pagination. Totals and daily values cover the full filtered set.
Signed cursors are bound to the organization, period, filters, grouping, measure and page size;
changing any of them starts a new page. Cursors also bind a content revision of the filtered evidence.
If that evidence changes before the next page, the request is refused as an invalid cursor (tRPC
BAD_REQUEST, MCP -32602 / invalid_cursor). Omit the cursor to restart; never combine the old
rows/totals with a new revision. Changes outside a selected workspace do not invalidate its cursor.
Node/provider/model views return compute quantities as
null, because workflow compute cannot be allocated to those dimensions. Invocation rows include
retained numeric quote references, not prompts or tool payloads. Public POST /api/v1/usage/events
does not populate this trusted node ledger.
The existing GET /api/billing/organization-usage, billing-usage.organization, usage v1 routes,
and their payloads retain their current contracts. See the organization costs guide.
Events query
Query: dateFrom, dateTo (ISO; dateFrom must be earlier than dateTo; dateTo is exclusive), provider, model, usageClass, usageTag (1–64 letters, digits, ., _, :, or -), cursor (base64url { occurredAt, id }), limit (1–100, default 50). groupBy must be provider, model, or usageClass if set. The API key's workspace always scopes the query. usageTag filters events attributed to that product tag within the selected date range; use groupBy to total cost and quantity for the period.
Usage from a workflow run started with productContext.usageTag inherits the pseudonymous tag stored on that run. The event's own runId decides when it is a workflow run of the workspace; metadata.workflowRunId is read only when runId is not a workflow run (the agent runtime records a step under its own run ID and names its workflow run there). An event whose runId and metadata.workflowRunId name two different workflow runs of the workspace is refused with 400, so an event never belongs to one run while its usage is attributed to another. Internal, manual, and scheduled runs without product context have no tag. The ledger stores this product tag, never an end user's external ID.
data: { events, nextCursor, breakdown? }. Bigints serialize as strings; costCredits is a number.
The workspace-safe.v1 event projection omits metadata, eventKey, and requestId. For valid
audio_transcription events measured in audio_millisecond, it can include
evidence: { durationMs, status, providerCompleted }. Duration must match the event quantity and
be at most 60 seconds; invalid or missing evidence is omitted. status is succeeded or failed.
A completed provider request can be charged even when its transcript is empty or later validation
fails. This evidence contains neither recording bytes nor transcript content.
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.
