This guide covers spending caps and plans on a key, and the route policy that decides which model a request for model: "auto" runs.
Phantom AI stores and enforces all of this on its side. pai sends the settings and prints what Phantom AI returns. Where this guide describes how Phantom AI applies a setting, it describes the server's code at the time of writing.
A key has one spending cap for a period, plus an optional per-minute cap. budget and plan are two views of the same cap ( getBudget, BudgetResult):
pai budget # the cap, the per-minute cap, and spend against each
pai budget set --budget 20 --rate 0.5 # a cap for the period, and a per-minute cap
pai budget clear # remove both caps
pai plan # the cap as a plan: amount, spent, pace, per day left, days left
pai plan set --amount 20 --days 30 # $20 over 30 days
pai plan set --amount 20 # $20 over one calendar month
pai plan clear # remove the plan
budget set sends budget_usd and rate_usd_per_min ( setBudget). plan set sends budget_usd and period_days, with null for a calendar month ( setPlan). Both go to PATCH /key/budget.
On Phantom AI's side:
budget set and budget clear send no period_days, so a length set with plan set --days stays. The period is a calendar month only while no length is set.budget_exceeded. The credit on the key stays. A route policy with on_empty: "cheapest" lets auto keep running on the cheapest model instead.plan reports a pace. Phantom AI draws an even line from the start of the period to the end: by halfway through, an even spend has used half the amount (allowed_so_far_usd). The pace reads ahead when the spend passes that line plus some slack (one day's share, or a tenth of the period if that is shorter), and on_pace otherwise. Once ahead, it stays ahead until the spend falls back under the line itself, so a key spending right at the line doesn't flip on every call. daily_allowance_usd spreads what is left over the days left.
The MCP tools are get_budget, set_budget, plan_status and set_plan.
A request for model: "auto" needs a route policy on the key. Without one, Phantom AI refuses it. A policy names the models auto may run and the rules that pick one ( RoutePolicy, RouteRule):
| Field | Meaning | Phantom AI's default |
|---|---|---|
models |
the models auto may run; the first is the default |
required |
applies_to |
auto routes only requests for auto; all routes named models too |
auto |
rules |
checked in order; the first one that matches picks the model | none |
on_empty |
when the cap is used up: stop refuses, cheapest runs the cheapest model in models |
stop |
fallback_on_error |
if the chosen model fails, try the other models | false |
stick_minutes |
keep a conversation on its model this many minutes after its last request; 0 turns it off |
5 |
stick_by_prompt |
recognise a conversation by its opening when it sends no x-phantom-session header |
true |
Each rule has one condition in if and a model in use:
| Condition | Matches when |
|---|---|
pace |
the plan's pace is on_pace or ahead (never, with no plan) |
budget_left_pct_below |
less than this percent of the cap is left |
days_left_below |
fewer than this many days are left in the period |
has_tools |
the request does (true) or doesn't (false) send tools |
input_tokens_over |
the prompt is estimated at more than this many tokens |
reasoning_requested |
the request does or doesn't ask for reasoning |
use names a model from models, or one of:
cheapest: the model in models with the lowest input plus output price,first: the first model in models,next: the model after the one requested in models, wrapping around; for auto, the second model.With no match, auto runs the first model, and a named model (under applies_to: "all") runs as asked.
pai route # show the policy
pai route set --models <model-a>,<model-b> # the first is the default
pai route set --on-empty cheapest --fallback # change two fields, keep the rest
pai route set --file policy.json # replace the whole policy
pai route rule add --if pace=ahead --use cheapest # add a rule at the end
pai route rule add --if has_tools=true --use first --at 1 # add it first
pai route rule rm 2 # remove rule 2
pai route clear # remove the policy
route set with flags reads the current policy first. When one exists, pai sends only the fields you gave, and Phantom AI keeps the rest ( patchRoute). When none exists, pai sends the fields as a new policy ( putRoute), so the first route set needs --models. --file always replaces the whole policy.
--fallback and --stick-by-prompt turn a field on; follow them with false to turn it off.
route rule add needs a policy already. pai reads --if name=value into one condition: true and false become true and false, a number becomes a number, and anything else stays text ( parseCondition). --at n puts the rule at position n, counting from 1. pai then sends the whole new list of rules.
Phantom AI checks every policy before saving it: from one to ten models, up to 20 rules, one condition per rule, and a use that names one of models or cheapest, first or next. A child key can read its policy but not change it.
pai route test # what "auto" would run now, and the rule that picked it
pai route test --model <id> # the same for a named model
route test runs Phantom AI's router against the key's current cap and pace. Phantom AI calls no model and charges nothing ( testRoute). pai sends only the model name, so conditions about the request itself (has_tools, input_tokens_over, reasoning_requested) see an empty request. The MCP tool test_route does the same.
Switching models mid-conversation throws away the prompt cache, the discount a provider gives for repeating the start of a prompt. With stick_minutes above 0, Phantom AI keeps a conversation on the model it last ran unless the rules' pick costs less even against the cached price.
Phantom AI tells conversations apart by the x-phantom-session header your own inference requests send. Without the header, and with stick_by_prompt on, it uses a hash of the conversation's opening, held in memory. pai sends no x-phantom-session header: it makes no inference calls apart from verify. Add the header in the client your agent uses to call Phantom AI.
A request with the header x-phantom-route: off skips routing; a request for auto with that header is refused.
The MCP tools are get_route, set_route (pass policy: null to remove it, or merge: true to change some fields) and test_route.