Policies & trust
Set the rules that decide which agent spends you accept — dollar caps, velocity limits, mandate requirements, reputation floors, and review routing — then publish them safely as immutable versions.
Your policy is the set of local rules the acceptance gate evaluates on every settle, before any money is captured. It's how you decide which agent purchases to accept, hold for review, or reject — by dollar amount, velocity, authorization (mandate), agent reputation, and intent. This guide is the practical "how do I set it up" recipe; the Policy concept is the field-by-field reference.
Start from a preset
Adopt a posture wholesale, then tune individual fields. The presets are functions (each call returns a fresh, isolated policy), so one merchant can never mutate another's:
The recommended default. Small spends clear on reputation; larger ones need a mandate; generous-but-bounded caps; only the weakest tier goes to review.
import { BALANCED } from '@veto-protocol/checkout';
const policy = BALANCED();
// requireMandateOverUsd: 20, minReputationTier: 'standard', maxPerTransactionUsd: 500,
// maxPerAgentPerDayUsd: 2000, ratePerAgentPerHour: 60, ratePerAgentPerDay: 300,
// holdForReviewBelowTier: 'cautious'For high-value or sensitive goods. A mandate is required for any spend, reputation must be
trusted, caps are modest, velocity is tight, and anything not clearly trusted is sent to
human review. "I'd rather lose a sale than eat a chargeback."
import { STRICT } from '@veto-protocol/checkout';
const policy = STRICT();
// requireMandateOverUsd: 0 (mandate for ANY spend), minReputationTier: 'trusted',
// maxPerTransactionUsd: 100, maxPerAgentPerDayUsd: 250,
// ratePerAgentPerHour: 10, ratePerAgentPerDay: 50, holdForReviewBelowTier: 'trusted'Maximum reach, minimum friction — cheap digital goods and content APIs where the rail itself (paid-up-front x402) is the guarantee and a bad sale costs almost nothing. No mandate requirement, accept any reputation, high cap, no rate limit, never hold.
import { OPEN } from '@veto-protocol/checkout';
const policy = OPEN();
// requireMandateOverUsd: ∞ (never require), minReputationTier: 'risky',
// maxPerTransactionUsd: 10000, no daily cap, no rate limits, never holdsimport { createCheckout, BALANCED } from '@veto-protocol/checkout';
const checkout = createCheckout({
// …
policy: { ...BALANCED(), maxPerTransactionUsd: 250, requireMandateOverUsd: 50 },
});Every field is optional — safe defaults fill the rest. A field you don't set stays unset, and
"no cap" is a distinct, legitimate posture from "default cap": leaving maxPerAgentPerDayUsd
undefined means no daily cap, not the preset's value.
The dials, and the code each fires
The gate evaluates these in order and returns a verdict with one or more stable reason codes. Set what matches your risk:
| Dial | Field | Fires |
|---|---|---|
| Per-transaction cap | maxPerTransactionUsd | OVER_PER_TX_CAP |
| Daily spend cap (per agent) | maxPerAgentPerDayUsd | OVER_DAILY_CAP |
| Hourly rate (per agent) | ratePerAgentPerHour | RATE_LIMITED_HOURLY |
| Daily rate (per agent) | ratePerAgentPerDay | RATE_LIMITED_DAILY |
| Require a mandate above $X | requireMandateOverUsd | MANDATE_REQUIRED |
| Reputation floor | minReputationTier | REPUTATION_TOO_LOW |
| Blocklist | blockedAgents | AGENT_BLOCKED |
| Allowed rails | allowedRails | RAIL_NOT_ALLOWED |
| Forbidden intent words | forbiddenIntentKeywords | INTENT_FORBIDDEN_KEYWORD |
| Intent must match cart | requiredIntentMatch | INTENT_MISMATCH |
| Review below a trust tier | holdForReviewBelowTier | HOLD_FOR_REVIEW |
How caps, mandates, and reputation interact
The three trust inputs aren't independent — they combine into one verdict:
Caps and velocity are hard walls
A spend over maxPerTransactionUsd, over the rolling daily cap, or over a rate limit is
rejected outright — no mandate or reputation overrides a cap.
Mandates unlock higher-value spends
Above requireMandateOverUsd, the agent must present a valid, bound
mandate. A Veto mandate that's signed, approved, unexpired,
amount-sufficient, and merchant-matched resolves to the premium tier. No mandate over the
threshold → MANDATE_REQUIRED (rejected) or MANDATE_REQUIRED_HOLD (held), depending on your
posture.
Reputation is the floor for the rest
For spends that don't need a mandate, the agent's hosted reputation
tier must clear minReputationTier. Unknown agents read as standard — never as risky by
default — and reputation always degrades safe: if the lookup is slow or down, the gate
falls back to the neutral default rather than blocking the sale.
Review routing catches the ambiguous middle
holdForReviewBelowTier sends anything below a trust tier to a 202 hold with a review_url,
instead of an outright reject — the right move when you'd rather a human look than lose a
borderline sale.
The intent-match footgun.
requiredIntentMatch: true holds every transaction when no intentMatcher is wired (there's
no score to compare against). If you want intent matching, set the flag and provide a
intentMatcher in your config — otherwise leave it off. This is why the STRICT preset
deliberately does not enable it.
Editing policy on the hosted plane
Hosted policies are immutable and versioned — you never mutate an active policy in place.
Each change creates a new inactive version; publish flips it active in one transaction.
A receipt embeds the policyHash, so every order provably names the exact policy version that
accepted it.
Write a new version
curl -X PUT https://api.veto-checkout.com/v1/policy \
-H "Authorization: Bearer veto_test_…" -H "Content-Type: application/json" \
-d '{ "body": { "requireMandateOverUsd": 50, "maxPerTransactionUsd": 250, "minReputationTier": "standard" } }'
# → { "version": 2, "hash": "…", "active": false }Publish the gate
POST /v1/publish runs the SDK's validateConfig() over your draft and refuses to publish a
foot-gun config (returns 422 with CONFIG_INVALID + findings). If it passes, it activates
the newest version and invalidates the manifest cache so the next discovery reflects it.
curl -X POST https://api.veto-checkout.com/v1/publish -H "Authorization: Bearer veto_test_…"
# → { "published": true, "active_policy_version": 2 }Prefer the CLI? veto policy show prints the active policy and veto policy set <field> <value>
creates a new version in one line.
Validate before you self-host
Self-hosting, run the same shared validator over your config before you ship it — it catches the foot-guns at build time instead of at an agent's first request:
import { validateConfig } from '@veto-protocol/checkout';
const findings = validateConfig(myConfigInput);
if (findings.length) throw new Error(`config invalid: ${JSON.stringify(findings)}`);Recommended starting points
Cheap digital goods / content API
Start from OPEN. The paid-up-front rail is your guarantee; keep friction near zero.
Most stores
Start from BALANCED. Small spends clear on reputation; mandates gate the larger ones.
High-value or sensitive
Start from STRICT. Mandate for everything, trusted reputation, tight caps, review the rest.
The full field reference
Every policy field, its type, default, and the code it fires.
Go live with x402 + Coinbase CDP
Settle real USDC over HTTP-402 with a Coinbase CDP facilitator — from Base Sepolia testnet to Base mainnet, the EIP-712 domain trap, the txHash-is-authoritative rule, funding, and the live cutover checklist.
Webhooks
Receive and verify HMAC-SHA256 signed event deliveries from the hosted control plane with the SDK's verifyWebhook helper — the Veto-Signature scheme, replay window, retry schedule, idempotency, and secret rotation.