Policy Engine
A deny-by-default engine that decides, before signing, whether funds can move and whose approval is needed: condition trees, limits, whitelists, and m-of-n.
The Policy Engine is the layer that every action involving funds passes through before anything is signed: a transfer, address creation, a counterparty change. It answers the question "can this be done, and under what conditions," and it does so before the MPC nodes produce a signature.
The engine follows the deny-by-default principle: nothing is allowed by default, and an action goes through only if a policy allows it.
How a decision is made
The core of the engine is the POST /api/v1/evaluate endpoint. It collects the policies that apply to the user and returns a verdict:
The user's policies are collected
All policies available to the user in this Vault through their group memberships. Your permissions are the union of your groups' policies.
Matching policies are selected
Only enabled (enabled) policies whose action type matches the requested one (actionType = action) are kept.
Applicability is checked
Each policy has a boolean condition tree (condition: and / or / not / leaf). condition=null means "always applies."
The outcome is calculated (deny-overrides)
If at least one applicable policy blocks, the outcome is block, no matter what the others allow. A denial always overrides a permission.
Since evaluate doesn't change anything, it's convenient to call it in advance to check a transfer before creating it.
Three outcomes: allow, queue, block
| Outcome | Meaning | What happens |
|---|---|---|
allow | allowed | the action is executed immediately |
queue | approval required | an approval request is created; the amount is reserved |
block | forbidden | the action isn't executed (403) |
Each decision comes with an explanation: not just "denied," but why:
{
"decision": "block",
"reason": "AMOUNT_EXCEEDED",
"policyId": "...",
"policyCode": "...",
"trace": [
{ "policyCode": "...", "policyName": "...", "effect": "block" }
]
}
reason is a machine-readable reason, and trace is the list of matched policies with their effect. So answering "why was this blocked" takes a query, not an investigation.
What a policy is made of
A policy has an effect (allow or block), applicability (a condition tree), rules with selectors, limits, and an owner group and approver group.
Condition vs. selector
These two mechanisms are easy to confuse:
conditiondefines when the policy applies at all. It's a boolean tree (and/or/not/ leaf);nullmeans always.- Selectors define along which axes a rule matches an action: network, asset, purpose, sender, recipient. These are the same segments as in the Vault's tree (see Vaults and derivation), so rules are written by meaning rather than by listing addresses.
An example condition tree (simplified):
and
├── asset = usdt
└── amount > 10000
This condition makes the policy apply only to USDT transfers over 10,000.
Useful endpoints:
GET /api/v1/policies/{id}returns a policy with all its rules and selectors, plus its owner and approver groups.GET /api/v1/me/allowed-segmentsreturns which segment values you're allowed to use for an action. It's the union across all yourallowpolicies: allowed values are extracted from sender selectors (senderPattern); a policy without asenderPatternallows everything (*), andblockpolicies aren't taken into account here.
Limits and whitelists
Limits cap amounts. When a limit is exceeded, the engine returns block or queue with a reason such as AMOUNT_EXCEEDED. Typical kinds of limits:
| Limit type | What it caps |
|---|---|
| per operation | the maximum for a single operation |
| per period | the total over a rolling time window |
| cumulative | total volume (the current operation is included) |
A whitelist is an allow rule with a recipient selector: only the listed recipients are allowed, and everything else is forbidden (a consequence of deny-by-default).
The limit taxonomy above comes from the product positioning. The exact schema for limits and selectors is in the API Reference. This page describes what the rules mean, not how they're written.
Example policy
Here's an illustration using a set of rules (simplified):
- Limit with approval.
usdttransfers ontronover 10,000 →queuewith a threshold of 2 from the approver group. - Recipient whitelist. An
allowrule with a recipient selector allows only addresses from the counterparty list. - Everything else is forbidden by default until an allowing policy exists.
An attempt to send 15,000 USDT returns decision: queue, a reason such as AMOUNT_EXCEEDED, and a trace with the matched limit policy. A transfer to an address outside the whitelist returns block.
This example is simplified. The exact format for rules and thresholds is in the API Reference.
Approvals (m-of-n)
When the outcome is queue, the decision carries approval parameters:
threshold: how many approvals are needed (m of n).- approver group: who is allowed to approve.
adminBypass: an admin initiator executes the action without an approval request.allowInitiatorApproval: whether the initiator can vote on their own request.
A single reject is a veto. Several related actions can be combined into a composite request: each step is gated by its own policy, and rejecting any step rejects the entire chain. The approval request lifecycle is covered separately, in the section on approvals.
Versions: nothing is lost
Policies are never overwritten in place; every change is recorded as a new revision:
GET /api/v1/policies/{id}/versionsreturns an append-only history. Any edit (limits, approvers, condition) creates a version with a snapshot of the decision-making part:effect,condition,approval,limits,approvers. Thereasonfield shows why the version was created (created/updated/backfill).
An approval request remembers the rule as it was when the request was created. That's why later policy changes don't retroactively rewrite existing requests: the approvers and threshold stay as they were at the time of the request.
Where the engine applies
The Policy Engine gates more than just transfers. Each action has its own actionType, such as address creation (create_address), counterparty archiving (counterparty_archive), and others; all of them go through the same engine. Policy lists:
GET /api/v1/policies?vaultId=...(admin) orGET /api/v1/vaults/{code}/policiesreturns a Vault's policies.GET /api/v1/policy-groups/{id}/policiesand/accessreturn a group's policies and access.