Docs Concepts
Policy
LiveUpdated
A policy is a signed, versioned object written in the terms of the user’s own approval rules. Every decision references the exact version it was made under.
What a policy expresses
| Rule | Field | Example | Enforced by |
|---|---|---|---|
| Payee allowlist and verification state | payees[]{domain, name, verified, category, mcc[]} | reamandrule.com, verified | Enruta; merchant lock on a single-use card |
| Per-payment cap | limits.per_payment | ≤ $200 | Enruta; card amount = mandate amount |
| Cumulative budget | limits.budget{amount, window} | ≤ $1,000 per calendar month | Enruta, reserved atomically at decision time |
| Time window | window{hours, expiry_seconds} | Business hours, expiry 15 minutes | Enruta; card expiry |
| Category | categories[] and MCC | Office supplies (MCC 5943, 5111) | Enruta; rail MCC controls where offered |
| Approval threshold | approval{above, approvers[]} | > $150 needs the person who approves | Enruta step-up; nothing is minted before the approval |
| Duplicate and idempotency checks | duplicate_window | Same payee, amount and reference inside 24 h | Enruta |
| Sanctions screening | screening | Payee against current lists | Enruta |
| Which agents | agents[] | agt_procurement_02 | Enruta (selectPolicy) |
Fields added in 0.2 and 0.3
All optional. A policy signed before they existed keeps its hash and its meaning.
| Field | Values | What it does |
|---|---|---|
mode | specified · bounded · bounded_stepup (default) | Sets the autonomy level: a locked target, autonomy inside the boundary, or autonomy inside the boundary with a step-up beyond it |
refill_policy | never (default) · on_void_only · on_settled_refund · on_any_reversal | Whether a reversal gives the amount back to the mandate account and the budget |
deviation{tolerance, action} | "0.05"; pause_and_notify · auto_cancel | Price deviation the agent may accept without asking; the card limit is opened at amount × (1 + tolerance) |
finality_rules{irreversible_per_payment} | decimal string or null | A lower per-payment cap for rails whose payments cannot be reversed |
retry_policy{max_attempts, window_seconds, same_amount_only} | max_attempts ≥ 1, the first attempt included; window_seconds; boolean | At most max_attempts attempts at one payment (the same agent, payee and purpose, after a deny, a rail decline or a hand-off) within window_seconds; the attempt beyond them, and with same_amount_only a retry for another amount, is denied with retry_limit_reached. Signed into the mandate (retrying) |
visibility_scope{merchant[], issuer_only[]} | policy · mode · refill_policy · finality_class · principal_login_id · retry_policy · deviation · per_payment_cap · budget · recurrence | Which of the mandate’s constraints the merchant sees. The others leave the mandate the merchant receives and are committed to by hidden_constraints_digest, their values kept in the record bundle (mandate) |
shadow | boolean | Shadow mode: decisions that would step up or deny come back as observe, sealed and reported, never enforced |
require_consent | boolean (default false) | A request without an active consent record for the agent is denied with consent_revoked |
reversal{window_days, step_up_above} | 120 days; decimal string or null | How long after settlement a refund may be requested, and above which amount a person signs the refund |
intent | { required_slots, clarify, rules, model } | What a request must say, the rules on its slots, and whether a model judges what rules cannot (intent and clarification) |
identity_requirements[] | { above, rails, require, on_fail } | Which identity anchors a payment needs, by amount and rail |
payee_identity | { require_verified_above, on_fail } | Whether the merchant must be verified before paying (trust model) |
Retries and what the merchant sees
A retry is a new request by the same agent, to the same payee, for the same purpose (compared without case or extra spaces) after one that failed inside the window: it was denied, the rail declined it, or it was handed off. A request still waiting for a person, and a payment that went through, are not failed attempts. visibility_scope changes no decision: it decides what the merchant’s copy of the mandate says. The amount, currency, payee, checkout and expiry are not constraints; the merchant always sees them.
"retry_policy": { "max_attempts": 3, "window_seconds": 86400, "same_amount_only": true },
"visibility_scope": { "merchant": ["retry_policy"], "issuer_only": ["per_payment_cap", "budget", "policy"] }
// three attempts at one payment a day, the first included, never for another amount;
// the merchant sees the amount, the payee, the checkout and the retry rule, not the ceilings or which policy decidedWhen a policy declares no visibility_scope, the merchant sees policy, mode, refill_policy, finality_class, principal_login_id and retry_policy, and deviation, per_payment_cap, budget and a recurrence’s caps are not signed into the mandate at all. A name in neither list keeps that default; no name may be in both.
Atomic budgets
Budgets are reserved when a decision is made and released or consumed when the rail posts. Ten concurrent agents cannot each pass the same $100 check, because the first reservation lowers the budget the others see. A completed reversal with a refill effect gives the amount back to the budget, never below zero.
Enforced twice
Enruta decides first, then compiles the decision into the control the rail itself offers: a single-use card for the exact amount at the named merchant, a spend limit scoped to one purpose, an approval bound to one bill. If Enruta is wrong, the rail still refuses anything outside the mandate. If the rail is loose, Enruta already refused. The five checks page lists each moment.
Versions
Policies are immutable once signed. An edit creates version n+1; mandates issued under n remain valid until they expire or are consumed, and every record names the version it was decided under. Your auditor can read version 7 in 2033 exactly as it was in 2026.
Fail closed
A request that matches no policy is denied with reason no_matching_policy. There is no default allow. A paused agent is denied with agent_paused before any policy is read.