Skip to main content
The engine prices a workflow before it runs, from each model’s catalog price and the output limit each step declares (max_tokens). A spend cap turns that price into a rule: a run whose cost floor is above the cap never starts.
spend.mts
This runs offline, with no Anthropic key: the refusal comes before any access check or provider call.

The rules, on the local engine

maxCostUsd is the run’s budget over metered spend, in USD, at the catalog’s list prices.
  • Before the run. A cost floor above the cap makes run() throw NikaOperationError with code NIKA-1709 (status 2).
  • A cloud model the catalog cannot price is refused before any call too, with or without maxCostUsd: the engine cannot bound spend it cannot meter, and an unpriced model never counts as $0. The engine names no code for this refusal, so code is the SDK’s run_refused, status is 3 and the message starts price unknown:. The engine your app starts has no terminal to ask; nika run in an interactive terminal asks once for a cost review instead.
  • During the run. The call that crosses the cap completes and counts, nothing new starts, unstarted tasks are cancelled, and run.result() resolves failed with NIKA-1704 and no outputs.
  • Local and mock models are never blocked. They are unpriced, not free: the settlement reports them as unpriced.
  • Without maxCostUsd, the ceiling: in the project’s nika.yaml applies; maxCostUsd wins over it.
  • The floor is an output-token estimate (max_tokens × output price). Prompt tokens are not priced statically, and a wide wave of parallel tasks can overshoot by up to that wave’s spend before the crossing is seen: max_parallel: narrows that window.

model replaces only the default

The model option replaces the workflow’s envelope model: only. A task that pins its own infer.model, and a child workflow it invokes, keep their models: model: 'mock/echo' does not make those calls offline. Keep maxCostUsd: 0 next to it, so a priced pin is refused (NIKA-1709) instead of spending. A local-model pin still runs.

On nika serve

There is no per-run maxCostUsd over HTTP: the SDK refuses it with NikaCompatibilityError (capability: 'runOptions') instead of dropping it. The server bounds spend itself:
  • nika serve --run-cost-ceiling <USD> caps every job your code submits: 1 USD by default; none removes the cap explicitly.
  • A job whose floor exceeds it is admitted and then settles failed with error.code NIKA-1709; run() does not throw.
  • A schedule carries its own maxCostUsd, which must be positive (Schedules).
Catalog prices are estimates, not invoices: your provider meters and bills the calls. Cost honesty explains how nika check reads them.

Results

How a budget stop reads in the settlement.

CI gate

Rehearse on the mock model before a protected job spends.