This page provides recommended patterns for planning, configuring, and maintaining transaction policies.
Start restrictive, then expand. Begin with conservative limits and increase them as you understand your transaction patterns. It's easier to raise limits than to recover from unauthorized transactions.
Layer your limits. Combine multiple limit types for defense in depth:
| Limit type | Purpose |
|---|---|
| Per transaction | Prevents large single transactions |
| Rolling duration | Prevents rapid accumulation of smaller transactions |
| Max total value | Provides an absolute backstop |
For example, a hot wallet holding ETH might use three policies together:
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "10"}
{"limitType": "ROLLING_DURATION", "symbol": "ETH", "limitQty": "50", "duration": "86400s"}
{"limitType": "CONSTANT", "symbol": "ETH", "limitQty": "500"}This ensures no single transaction exceeds 10 ETH, no more than 50 ETH leaves in any 24-hour period, and the wallet can never send more than 500 ETH total.
Enable approval groups. Without approval groups, anyone with policy:create or keylimit:create API permissions can instantly enable policies. See Approvals to add human oversight.
Avoid common mistakes:
- Don't use zero or negative limits, they block all transactions or behave unpredictably.
- Don't rely on a single high-limit policy without matchers—it provides minimal protection.
- Don't rely solely on per-transaction limits—combine with rolling limits to catch accumulation attacks.
- Don't leave assets without policies—transactions will fail with
No transaction limit configured for <asset>.
The following examples show common policy configurations for different wallet types.
Hot wallet – Process many small transactions automatically with tight limits:
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "1"}
{"limitType": "ROLLING_DURATION", "symbol": "ETH", "limitQty": "50", "duration": "86400s"}You can create multiple rolling-duration policies with different windows by using different matchers. Without matchers, only one rolling-duration policy per asset is allowed.
Cold storage – Hold long-term reserves with strict, manually-approved withdrawals. Use low per-transaction limits and combine with mobile-only MPC quorums and transaction approval groups.
Treasury wallet – Handle large transfers to trusted counterparties while restricting other destinations:
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "1000", "matchers": [{"type": "COUNTERPARTY_ID", "values": ["trusted-uuid"]}]}
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "10"}Role-based limits – Use USER matchers to set different limits for different team members:
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "5", "matchers": [{"type": "USER", "values": ["ops-user-uuid"]}]}
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "100", "matchers": [{"type": "USER", "values": ["finance-user-uuid"]}]}API credential isolation – Use API_CREDENTIAL matchers to limit each integration's authority:
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "1", "matchers": [{"type": "API_CREDENTIAL", "values": ["trading-bot-uuid"]}]}
{"limitType": "ROLLING_DURATION", "symbol": "ETH", "limitQty": "100", "duration": "86400s", "matchers": [{"type": "API_CREDENTIAL", "values": ["settlement-uuid"]}]}Review policies regularly. Schedule periodic reviews to check whether limits are still appropriate for current transaction volumes, whether unused policies should be removed, and whether new assets need policies.
Monitor transaction rejections. Frequent rejections may indicate limits set too low, attempted unauthorized access, or misconfigured policies. Use the transaction list to filter by REJECTED status and review the rejection reasons.
Document your policy strategy. Record why each policy exists, who approved it, and when it was last reviewed. This helps during audits and when onboarding new team members.
Upgrade to scoped matchers. If you have existing policies without matchers, you can add more granular policies alongside them. When a policy with matchers matches a transaction, it replaces the policies without matchers for that transaction, including rolling and max-total-value policies. Add matching rolling or lifetime policies with the same matchers if you want those caps to keep applying:
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "10"}
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "100", "matchers": [{"type": "COUNTERPARTY_ID", "values": ["trusted-uuid"]}]}Replace policies safely. Policies are immutable. To change a policy's limits, create the new policy first, then delete the old one after the new policy is active. See Manage policies via the console for the step-by-step process.
| Symptom | Cause | Solution |
|---|---|---|
Transaction rejected: No transaction limit configured for <asset> or Transaction limits for <asset> exist but are not active (PAL006.005) | No active policy exists for the asset | Create a limit rule for the asset. Verify status is RULE_ENABLED. |
| Transaction rejected: "exceeds limit" | Transaction amount exceeds policy limit | Reduce amount, or delete and recreate policy with higher limit. For rolling limits, wait for older transactions to roll off. |
| Transaction rejected: "policy check failed" | Multiple policies apply and one rejects | List all policies for the wallet and check which ones match your transaction. |
API error PAL006.039: "an equivalent policy rule already exists" (or PAL006.023: "limit policy already exists" from the wallet limits endpoints) | Rule with same wallet, kind, trigger, asset, limit type, and matchers exists | Delete existing policy first, or add different matchers to create a distinct policy. |
API error PAL006.016: "action not allowed" | Attempting to delete a pending policy | Wait for policy to be approved or rejected before deleting. |
API error PAL000.002: "unauthorized request" | Missing API scope or vault access | Verify credentials have the policy:* permissions (policy rules API) or keylimit:* permissions (wallet limits endpoints) as needed. |
Policy shows active: false | Policy awaiting approval | Check if status is RULE_CREATION_APPROVAL_PENDING. Ask approvers to authorize. |
| Transactions passing that should be blocked | Policy matchers don't match transaction | Review matchers—policy only applies when all matchers match. |
| Policy stuck in pending | Not enough approvers responded | Ask additional approvers. If timeout expires, create a new policy. |
| Policy rejected unexpectedly | Approval threshold impossible to meet | Too many approvers skipped. Create a new policy and coordinate approvers. |
PAL006.039 or PAL006.023 when creating a replacement policy | Rejected policy still counts for uniqueness | A policy in RULE_REJECTED status still occupies its uniqueness slot. Deleted policies don't. Delete the rejected policy, then create the replacement. |
| Transactions blocked after deleting a counterparty or address | Policy has orphaned matcher reference | The policy still exists but its matcher references a deleted entity, so no transaction can match it. Delete the orphaned policy and create a new one. See Policy reference for details. |
Rolling limit rejection messages may display "0-hour" regardless of the actual duration configured. The enforcement uses the correct duration.
- Policies – Core concepts and lifecycle
- Policy reference – Complete API reference
- Manage policies via the console – Step-by-step guide
- Approvals – Configure approval groups