budget
Poll budget state, run pre-flight guardrail checks (singly or in batches), settle reservations with attribution, configure limits, and restore threshold defaults
budget is the guardrail command group. Its subcommands split into three jobs:
| Command | Purpose |
|---|---|
budget status | Poll budget state without authorizing spend. Safe to run on a loop. |
budget check | Reserve budget immediately before one un-instrumented provider call — or a batch of them with --batch. |
budget confirm | Settle a reservation after that call, reporting the usage it actually consumed, and record attribution. |
budget set | Change the limit, period, or enforcement mode. |
budget restore-defaults | Restore the default percentage thresholds, keeping every other rule. |
budget rules, budget rule, budget threshold | Inspect and manage alert and enforcement rules. Documented on budget rules. |
Poll with status, check immediately before un-instrumented spend, and confirm only a non-null reservation. Calls you make through the Solwyn SDK are already metered — check and confirm are for spend the SDK does not wrap, such as a raw provider call in a script or an agent's own tool. Spend that happened with no reservation at all is recorded afterwards with record.
Run identity and tags
check, confirm, and record share two attribution inputs. Set them once per job and every command picks them up:
| Input | Flag | Environment | Purpose |
|---|---|---|---|
| Run id | --agent-run-id | SOLWYN_AGENT_RUN_ID | Groups a job's checks and spend into one agent run, enables per-run caps, and lets an operator stop it. Up to 256 characters. |
| Tags | --tag key=value, repeatable | SOLWYN_TAGS=key=value,key2=value2 | Attribution labels. Up to 10 keys; key 1–64 characters, value up to 256, no NUL. Entries split on the first =; commas cannot appear in values; a flag overrides a matching environment key. |
Supplying a run id is what opts a check into run-scoped enforcement and the operator-stop directive. Omit it and the check is project-only.
budget status
Read the current budget without reserving or authorizing anything.
solwyn --project <project_id> budget status [--max-utilization FLOAT]| Option | Type | Default | Purpose |
|---|---|---|---|
--max-utilization | percent 0.0–100.0 | none | CI deploy gate: exit 4 when utilization is at or above this percentage. |
| Contract | |
|---|---|
| Auth | any (project key or JWT) |
| Exit codes | 0 success · 1 generic/API · 2 usage · 3 auth · 4 utilization gate · 5 not found |
| JSON schema | solwyn.budget-status.v1 |
--max-utilization turns status into a deploy gate: the command still prints the budget, then exits 4 when utilization is at or above the threshold, so a pipeline step can block a rollout that would run over budget.
$ solwyn --project proj_0123456789abcdef01234567 budget status --max-utilization 90
Field Value
Project proj_0123456789abcdef01234567
Budget limit 100.00
Current usage 75.00
Remaining budget 25.00
Budget period monthly
Budget mode hard_deny
Utilization 75.00%
Outage overspend ceiling 0.00
Lease float (included in usage) —
Runaway run 50.00 (hard_deny)
Scoped tag: team=research — / 25.50 (hard_deny)
$ echo $?
0Two rows are always present below utilization. Outage overspend ceiling is how far the SDK may spend past the limit while Solwyn Cloud is unreachable. Lease float is budget currently held by run-scoped leases, already counted inside current usage. Then one row per runaway-run or scoped-budget rule; the other rule types appear only in the JSON thresholds array.
{"schema":"solwyn.budget-status.v1","outage_overspend_ceiling_usd":0.0,"lease_float_usd":null,"thresholds":[],"project_id":"proj_0123456789abcdef01234567","budget_limit":100.0,"current_usage":75.0,"remaining_budget":25.0,"utilization_pct":75.0,"budget_mode":"hard_deny","budget_period":"monthly"}budget check
Reserve budget for one caller-made, un-instrumented provider call. Run it immediately before the call — do not poll with it.
solwyn --project <project_id> budget check \
--estimated-input-tokens <int> --model <name> --provider <provider> \
[--estimated-output-tokens <int>] [--tag key=value]... [--agent-run-id <id>] \
[--fallback-provider <provider> --fallback-model <name>]... \
[media quantities...] [--no-record]
solwyn budget check --batch <file>|- [--no-record] [--estimated-output-tokens <int>]| Option | Type | Default | Purpose |
|---|---|---|---|
--estimated-input-tokens | integer ≥ 0 | required | Estimated input tokens for the pending call. |
--model | string | required | Primary provider model name. |
--provider | provider enum | required | Primary provider (openai, anthropic, google, bedrock, and the other supported identifiers). |
--estimated-output-tokens | integer ≤ 100,000,000 | none | Output upper bound, used only in a denial's savings receipt. |
--tag | key=value, repeatable | none | Attribution tag. Overrides a matching SOLWYN_TAGS key. |
--agent-run-id | string ≤ 256 | SOLWYN_AGENT_RUN_ID | Run identity for per-run enforcement. |
--fallback-provider | provider enum, repeatable | none | A fallback provider, in attempt order; pair each with a --fallback-model. Up to eight pairs. |
--fallback-model | string, repeatable | none | Fallback model aligned with each fallback provider. |
--no-record | boolean | false | Skip the denial receipt. |
--batch | path or - | none | A JSON array of check bodies from a file or stdin. Excludes every per-check flag. |
For image, video, audio, and speech calls, give the API the quantities it prices instead of tokens:
| Option | Type | Purpose |
|---|---|---|
--image-count | integer | Images the call generates or edits. |
--generation-count | integer | Per-video, per-generation, or per-song units. |
--video-seconds, --audio-seconds | float | Duration billed, in seconds. |
--input-characters | integer | Speech-synthesis input length. No content is sent, only the count. |
--resolution, --quality | string ≤ 32 | Pricing variant, for example 1024x1024 or hd. |
--is-estimated | boolean | Mark the media quantities as estimates rather than measured. |
The three primary flags are required for a single check and forbidden with --batch; missing them exits 2 with missing_budget_check_options. Each --fallback-provider must be paired with one --fallback-model in the same order. Every check requests price hints.
| Contract | |
|---|---|
| Auth | project key with full scope (a JWT is rejected; a read-only key exits 3 with read_only_key) |
| Exit codes | 0 allowed or fail-open · 1 generic/API, or a batch item error · 2 usage · 3 auth · 4 budget denial · 5 not found |
| JSON schema | solwyn.budget-check.v1; solwyn.budget.check.batch.v1 with --batch |
Read the result before you spend:
allowed: true,reservation_idset — reserved. Make the call, then settle it withbudget confirm.allowed: true,reservation_id: null— fail-open. The check could not reserve (Cloud API unreachable, or a model the API declines to price), so the call is allowed unreserved. The CLI printsfail-open, unreserved — skip confirmto stderr. Make the call and skipconfirm.allowed: false,denied_by_perioda period — denied, exit4. Only ahard_denybudget denies a check. Do not make the call.allowed: false,denied_by_period: "tag"— a scoped tag cap denied it; stderr saysdenied by tag cap. Do not spend under that tag.allowed: false,reason: "run_terminated"— an operator stopped the run. End the job. Do not retry under a new run id.
$ solwyn --project proj_0123456789abcdef01234567 budget check \
--estimated-input-tokens 1500 --model gpt-4o --provider openai --fallback-provider anthropic --fallback-model claude-sonnet-4
Field Value rel. price
Allowed yes -
Reservation ID res_01JZEXAMPLE -
Project proj_0123456789abcdef01234567 -
Budget mode hard_deny -
Budget limit 100.00 -
Current usage 18.75 -
Remaining budget 81.25 -
Denied by period - -
Provider anthropic 1.15
Provider openai 1The rel. price column appears when the API returns price hints. They are relative, not currency: the cheapest positively priced lane is 1, a local zero-cost lane is 0, and a 1.15 costs fifteen percent more than the cheapest. Treat them as advice, not permission to spend. In JSON they are the price_hints object.
Denial receipts
A denied check records a savings receipt by default — an attribution event with status: budget_denied, the run id, tags, and the output bound from --estimated-output-tokens — so the dashboard's blocked-call savings stay accurate. The receipt gets a fresh call_id, which the payload carries along with a recorded block; quote that id when referring to the attempt. --no-record skips the receipt. Recording never changes the decision: if the receipt fails, stderr warns and the exit code is still 4.
A denial for a stopped run renders one extra row:
Denied by period run_stopped
Reason run stopped by an operator — do not retry under a new run idBatch checks
--batch reads a JSON array of check bodies — the same fields as the single-check flags, in snake case — validates every item locally, then submits them in chunks of 32. Each allowed item holds its own reservation and must be confirmed after its own call. Every item counts against your plan's checks_per_minute.
$ solwyn --project proj_0123456789abcdef01234567 budget check --batch checks.json
Index Outcome Details Call ID Recorded
0 allowed res_1 - -
1 unknown_model Model cannot be priced - -
2 denied monthly 12345678-1234-1234-1234-123456789abc ingestedAny denied item exits 4, which takes precedence; an item error with no denial exits 1. A failure partway through still prints the completed results, adds failed_chunk_start and total_checks to the payload, and explains on stderr which reservations are known and which are unknown — do not replay completed checks. --no-record and --estimated-output-tokens are the only flags that may accompany --batch; a run id comes from the items or from SOLWYN_AGENT_RUN_ID.
Every legacy batch 503 leaves the failed chunk's reservation state unknown, even in the first chunk and even for Core's fixed busy detail. Earlier items may already have committed. Later chunks were not submitted; do not assume rollback or blindly replay the failed chunk. Single checks and MCP durable admission have a narrower recognized no-reservation guarantee. See Scripting and journal recovery.
budget confirm
Settle one reservation after the call, reporting the usage it actually consumed. Confirm exactly once, and only when check returned a non-null reservation. By default, confirm also records attribution for the call under the same call id — so do not additionally run record for it.
solwyn budget confirm \
--reservation-id <id> --call-id <uuid> --model <name> --provider <provider> \
[token counters...] [media quantities...] [attribution...] [--no-record]
solwyn budget confirm --record-only <event.json>| Option | Type | Default | Purpose |
|---|---|---|---|
--reservation-id | string | required | Reservation returned by the immediately preceding check. |
--call-id | canonical lowercase UUID | required | Your identifier for the provider call. Generate one with uuidgen | tr A-F a-f. |
--model | string | required | Model that actually served the call. |
--provider | provider enum | required | Provider that actually served the call. |
--status | success | error | success | How the call ended. |
--latency-ms | float ≥ 0 | 0 | End-to-end latency. |
--modality | text | image | audio | video | embedding | text | The completed call's modality. |
--is-provider-fallback | boolean | false | A fallback provider served the call. |
--provider-region | string ≤ 32 | none | Serving cloud region, when relevant. |
--service-tier | service-tier enum | none | Provider service tier used (auto, default, flex, scale, priority, standard, optimized). |
--agent-run-id, --parent-run-id, --run-name | string | SOLWYN_AGENT_RUN_ID for the run id | Run attribution. |
--tag | key=value, repeatable | none | Attribution tags; overrides SOLWYN_TAGS. |
--attempt-index | integer ≥ 0 | 0 | Observed attempt index. Keep it identical when retrying a recording. |
--is-estimated | boolean | false | Mark the supplied token and media quantities as estimates rather than provider-reported. |
--no-record | boolean | false | Settle without recording attribution. For replaying a settlement after a lost reply. |
--record-only | path | none | Ingest the exact event file a previous confirm returned, with no settlement and no other flags. |
Thirteen token counters carry the usage breakdown. Each is an integer from 0 to 100,000,000, defaulting to 0 — supply only the ones the response reported:
| Counter | Counts |
|---|---|
--input-tokens | Input (prompt) tokens |
--output-tokens | Output (completion) tokens |
--cached-input-tokens | Input tokens served from cache |
--cache-creation-5m-tokens | Cache-creation tokens, 5-minute TTL |
--cache-creation-1h-tokens | Cache-creation tokens, 1-hour TTL |
--reasoning-tokens | Reasoning tokens |
--audio-input-tokens | Audio input tokens |
--audio-output-tokens | Audio output tokens |
--image-input-tokens | Image input tokens, already counted within --input-tokens |
--image-output-tokens | Image output tokens, already counted within --output-tokens |
--accepted-prediction-tokens | Accepted predicted-output tokens |
--rejected-prediction-tokens | Rejected predicted-output tokens |
--tool-use-input-tokens | Tool-use input tokens |
The observed media quantities — --image-count, --generation-count, --video-seconds, --audio-seconds, --input-characters, --resolution, --quality — mirror the check flags. Omit a quantity when it was unobservable; pass 0 only when you observed none.
| Contract | |
|---|---|
| Auth | project key with full scope (a JWT is rejected) |
| Exit codes | 0 settled · 1 generic/API · 2 usage · 3 auth · 5 reservation not found |
| JSON schema | solwyn.budget-confirm.v1 |
confirm never exits 4. Running it without --reservation-id exits 2 with reservation_required and the hint that a null reservation means fail-open — skip confirm.
$ solwyn budget confirm \
--reservation-id res_01JZEXAMPLE --call-id 4b1e8a20-1c9d-4f0a-9b7e-2f6c1a3d5e80 \
--model gpt-4o --provider openai --input-tokens 1487 --output-tokens 352
Field Value
Reservation ID res_01JZEXAMPLE
Call ID 4b1e8a20-1c9d-4f0a-9b7e-2f6c1a3d5e80
Settlement confirmed
Recorded ingestedSettlement is confirmed when this invocation saw the API accept the settlement, unknown when the reply was lost, and not_submitted under --record-only. Recorded is attribution status; inspect it separately from settlement. A known confirmed settlement stays confirmed when attribution fails. Ingested or call-ID deduplication acknowledges the event; a legacy_key duplicate remains failed in CLI and unknown in MCP. Full results can include recording_retry, the exact event prepared by this invocation.
Recovering from a lost reply
Settlement and recording are two steps, and the CLI is explicit about which one is in doubt:
- A transport error during settlement leaves the outcome unknown. Within the 3,600-second project-bound receipt window, replay identical settlement inputs with
--no-record; after a known 204, record only the first exactrecording_retrypreviously saved. Never replace it with the settlement-only replay's unused event. Ordinary CLI transport errors do not return that prepared event, so original flags cannot reconstruct its unseen timestamp/SDK instance. A wholly lost MCP reply or compact result that omits it has the same limit. Inspect settlement and attribution instead of inventing a replacement event. - A
5means the reservation expired or was already consumed. Preserve the completed call's identity and usage and inspect settlement and attribution separately. Do not reserve again for a completed call. - A recording failure after a confirmed settlement warns on stderr and leaves the settlement intact. Save the returned
recording_retryevent and ingest it later with--record-only.
The settlement receipt does not compare payloads, correct a completed settlement, or reconstruct attribution. Record-only sends the retained event unchanged, applies no ambient run/tags, and reports settlement=not_submitted; keep the earlier confirmed state separately. See observed media and recovery.
budget set
Change selected budget settings; every other setting — including every rule — is preserved.
solwyn --project <project_id> budget set [--limit FLOAT] [--period daily|weekly|monthly] [--mode alert_only|hard_deny]| Option | Type | Default | Purpose |
|---|---|---|---|
--limit | float > 0 | none | Budget limit in USD. |
--period | daily | weekly | monthly | none | Budget period. |
--mode | alert_only | hard_deny | none | Enforcement mode. |
At least one of --limit, --period, or --mode is required. Running set with none exits 2.
| Contract | |
|---|---|
| Auth | owner/admin JWT (budget.write; a project key is rejected) |
| Exit codes | 0 success · 1 generic/API · 2 usage · 3 auth · 5 not found |
| JSON schema | solwyn.budget-set.v1 |
$ solwyn --project proj_0123456789abcdef01234567 budget set --limit 200 --mode hard_deny
Field Value
Project proj_0123456789abcdef01234567
Budget limit 200.00
Current usage 23.47
Remaining budget 176.53
Budget period monthly
Budget mode hard_deny
Sub-period -
Sub-limit -
Threshold rules 3
Utilization 11.74%set reads the current configuration, merges your changes, and writes the whole thing back. If the write answers 503, the CLI re-reads the budget and compares: a match counts as success, and a mismatch exits 1 with budget_update_indeterminate and the hint to run budget status before retrying. A script must not blindly retry that error.
budget restore-defaults
Restore the default set of percentage thresholds, then print every retained rule.
solwyn --project <project_id> budget restore-defaults| Contract | |
|---|---|
| Auth | JWT (owner or admin) |
| Exit codes | 0 success · 1 generic/API · 2 usage · 3 auth · 5 not found |
| JSON schema | solwyn.budget.rules.v1 |
Only the budget_threshold rules are reset. Runaway-run, scoped-budget, forecast, and anomaly rules survive with their paused and snoozed state intact; the limit, period, and mode are untouched. There is no confirmation prompt. The output is the same rules table as budget rules list.
Exit code 4 — three triggers
Three commands can exit 4, for different reasons:
budget checkexits4on a denial — ahard_denybudget, a scoped cap, or an operator stop refused the reservation.budget check --batchexits4when any item is denied, ahead of any item errors.budget statusexits4on the--max-utilizationgate — utilization reached the threshold you set.
A check denial means "do not spend"; a status gate means "utilization is high." Handle them per command, not by the code alone.
budget uncounted
solwyn --json --project proj_0123456789abcdef01234567 budget uncountedReads cumulative calls/tokens reported as admitted while Solwyn was unreachable, plus nullable last_reported_at. This is an advisory, potentially overcounted, unpriced upper bound. It is never priced spend, permission to spend, or an exact count of lost traffic. Known zero is zero; a 503 means unknown, not zero, and follows shared service-busy read guidance. The CLI does not itself admit offline calls or report advisory uncounted traffic.
Auth is a project key or JWT with access to the selected project. Output uses solwyn.budget.uncounted.v1; exits are 0 success, 1 API/invalid response, 2 usage, 3 auth, 5 not found. MCP provides budget_uncounted in both profiles.
Related
- budget rules — thresholds, runaway-run caps, scoped budgets, anomaly rules, and snoozing
record— attribute spend that had no reservationruns— inspect and stop the runs a run id creates- Budget enforcement — how the SDK checks and settles budgets automatically
status— budget plus provider health in one view- Scripting — JSON envelopes and the full exit-code contract