Skip to content

Policy reference

spendctl.yaml is validated on start; unknown keys are errors. Amounts are USD strings ("0.05", "$0.05"). Durations: "30s", "10m", "24h", "7d".

version: 1
signer: { type: cdp } # mock | cdp | private-key
server: { host: 127.0.0.1, port: 8402, connect: tunnel, upstreamTimeout: 30s }
agentAuth: header # header (local dev) | token (per-agent tokens via `spendctl token <agent>`)
defaultAgent: researcher # optional: agent used when a request names none
approvals:
timeout: 10m
hold: true # false → return an approval id; retry with X-Spendctl-Approval
webhooks: [{ url: https://…, format: slack, secretEnv: SPENDCTL_WEBHOOK_SECRET }]
publicUrl: https://spendctl.internal.example # base URL for review links
circuitBreaker: { failures: 3, cooldown: 5m }
idempotencyWindow: 10m
assets: [] # extra priced assets, e.g. MPP token currencies
defaults: { … agent keys … } # merged into every agent (deny lists accumulate)
agents:
<name>: { … agent keys … }
Key Meaning
mode enforce (default) or dry-run
budget.perRequest max for a single payment
budget.hourly / daily / weekly / monthly rolling windows (1h, 24h, 7d, 30d) over reserved + settled + unknown spend
onBudgetExceeded deny (default) or approve (hold for a human)
requireApprovalAbove any single payment at or above this needs a human
allow host/path patterns that may be paid; omitted = any host
deny patterns that may never be paid (checked first)
payTo map of host pattern → allowed recipient addresses
protocols ["x402", "mpp"] subset
networks CAIP-2 ids, wildcards allowed: ["eip155:84532"], ["eip155:*"]
assets symbols or addresses, e.g. ["USDC"]
maxChainDepth max agent→agent hops (from baggage: spend.depth)
firstPaymentMax first payment to a new (host, recipient) above this needs a human
priceJumpFactor price above N× the last price paid for the endpoint needs a human
maxTimeoutSeconds longest authorization we sign (default 900)
api.example.com any path, any port
api.example.com:8443 that port only
*.example.com subdomains (not the apex)
api.example.com/v1/* path prefix
* everything

unknown agent → chain depth → circuit breaker → deny → allow → per-option filters (protocol, signable scheme, network, asset, priced, payTo, timeout) → cheapest viable option → per-request cap → time windows → approval threshold → first payment → price jump. Any deny wins over approve; a human can only waive approve reasons.