Budgets & FinOps
Every call through the gateway is priced and attributed, and agents can carry budgets that warn or hard-block before overspend.Pricing
Cost is computed from a versioned pricebook shipped with KnoxCall (packs/ai-gateway-pricebook/), covering current Anthropic, OpenAI, and Gemini models with dated price rows. Two guarantees matter:
- Never silently $0. If a model isn’t in the pricebook, the call is recorded with
cost_source = 'unpriced'and surfaced through anunpriced_request_count— never booked as free. Provider inference falls back across providers rather than dropping to zero. - No remote config fetch. The pricebook is local — there is no run-time call to a third-party price feed. A weekly in-process check flags any observed-but-unpriced models via an alertable
ai_gateway.unpriced_modelaudit event, so you update the manifest deliberately.
cost_source (pricebook / unpriced / cache_hit).
Embedding calls are metered too
If an agent runs the semantic cache (cache_mode: "semantic"), every cache lookup embeds the request — which is a
real, billable provider call. Those calls are metered: they get their own
usage rows, they appear in your exports, and they count against the agent’s
budget and against a token’s signed daily caps.
They were not, until 2026-08-28. If you turned semantic caching on before then,
expect your row count and your metered spend to rise: the money was always being
spent, and only the accounting changed. A budget can now trip on cache traffic.
One deliberate asymmetry: under a token carrying a signed max_cost_per_day_usd,
an embedding on a model that is not in the pricebook is not purchased at all.
Semantic matching degrades to exact matching rather than making a spend the cap
has no way to count. Since 2026-09-08 that degrade is no longer silent: the
request’s usage row records it, and the Agent’s Budgets & cache tab counts
it and names the reason.
Budgets
Attach a budget to an agent with a daily and/or monthly USD cap and an overage action:
A blocked call gets HTTP 429, not 402:
X-Knox-AI-Budget-Pct alongside it. (Earlier revisions of this page said
402. 429 is what the gateway has always sent — treat a budget block as a
back-off signal, and reach for the same retry logic you use for a rate limit.)
X-Knox-AI-Budget-Pct carries the current utilization whenever there is something
to say — over the cap on a warn agent, or from 95% of it on any agent.
X-Knox-AI-Budget-Warning is set only by the warn action at the cap itself.
A call comfortably inside its budget gets neither header, so treat their absence
as “nothing to report”, not as “no budget configured”.
Budgets are stored in a cross-worker store (Redis when enabled) so the cap holds
across all workers, not per-process.
fallback is enforced. Earlier revisions of this page said it was not, and
that was true until 2026-08-25: the handler branched only on block and warn,
so an agent set to fallback proceeded on its expensive model at full price.
It now serves the request from the agent named by fallback_agent_id — see
Falling back to a cheaper agent for what is
and is not swapped.Failover chains (a different kind of fallback)
Not to be confused with the budget action above, and this one is implemented. An agent can carry an ordered list offallback_route_ids. When the primary
upstream answers 5xx (or the connection fails), the gateway tries each fallback
route in order and serves the first non-5xx response.
Each hop is a real, separately-authorised call, not a retry of the same one:
- the fallback route resolves its own credential, so provider A’s key is never sent to provider B, and provider B’s linked-secret domain lock is checked;
- a fallback the presenting token’s scope does not cover is skipped, so a 5xx cannot become a way to reach an upstream the token was never granted;
- the fallback is loaded in the same environment as the primary, so a
kp_test_token cannot reach a Live route by way of a failure; - the usage row records the route that actually served, so cost lands against the provider that did the work.
Falling back to a cheaper agent
Setbudget_overage_action: "fallback" and fallback_agent_id to another agent
of yours. Past the cap, the request is served using that agent’s upstream route
and default model — the two things that determine cost — and the response
carries X-Knox-AI-Budget-Fallback: <slug> plus
X-Knox-AI-Budget-Fallback-Model so your client can see which agent answered.
Your policies do not change. The firewall verdict, the PII stack and the tool
allowlist of the agent you addressed stay in force; the fallback agent supplies
only the route and the model. If it supplied its policies too, spending your own
budget out would be a way to reach a laxer configuration — a control anyone could
switch off with a credit card.The fallback route must be in the same data space (Live or Test) and, if the
addressed agent is region-pinned, in the same
region. A fallback that fails either check is not used.
X-Knox-AI-Budget-Fallback: unavailable. It
is never silently served at full price.
Caps carried by the token itself
An agent budget is set by you, the operator, on the agent. A capability scope can additionally carrymax_cost_per_day_usd and max_tokens_per_day on one
token, so a single credential can be confined below the agent’s own cap. These
are HMAC-signed fields of the token — editing them requires the tenant master
key — and they are enforced on every /v1/ai call before the upstream is dialled.
Over a token cap the gateway answers HTTP 429 with:
There is no API today that mints a token with these fields set. Both mint
endpoints —
POST /v1/ai-gateway/agents/{agentId}/tokens and the admin twin —
deliberately stamp an empty scope, because neither offers a parameter to set
one and inventing a restriction the operator never asked for is worse than no
restriction at all. The one path that produces a scoped token today is an
OIDC binding,
whose scope you author on the binding — and which now refuses a scope that
narrows nothing. An explicit scope parameter on mint is planned and not shipped.Attribution
Every call is attributed so you can build showback/chargeback:-
User — pass
X-KC-Useron the request, set to the person’s key in your workspace directory:- for someone your identity provider provisions over SCIM, their SCIM
externalId— or theiruserName, if your IdP sends noexternalId; - for any other member, their KnoxCall sign-in email.
X-Knox-AI-Attribution: user_unregisteredand the usage row is left unattributed. The same happens once a person’s membership ends — deprovisioned by your IdP or removed from the team page — so spend is never billed to someone who has left. Their earlier usage keeps its attribution. - for someone your identity provider provisions over SCIM, their SCIM
-
Team — the directory team for that user, unless the request names another
with
X-KC-Team. - Model and agent — recorded automatically.
The X-KC-Team override
X-KC-Team bills one call to a cost centre other than the user’s default team —
a contractor working across projects, a batch job charged to whoever asked for it.
It is a client header, so the gateway resolves it before it can reach a ledger:
- it must be a UUID, and
- it must name a team your own directory uses.
X-Knox-AI-Attribution: team_malformed or
team_unknown, and the claimed value is logged. A request that sends X-KC-Team
with no X-KC-User is attributed to the team alone — a service account or CI job
has a cost centre and no employee behind it.
How people get into the directory. SCIM provisioning (Enterprise) adds each
person the moment your IdP creates them — the directory entry and the workspace
membership are written together, and a later rename or
externalId change in
the IdP moves the same entry rather than creating a second one. A member who is
not provisioned over SCIM gets an entry the first time they connect their own
account to an MCP server or approve a device, keyed on their KnoxCall email. If
they later change that email, the same entry moves to the new address and the
old address stops identifying them — at once in every workspace they belong to,
and in a workspace they have left, at the moment they rejoin it. If the new
address is already another person’s directory key in your workspace, their entry
stops answering to either address — the old one comes back user_unregistered
and the new one keeps naming the person who holds it — until that key changes
and they next connect an MCP account or approve a device.
Signing in with SAML SSO does not by itself create one. There is not yet a way to
add directory entries for people who are not workspace members (your own
application’s end users); until there is, attribute those calls by
agent-per-team and group spend by agent. X-KC-Team resolves only against
teams already present in the directory, and nothing assigns a person a team yet,
so today it comes back team_unknown.cost_source that the daily rollup drops) and surfaces unpriced requests so a pricing gap is visible rather than hidden.
Showback / chargeback exports
Export aggregated spend for any period, grouped by the dimension your finance team bills on — user, team, agent, model, provider, or a custom tag (cost_center / project / …). Tag agents via their tags field, then group
by tag:<key>.
requests, input_tokens, output_tokens, cost_usd, and
unpriced_requests (so a pricing gap is visible, never hidden). group_by is a
strict whitelist and a tag key is bound as a parameter — no injection surface.
Provable budget enforcement
“Budgets” that only warn are theatre. KnoxCall’s enforcement is covered by a test suite that exercises each overage action against the budget store:- block — once the daily/monthly cap is reached, further calls are rejected before reaching the upstream (no spend past the cap).
- warn — the call proceeds with
X-Knox-AI-Budget-Warning; nothing is silently dropped. - per-call token ceiling — a request asking for more output than the agent’s
budget_per_call_max_tokensis rejected up front, so one call can’t blow the budget in a single shot.
cost_source = 'cache_hit', and never charge the budget.
Spend-spike detection
Budgets catch a hard ceiling. A spend spike is the softer failure — a 5x jump well under the cap, arriving on the invoice a month later. A daily job sweeps each agent’s rolled-up daily spend and raises an alertableai_gateway.spend_spike
audit event when the most recent day clears all four of:
The baseline is a median + MAD (median absolute deviation), not a mean and
standard deviation. The daily rollup gives at most ~30 points per agent, and a
single earlier spike would poison a mean-based baseline into never firing again.
The event carries the agent id, the day’s spend, the baseline and the multiple.
Watch it with an alert rule on the
ai_gateway_event type:
POST that to /admin/alerts with your tenant header. The same rule shape works
for any AI-gateway audit action — ai_gateway.unpriced_model,
ai_gateway.geo_velocity_anomaly, ai_gateway.canary_leak.
ai_gateway_event is not yet offered in the admin UI’s alert-type picker, so this
rule has to be created through the API. The job that raises the event runs once a
day on a single scheduler process — it needs at least a week of usage before it can
say anything, and it reads the hourly usage rollup rather than raw requests.Where to look
- Usage tab — spend and request counts by period, with attribution + export.
- Models tab — the effective pricebook and each model’s allowlist status.
- OpenTelemetry —
knoxcall.ai_gateway.cost_usdon every span for your own dashboards.