Local-first SDK and CLI for enforcing outbound call budgets and policies.
callbudget helps agents decide whether an external call is allowed before they make it, then log what happened afterward in a simple JSONL ledger.
It is built for the messy real world where agents touch paid APIs, rate-limited endpoints, and tools you only want to allow under explicit rules.
A lot of agent tooling stops at “here’s how to call the tool.”
What’s still awkward:
- enforcing a host/method allowlist locally
- capping total calls for a task or run
- capping estimated spend per endpoint
- giving agents a deterministic preflight result and exit code
- keeping a cheap local ledger for later auditing
callbudget handles that with one policy file and one append-only ledger.
npm i callbudgetOr use it directly:
npx callbudget initCreate a starter policy:
callbudget initExample callbudget.policy.json:
{
"version": 1,
"ledgerPath": ".callbudget/ledger.jsonl",
"defaults": {
"allow": false
},
"limits": {
"maxCalls": 500,
"maxCostUsd": 25
},
"rules": [
{
"name": "openai-posts",
"allow": true,
"match": {
"host": "api.openai.com",
"methods": ["POST"],
"protocol": ["https:"]
},
"limits": {
"maxCalls": 200,
"maxCostUsd": 15
}
}
]
}The intended flow is:
- Check whether a planned call is allowed.
- Execute the external call only if allowed.
- Record the result, or use
runto do both. - Summarize usage when the task ends.
callbudget check \
--policy callbudget.policy.json \
--url https://api.openai.com/v1/responses \
--method POST \
--cost 0.08 \
--format jsonIf the request is denied, callbudget check exits with code 2.
That makes it easy for agents to branch deterministically.
callbudget record \
--policy callbudget.policy.json \
--url https://api.openai.com/v1/responses \
--method POST \
--cost 0.08 \
--status 200 \
--result okcallbudget run \
--policy callbudget.policy.json \
--url https://api.openai.com/v1/responses \
--method POST \
--cost 0.08 \
-- node ./scripts/invoke-openai.jsimport { checkCall, recordCall, summarizePolicy } from "callbudget";
const decision = checkCall("./callbudget.policy.json", {
url: "https://api.openai.com/v1/responses",
method: "POST",
costUsd: 0.08,
});
if (!decision.allowed) {
throw new Error(`Call denied: ${decision.reason}`);
}
// ...perform the actual external request...
recordCall("./callbudget.policy.json", {
url: "https://api.openai.com/v1/responses",
method: "POST",
costUsd: 0.08,
}, {
status: 200,
result: "ok",
});
console.log(summarizePolicy("./callbudget.policy.json"));You do not need a larger framework to get value from this.
Useful cases:
- shell scripts that call paid APIs
- cron jobs that should stop after a budget threshold
- CI jobs that should stay within a fixed call count
- auditing which hosts a workflow talked to
callbudget check \
--policy callbudget.policy.json \
--url https://api.github.com/repos/octocat/hello-world \
--method GETcallbudget summary --policy callbudget.policy.jsoncallbudget init [--path callbudget.policy.json] [--force]
callbudget check --policy callbudget.policy.json --url <url> --method <method> [--cost 0.12] [--format text|json]
callbudget record --policy callbudget.policy.json --url <url> --method <method> [--cost 0.12] [--status 200] [--result ok] [--format text|json]
callbudget summary --policy callbudget.policy.json [--format text|json]
callbudget run --policy callbudget.policy.json --url <url> --method <method> [--cost 0.12] -- <command> [args...]defaults.allow: default allow/deny when no rule matcheslimits.maxCalls: global call cap across the ledgerlimits.maxCostUsd: global estimated cost caprules[].match.host: exact host or wildcard like*.openai.comrules[].match.methods: allowed HTTP methods for that rulerules[].match.protocol: typicallyhttps:rules[].limits.maxCalls: per-rule call caprules[].limits.maxCostUsd: per-rule estimated cost cap
Rules are matched in order. First match wins.
- Fork the repo.
- Create a branch.
- Run:
npm install npm run lint npm test npm run build - Open a pull request with a focused change.
Start by starring the repo:
gh api -X PUT user/starred/gilangjavier/callbudgetThen:
- Read the policy and ledger model before changing behavior.
- Make the smallest useful change.
- Run:
npm install npm run lint npm test npm run build - Include concrete before/after examples in the PR.
- add time-windowed budgets (per hour / per day)
- add reserved-budget workflows for concurrent workers
- add optional redaction for query strings and headers in the ledger
- add richer rule predicates (path prefixes, tags, status-based accounting)
- add adapters for common tool runners and MCP transports
GitHub Actions runs lint, tests, and build on every push and pull request.
MIT