budget rules
List, add, remove, pause, snooze, and preview the six kinds of budget rule, and snooze percentage thresholds
A budget rule watches one condition on a project and either notifies or enforces when it trips. Three subgroups of budget manage them:
| Command | Purpose | Auth |
|---|---|---|
budget rules list | List every rule with its routing, paused state, and snooze. | any |
budget rule add | remove | pause | resume | snooze | unsnooze | Manage one rule by its key. | JWT, owner or admin |
budget rule preview | Dry-run where a saved or draft rule would deliver. | any |
budget threshold snooze | unsnooze | Snooze a percentage threshold by its percent, not its key. | JWT, owner or admin |
Rules are the producers of alerts; the events they fire, with delivery receipts, are read with alerts, and the channels they route to are managed with channels. The dashboard shows the same rules on the project's Budget & Alerts tab — see Budgets and Alerts & notifications.
Two guidance lines print to stderr on every successful rule command, because they matter:
A snoozed hard_deny still denies; pause releases enforcement.
Rule keys are content-addressed; editing keyed fields changes the rule_key.The six rule types
--type | Condition flags | Enforces? |
|---|---|---|
budget_threshold | --percent 1–100 | No — notifies when spend crosses a percentage of the limit. |
runaway_run | --spend <usd>, optionally --calls <n> with --window <minutes> (1–1440) | With --mode hard_deny, denies a run's further calls at the cap. Default alert_only. |
scoped_budget | --scope model|provider|agent_run|tag, --match <selector>, --value <tag value> (tag scope only), --limit <usd>, --mode | --mode is required. A hard_deny scoped rule denies matching calls even on an alert_only project. |
forecast_overrun | --spend <usd> | No — fires when the month's projected spend exceeds the amount. |
daily_anomaly | --multiplier (> 1, ≤ 100), --lookback days (3–30, default 7) | No — a day's spend exceeds the baseline by the multiplier. |
unit_cost_anomaly | --multiplier, --lookback | No — cost per request drifts beyond the multiplier. |
Amounts are in USD. A runaway-run spend must be whole cents and at most 1,999,999.99; a scoped limit must be exact micro-dollars and at most 999,999.999999. A flag that does not belong to the chosen type is a usage error, exit 2, before any request. Anomaly rules arm only once a baseline has three days of data.
budget rules list
$ solwyn --project proj_0123456789abcdef01234567 budget rules list
Rule key Type Params Mode Paused Snoozed until Will send Channels Muted reason Usage / limit
budget:80 budget_threshold {"percent": 80} alert_only true 2026-07-11T12:00:00+00:00 false — email_off —
runaway:50 runaway_run {"call_threshold": 100, "spend_thr… alert_only false — false — email_off —
scoped:model:gpt-4o scoped_budget {"limit": 25.5, "match": "gpt-4o",… hard_deny false — false — email_off — / 25.5
A snoozed hard_deny still denies; pause releases enforcement.| Contract | |
|---|---|
| Auth | any (project key or JWT) |
| Exit codes | 0 success · 1 generic/API · 2 usage · 3 auth · 5 not found |
| JSON schema | solwyn.budget.rules.v1 |
Cells wrap in the real terminal; they are abridged here. Will send, Channels, and Muted reason come from each rule's routing — whether a firing would reach anyone, through which channels, and if not, why (severity_floor, event_filter, or email_off). Usage / limit is filled for scoped budgets only. In JSON each rule carries rule_key, type, its condition fields, channel_ids, routing, paused, and snoozed_until.
Last fired
solwyn --json budget rules list --with-last-fired--with-last-fired requires JWT even for project rules. It fetches latest events using read-only POSTs with at most 100 selectors per request, built from the rules already read. JSON maps rule keys to the latest firing event or null. Thresholds match by percent, so limit edits preserve their history. Core's default 30-day window and plan retention apply; null does not prove a rule never fired. Without the flag, there is no extra latest-events request. MCP list_budget_rules(with_last_fired=true) behaves the same way.
budget rule add
Append one rule, preserving the rest of the configuration.
solwyn --project <project_id> budget rule add --type <type> [condition flags] [--mode alert_only|hard_deny] [--channel <id>]...| 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 (the added rule only) |
$ solwyn --project proj_0123456789abcdef01234567 budget rule add --type runaway_run --spend 25 --mode hard_deny --channel ch_one
$ solwyn --project proj_0123456789abcdef01234567 budget rule add --type scoped_budget --scope tag --match team --value research --limit 50 --mode hard_deny
$ solwyn --project proj_0123456789abcdef01234567 budget rule add --type budget_threshold --percent 90add is a read-modify-write of the whole configuration, with safeguards a script should know:
- Duplicates are rejected by condition, not by full equality: a second
budget_thresholdat the same percent, a second runaway or forecast rule at the same spend, a second anomaly rule at the same multiplier, or a second scoped rule on the same selector, exits2even if its mode, limit, or lookback differ. Remove the old rule first. - Caps. A configuration holds at most 50 rules; your plan's
max_thresholds(seeplan) may be lower, in which case the API's upgrade response is relayed and the command exits1. - Concurrent edits abort. The CLI re-reads the configuration before writing. If it changed in between, nothing is sent and the command exits
1withbudget_configuration_changed— safe to retry. - A
503on the write is reconciled, not replayed. If a follow-up read matches, the add succeeded; otherwisebudget_update_indeterminateexits1, and you should runrules listbefore retrying.
The rule key is assigned by the API and is content-addressed: budget:80, runaway:50, scoped:model:gpt-4o. Change a keyed field and the key changes, so "editing" a rule is remove followed by add.
budget rule remove, pause, resume, snooze, unsnooze
Each takes the rule key as a positional argument.
solwyn --project <project_id> budget rule remove <rule_key>
solwyn --project <project_id> budget rule pause <rule_key>
solwyn --project <project_id> budget rule resume <rule_key>
solwyn --project <project_id> budget rule snooze <rule_key> --until <when>
solwyn --project <project_id> budget rule unsnooze <rule_key>| Command | Effect |
|---|---|
remove | Deletes the rule — enforcement and notifications both stop. |
pause | Silences notifications and releases enforcement. The only action that makes a hard_deny rule stop denying. Reversible with resume. |
resume | Restores notifications and enforcement. |
snooze | Silences notifications only. A snoozed hard_deny still denies. |
unsnooze | Clears the snooze. |
| Contract | |
|---|---|
| Auth | JWT (owner or admin) |
| Exit codes | 0 success · 1 generic/API · 2 usage · 3 auth · 5 not found |
| JSON schema | solwyn.budget.rule_action.v1 |
--until accepts a relative duration — +30m, +2h, +7d — or an absolute ISO 8601 timestamp with a timezone. A naive timestamp exits 2 with invalid_snooze_time. None of these commands prompt for confirmation.
$ solwyn --project proj_0123456789abcdef01234567 budget rule pause scoped:model:gpt-4o
Field Value
rule_key scoped:model:gpt-4o
paused True
deleted Falsebudget rule preview
See where a rule would deliver without saving anything.
solwyn --project <project_id> budget rule preview [<rule_key>] [--channel <id>]...
solwyn --project <project_id> budget rule preview --type <type> [condition flags] [--channel <id>]...| Contract | |
|---|---|
| Auth | any (project key or JWT) |
| Exit codes | 0 success · 1 generic/API · 2 usage · 3 auth · 5 rule not found |
| JSON schema | solwyn.budget.rule_preview.v1 |
Three forms: a saved rule by key, every saved rule with no arguments, or a draft built from --type and its condition flags. A key and --type together is a usage error. --channel overrides the channel list for the preview, so you can answer "would this reach on-call if I routed it to ch_two?" before committing.
$ solwyn --project proj_0123456789abcdef01234567 budget rule preview budget:80
Rule key Will send Email Channels Source Muted reason
budget:80 false — — project_default email_offbudget threshold snooze and unsnooze
Snooze a percentage threshold by its percent. Because the percent, not the rule key, identifies it, these survive a key change.
$ solwyn --project proj_0123456789abcdef01234567 budget threshold snooze 80 --until +2h
Field Value
snoozed_until 2026-09-07T22:00:00Z
$ solwyn --project proj_0123456789abcdef01234567 budget threshold unsnooze 80| Contract | |
|---|---|
| Auth | JWT (owner or admin) |
| Exit codes | 0 success · 1 generic/API · 2 usage · 3 auth · 5 not found |
| JSON schema | solwyn.budget.rule_action.v1 |
The percent must be 1 through 100; --until takes the same forms as rule snooze.
Related
budget— status, check, confirm, set, and restore-defaultsalerts— the events these rules fired and whether they were deliverednotifications— the channel ids rules route to- Budgets — runaway-run and scoped budgets from the dashboard, and what a denial looks like in the SDK