Evaluate a transfer
Check a transfer with POST /evaluate before creating an operation: get allow/queue/block, the reason, and the matched policies without changing anything.
Before creating a transfer, you can ask the Policy Engine "will this go through?" without creating anything or reserving funds. evaluate returns the same allow / queue / block verdict, along with the reason and the list of matched policies. It's useful for preflight checks in UIs and automation. For the engine itself, see Policy Engine.
How evaluate reaches a verdict (the same calculation as when creating a transfer, but with no side effects):
Prerequisites
- A Vault (
vaultId) and the user (userId) on whose behalf the action is checked. - An access token (see Authentication).
- The parameters of the intended action:
asset,amount,toAddress.
Step 1. Send an evaluation request
POST /api/v1/evaluate is a top-level endpoint (not under /vaults/{code}). It doesn't create anything; it only calculates the verdict.
curl -X POST "[BASE_URL]/api/v1/evaluate" \
-H "Authorization: Bearer $V3_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": "transfer",
"vaultId": "VAULT_ID",
"userId": "USER_ID",
"asset": "usdt",
"amount": "1350.00",
"toAddress": "TQ2m...p4rX"
}'
const res = await fetch("[BASE_URL]/api/v1/evaluate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.V3_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
action: "transfer",
vaultId: "VAULT_ID",
userId: "USER_ID",
asset: "usdt",
amount: "1350.00",
toAddress: "TQ2m...p4rX",
}),
});
console.log(await res.json());
res = requests.post(
"[BASE_URL]/api/v1/evaluate",
headers={"Authorization": f"Bearer {os.environ['V3_TOKEN']}"},
json={
"action": "transfer",
"vaultId": "VAULT_ID",
"userId": "USER_ID",
"asset": "usdt",
"amount": "1350.00",
"toAddress": "TQ2m...p4rX",
},
)
print(res.json())
asset is required; tokenCode is optional. The other body fields are for illustration only. Check the exact schema on the evaluate page in the API Reference. vaultId is the Vault's id, not its code.
Step 2. Read the decision
The response contains the verdict and an explanation:
{
"decision": "block",
"reason": "AMOUNT_EXCEEDED",
"policyId": "...",
"policyCode": "otc-daily-limit",
"trace": [
{ "policyCode": "otc-daily-limit", "policyName": "OTC daily limit", "effect": "block" }
]
}
| Field | Meaning |
|---|---|
decision | the outcome: allow, queue, or block |
reason | a machine-readable reason (for example, AMOUNT_EXCEEDED) |
policyCode | the code of the policy that determined the outcome |
trace | the list of matched policies with their effect |
How to use the verdict:
| Verdict | What to do in the UI or automation |
|---|---|
allow | you can create the transfer right away |
queue | you can create it, but it will go to awaiting_approval; warn the user upfront |
block | show the reason and don't allow sending |
Step 3. How the engine makes a decision
evaluate follows the deny-overrides principle:
- All of the user's policies in the Vault are collected (through group memberships).
- Only enabled policies whose action type matches the requested one are kept.
- Applicability is checked (a condition tree; an empty tree means "always applies").
- If at least one policy blocks, the outcome is
block.
The same calculation runs when a transfer is created, but there it comes with side effects (a reserve, an approval request). evaluate, on the other hand, is safe to call repeatedly. For details, see Policy Engine.
Common mistakes
| Symptom | Cause |
|---|---|
always allow when you expected a block | the context (amount / asset) wasn't passed, so the policy didn't apply |
asset not recognized | tokenCode was passed instead of the required asset |
| verdict differs from the actual transfer creation | the request parameters differ; compare the body with the actual transfer call |
401 / 403 on the endpoint itself | a token or access problem, not a policy one; see Authentication |
vaultId not found | the Vault's code was passed instead of its id |
You're done when
POST /api/v1/evaluatereturns adecisionwithout creating anything;- when a limit is expected, you get
blockwith a clearreason; tracecontains the matched policies;- a repeated call with the same parameters returns the same verdict (no side effects).