A budget the database refuses to break.
Stub sits between your AI agents and the money they spend. Every spend is recorded as a double-entry transaction, and one that would exceed your budget never commits. Here's the whole idea, top to bottom.
One budget, the whole fleet
You set a single company-wide budget and split it into teams and agents. A spend is bound by the tightest cap along that chain (agent, then team, then org), and all of it is checked inside one database transaction before any money moves.
Every spend is double-entry
A spend debits the agent's budget and credits the vendor in the same atomic transaction. The entries table is append-only and hash-chained, so it doubles as a tamper-evident audit log. Balances are derived from it, never edited by hand.
A breach fails the transaction
When two agents race the same dollars across regions, Amazon Aurora DSQL's optimistic concurrency control lets one commit and returns a serialization error (SQLSTATE 40001) to the other. Stub retries against the fresh balance or records a denial. The balance never goes negative. There's no window where an overspend is briefly real.
Policies and safety run on every spend
Per-transaction caps, rolling-window limits, vendor allow/blocklists, and approval thresholds are evaluated in the same transaction. A velocity breaker auto-freezes a runaway agent, and a kill switch freezes one agent or the whole fleet instantly.
Ask the ledger in plain English
“How much did Marketing's agents spend on data APIs?” The model fills a constrained, parameterized query over the ledger and answers. It never writes raw SQL against your data.
Tokens are spend too
Paid APIs are not the only money agents move. The SDK meters LLM calls through the same budget: it holds an estimate, runs the call once, then settles the real token cost and refunds the difference. It reads OpenAI-shaped and Anthropic-shaped usage, and you supply the rates, because a price table baked into a dependency goes stale and quietly mis-bills you.
Retrying can't double-charge
Every SDK call has a timeout and bounded retries on network errors and 5xx. Each spend carries a generated idempotency key, so a request retried after the server already committed it resolves to that same single ledger entry. Denials are never retried.
Try it in 60 seconds
The dashboard runs on a live Aurora DSQL cluster with sample data. Nothing you do spends real money.
- 1Run a spendIn “Simulate a spend,” authorize a small amount; it commits and appears in the ledger instantly.
- 2Trip a limitAuthorize more than an agent's remaining budget. It's denied and recorded, and the balance holds.
- 3Ask the ledgerType a question in “Ask your ledger” and get an answer drawn straight from the entries.
Drop it into an agent
Put the budget gate in front of any paid call. The money moves only after the spend commits.
import { StubClient } from "trystub";
const stub = new StubClient({ apiKey: process.env.STUB_API_KEY });
if (await stub.guard({ vendorAccountId, amountUsd: 0.02, intent: "fetch market data" })) {
await doThePaidThing();
}For LLM calls, where the cost is only known afterwards, hold an estimate and settle the real token spend:
import { meterLLM, estimateUsd } from "trystub/llm";
const pricing = { inputPerMTok: 3, outputPerMTok: 15 };
const response = await meterLLM(
stub,
{ vendorAccountId, pricing, estimatedUsd: estimateUsd(promptTokens, maxOutput, pricing) },
() => callTheModel(),
);Zero dependencies, ships as both ESM and CommonJS.
Run the whole thing locally
You do not need an AWS account to try Stub. Point it at local Postgres and the console, the ledger, and the invariant all work the same way.
npm run db:up
echo 'DATABASE_URL=postgres://stub:stub@localhost:5432/stub' >> .env
npm run migrate && npm run seed
npm run devLocal transactions run at SERIALIZABLE so plain Postgres raises the same 40001 as Aurora DSQL. Run npm run test:local to watch ten agents race one budget and lose.