Cereal docs
Deploy and monitor

Usage and spend caps

Read model usage, track estimated cost, and set environment and API key budgets.

Every turn writes a session.usage event, and those are folded into rollups you can query. usage.* costs only read, so a client can render its own budget without holding control.

const period = { from: "2026-09-01", to: "2026-09-30" }

// The environment, with each cap beside its meter.
const usage = await client.usage.environment({ period, groupBy: "agent" })
usage.totals.turns
usage.totals.costUsd
usage.spend.tokensPerMonth

// Per key, with each key's own cap.
await client.usage.keys({ period })

// One session, and its sub-agents.
const tree = await client.usage.session({ id: sessionId })
tree.own
tree.descendantUsage

groupBy is day, agent, key or identity. The last breaks the period down by the identity each session asserted, as scope:id:

const byPerson = await client.usage.environment({ period, groupBy: "identity" })
// [{ key: "user:ada", totals: { … } },
//  { key: "anonymous", label: "No identity asserted", totals: { … } }]

Sessions without an asserted identity appear under anonymous. Older usage without that dimension appears under unknown. Use scope:id to join an identity breakdown to your application's users. Breakdowns are alternate views of the same totals.

costUsd is a lower bound whenever unpricedCalls > 0. A model Cereal's price table does not know contributes nothing rather than a guess. Cost is computed per model call rather than from a turn's summed tokens, because a turn that fell down a fallback ladder used two models at two prices.

Caps

await client.environments.update({
  update: { slug: "dev", spend: { costUsdPerMonth: 50, tokensPerMonth: 20_000_000 } }
})

await client.apiKeys.update({ update: { id: keyId, spend: { costUsdPerMonth: 10 } } })

A key's cap may not exceed its environment's (SpendCapExceedsParentError).

Caps are checked in exactly three places and nowhere else: sessions.create, sessions.send, and when an agent spawns a sub-agent.

A spend cap does not interrupt active turns or block approval decisions, so usage can exceed the cap during work already in progress.

try {
  await client.sessions.send({ id, message })
} catch (error) {
  if (error instanceof SpendLimitExceededError) {
    // `resetsAt` is the only thing that changes the answer. Retrying before it is a
    // retry that cannot succeed.
    console.warn(`${error.scope} out of ${error.metric} until ${error.resetsAt}`)
  }
}

SpendLimitExceeded returns HTTP 402. Raise the cap or wait for the reported reset time before starting more work.

Sub-agents cannot be used to evade a budget

A session's descendantUsage is everything its tree spent, and limits.maxInputTokensPerSession is checked against the total on the root session. A session.usage event carries its descendants' usage too, and the rollup sums only own usage, because counting both would bill every sub-agent turn twice.

Traces

Separately from cost, every turn writes spans: the turn, each model request, each tool call, each compaction. Read them with traces.forSession; see traces.

On this page