Usage
Pull aggregated usage over a time window, or page through exact usage events. Usage events are the durable debit ledger: sum usage.costCents from events to reconcile wallet spend.
Token totals are cumulative across model calls. A run's inputTokens can exceed the serving model's context window because every call contributes its own prompt; cacheReadTokens is the cached subset of that input and is billed at the cached-input rate.
Methods
| Method | Route |
|---|---|
| novo.usage.aggregate({ groupBy?, from?, to? }) | GET /v1/usage |
| novo.usage.events.list({ ...filters }) | GET /v1/usage/events |
Type signature
type UsageGroupBy = 'day' | 'agent' | 'thread' | 'band' | 'api_key';
type UsageAggregateRow = {
groupKey: string | number | null;
inputTokens: number;
outputTokens: number;
cacheReadTokens: number;
cacheWriteTokens: number;
runtimeMs: number;
toolCallCount: number;
costCents: number;
};
type UsageEvent = {
usageEventId: string;
chargeId: string; // stable join key; equals usageEventId, also on terminal receipts
meterKind: 'ai_inference' | 'managed_runtime';
resourceKind: 'agent_run' | 'subagent_run' | 'managed_media' | 'managed_browser' | 'managed_workspace' | 'managed_document_processor';
modelLabel: string; // generic, provider-neutral label; never names the model/provider
agentId: string;
threadId: string;
runId: string | null;
usage: UsageTotal;
units: Record<string, unknown>;
createdAt: string;
};
type UsageNamespace = {
aggregate(input?: {
groupBy?: UsageGroupBy;
from?: string; // ISO datetime
to?: string; // ISO datetime
}): Promise<{ groupBy: UsageGroupBy; data: UsageAggregateRow[] }>;
events: {
list(input?: {
from?: string;
to?: string;
agentId?: string;
threadId?: string;
runId?: string;
meterKind?: 'ai_inference' | 'managed_runtime';
cursor?: string;
limit?: number;
}): Promise<Page<UsageEvent>>;
};
};
Aggregate
const { data } = await novo.usage.aggregate({
groupBy: 'day',
from: '2026-05-01T00:00:00Z',
to: '2026-05-31T23:59:59Z',
});
for (const row of data) {
console.log(row.groupKey, row.costCents);
}
Events
const page = await novo.usage.events.list({
from: '2026-05-01T00:00:00Z',
meterKind: 'managed_runtime',
});
const total = page.data.reduce((sum, event) => sum + event.usage.costCents, 0);
meterKind: 'ai_inference' covers run-final model and managed inference-tool debits. meterKind: 'managed_runtime' covers thread-scoped managed browser, managed workspace, and hidden document-processor debits. Runtime events use zero token counts and put quantities such as durationMs, billableMinutes, or sandbox compute/storage units in units.
resourceKind is the axis that distinguishes sub-call types under one run or thread. agent_run is the root run's own inference; subagent_run is a delegated subagent worker's inference, attributed to the root run's runId but billed and reported as its own row; managed_media is a single managed media generation (image, video, or audio), one row per generation keyed to the tool call. managed_browser, managed_workspace, and managed_document_processor are thread-scoped runtime rows; document-processor spend is reconciled from Novo's cumulative processor-runtime counters and can settle after the originating run. Filter usage.events.list({ runId }) to see run-scoped inference and managed-media rows. Use threadId for runtime rows that can settle outside a live run. These sub-call rows partition debits: summing every row's costCents for a run still equals the run's billed cost exactly. A subagent_run row reads $0 when a delegated worker terminated on an internal engine fault: platform faults are not billed, so that attempt is credited to zero while still recording that it ran. The credited row keeps the partition exact; it contributes $0 to both the row sum and the wallet.
Model label and provider identity
modelLabel is a stable, generic label for the charge, for example "Novo Agent Model Call", "Novo Subagent Model Call", "Novo Managed Browser", or "Novo Document Processor". It is constant per resourceKind and exists so you can populate OpenTelemetry gen_ai.request.model (and per-call dashboards) with an official value instead of a synthetic placeholder. The backing model and provider are never disclosed; modelLabel is not a model id.
Exact reconciliation with chargeId
Every usage event carries a chargeId (equal to its usageEventId). The same chargeId rides the run and step terminal stream chunks (data-novo-terminal / data-novo-step-terminal), so you can join a terminal receipt to its exact debit row instead of summing costs heuristically. Every step of a run shares that run's chargeId.
// Join a terminal stream receipt to its exact ledger debit.
const events = await novo.usage.events.list({ runId, meterKind: 'ai_inference' });
const debit = events.data.find((event) => event.chargeId === terminal.chargeId);
Defaults
/v1/usageandnovo.usage.aggregate()default togroupBy: 'day',fromat the start of the current UTC month, andtoat request time./v1/usage/eventsandnovo.usage.events.list()only applyfromandtowhen you pass them. Use explicit windows for reconciliation jobs.
groupKey semantics
| groupBy | groupKey |
|---|---|
| day | ISO date string YYYY-MM-DD (UTC bucket). |
| agent | agent ID. |
| thread | thread ID. |
| band | the band name, e.g. lite, core, or frontier, plus managed_runtime for thread-scoped runtime cost. |
| api_key | API key ID, managed_runtime for thread-scoped runtime cost, or null for dashboard / no-api-key runs. |
What costCents represents
The same final Novo-billed amount in fractional cents. Usage events are the exact debit rows. Run terminal usage and step terminal usage are receipts; use the event list when you need a single ledger shape across inference, browser, and workspace costs.
Errors
400 invalid_request_error: badgroupBy, malformedfrom/to, orfrom > to.