RULE SPEC v0.1
Fourteen rule kinds — the fourteenth is your own.
Every rule is a row in the database: kind + config JSON, scoped business-wide or to a single card, evaluated server-side on every intent, with a per-rule verdict persisted on the decision. Declarative depth is the bet: everything below is configuration, with no rule service for you to write, host, or operate.
POST /rules { "kind": "...", "config": { ... }, "card_id": null } # null = business-wide
GET /rules
PATCH /rules/{id} { "enabled": false }
DELETE /rules/{id}
# a decline from ANY rule denies; a human-gate parks (pending_approval);
# partial approvals return the approved ceiling; every result carries its reason.Merchant rules
Allowlist — the card only works at merchants you name.
{ "kind": "merchant_lock", "config": { "allow": ["pizzeria", "doordash"] } }Substring match on merchant name/domain. Not on the list → decline.
Pizza bot locked to food-delivery merchants; sourcing agent locked to Grainger + Uline.
Denylist — named merchants always decline.
{ "kind": "merchant_block", "config": { "deny": ["casino", "sweepstakes"] } }Matches → decline, even if everything else passes.
Company-wide 'never these merchants' policy under looser per-agent allowances.
Category control by MCC — allowlist or denylist of merchant category codes.
{ "kind": "mcc_block", "config": { "allow": ["5812", "5814"] } }
{ "kind": "mcc_block", "config": { "deny_codes": ["7995", "5993"] } }Allow mode: only listed MCCs pass. Deny mode: listed MCCs decline.
Allow restaurants only; block gambling and tobacco fleet-wide.
Amount rules
Per-transaction maximum, with optional partial approval.
{ "kind": "amount_ceiling", "config": { "max_cents": 3000, "allow_partial": true } }Over the max → decline, or partial-approve up to the ceiling when allow_partial is set.
No single pizza order over $30; infra agent capped at $1,500 per purchase.
Cumulative spend cap over a rolling window — the sum, not the count.
{ "kind": "spend_cap", "config": { "max_total_cents": 20000, "window": "day", "allow_partial": true } }Projected total (spent + this purchase) over the cap → decline, or partial-approve the remaining headroom. Windows: day, week, month, or seconds.
Agent spends at most $200/day no matter how many purchases it splits across.
A rolling spend cap scoped to one merchant — a loose global cap and a tight per-merchant cap on the same card.
{ "kind": "per_merchant_spend_cap", "config": { "merchant": "doordash", "max_total_cents": 6000, "window": "day" } }Only counts spend at the named merchant over the window; over the cap → decline (partial-approve supported). Other merchants fall under your global caps.
$200/day overall, but never more than $60/day at any single delivery service — one card, one agent key.
Cadence rules
Transaction count limit per rolling window.
{ "kind": "velocity", "config": { "max_count": 10, "window_sec": 3600 } }N-or-more purchases in the window → decline until it rolls off.
A stuck retry loop can't machine-gun 400 orders in a minute.
Minimum seconds between purchases — the runaway-loop brake.
{ "kind": "cooldown", "config": { "cooldown_sec": 300 } }A purchase within the cooldown of the last approved one → decline.
An agent that just bought lunch cannot buy lunch again 4 seconds later.
Lifetime use count. max_uses: 1 is a single-use card — as one config value, not a separate product.
{ "kind": "max_uses", "config": { "max_uses": 1 } }After N approved purchases, everything declines.
One-shot procurement: the card that buys exactly one laptop, then dies.
The retry-storm rule. Same merchant and same amount inside the window is treated as a duplicate fire, not a new decision.
{ "kind": "duplicate_charge_guard", "config": { "window_sec": 600 } }An identical merchant + amount within the window → decline. Built for how software fails, not how people spend.
A timeout after the charge already succeeded makes an agent pay again, verbatim, seconds later. Humans almost never do this; agents in a retry loop do.
Time rules
Allowed hours of day, timezone-aware, wraparound supported.
{ "kind": "time_window", "config": { "start_hour": 9, "end_hour": 18, "tz": "America/Chicago" } }Outside the window → decline. 22–6 works (overnight wraparound).
Procurement agent spends business hours only; scraper budget only at night.
The human gates
Purchases above a threshold park for your sign-off instead of auto-deciding.
{ "kind": "approval_threshold", "config": { "above_cents": 50000 } }Over the line → status pending_approval. You approve or deny from the dashboard, the TUI, or one API call. Approving re-runs every other rule.
The infra agent can buy $80 of compute alone, but a $2,400 reserved instance waits for you.
First purchase from a merchant this business has never bought from parks for review (or declines).
{ "kind": "new_merchant_review", "config": {} }
{ "kind": "new_merchant_review", "config": { "action": "decline" } }Unseen merchant → pending_approval (default) or decline. Once you approve a merchant once, it's known.
The agent-specific rule: a prompt-injected agent can't wire money to a brand-new 'merchant' behind your back; the first hit always crosses your desk.
Bring your own rule
Bring your own rule. APPEN POSTs the declared intent to your own HTTPS endpoint, and your service returns the decision — anything the spec doesn't cover.
{ "kind": "webhook_check", "config": { "endpoint_id": "whe_…", "fail_mode": "closed" } }A PAN-free, HMAC-signed request to your registered endpoint; your { decision: approve | decline | review } maps to pass / deny / review. SSRF-guarded, and fail-closed by default — a slow or unreachable endpoint declines, it can't wave spend through. Runs after the local rules pass, so a doomed intent is never sent out.
Your own fraud model, an inventory check, a per-customer budget in your database. Your service becomes a rule on the card, with no auth service to host.
Plus the budgeting layer
Envelopes sit above the rules: a cap per category (daily / weekly / monthly, with rollover), deny-by-default for categories with no envelope. Rules answer "is this purchase allowed?"; envelopes answer "is there budget left for this kind of thing?" Both run on every decision.
The frontier — designed, not live
Fourteen more kinds have full specs and wait their turn. Each is marked DESIGNED until it ships — none of these runs in the engine today, and none carries a live-money claim.
the card expires with the task; an expired card is inert by construction
refund scope separated from spend scope on a key
N declines in a window freezes the card
task scope declared at mint; out-of-scope intents park
amount checked against your own trailing median for that merchant
unregistered recurring charges park for review
country allow/deny on the declared merchant, before funding
whether an agent key can mint child keys, and how much they get
key pinned to a model fingerprint; a silent swap parks the intent
a charge outside the merchant's own hours parks
unmet receipt obligations block the next load
two humans must sign off above a threshold
spend gated to the owner's location, via the companion app
declared vs actual merchant geography reconciled after settlement
Where we're honest about the edges
- — Lithic ships a configurable declarative rule engine (Auth Rules v2) with persisted rule-result traces — for underwritten card programs (KYB + a program agreement), not individuals — and its ASA hook can express anything if you write, host, and operate your own low-latency authorization service. If you're a company willing to become a program, that's the right tool. APPEN's bet is that governed spend shouldn't require becoming one — our engine plays that role for a person with an API key.
- — Privacy.com's configurable surface is spend-limit durations, three card types, and pause/close. The depth gap is real, and it's the honest comparison to make.
- — Several kinds here (timezone-aware hours, lifetime max-uses, cooldowns, async human approval queues, new-merchant review) exist because software fails differently than employees do. Each element exists somewhere; what no live product ships (verified July 2026) is the combination — self-serve for an individual, a declare-before-credential intent gate, fourteen explained-and-persisted rule kinds including your own via webhook, and handle-not-PAN by construction.
WHERE THIS RUNS TODAY
The rule engine decides every declared intent today — live in the API, the MCP server, and the demo. Enforcement on real card swipes begins at our BIN connection with our issuing partner, when the same pre-authorized, user-directed policies execute inside the network authorization step. Card issuance opens by invite once our issuing-partner sandbox is live.