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 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 key limits found".
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": "10", "duration": "3600s"}
{"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", "value": "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", "value": "ops-user-uuid"}]}
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "100", "matchers": [{"type": "USER", "value": "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", "value": "trading-bot-uuid"}]}
{"limitType": "ROLLING_DURATION", "symbol": "ETH", "limitQty": "100", "duration": "86400s", "matchers": [{"type": "API_CREDENTIAL", "value": "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. The more specific policy (with matchers) takes precedence when its conditions match:
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "10"}
{"limitType": "PER_TX", "symbol": "ETH", "limitQty": "100", "matchers": [{"type": "COUNTERPARTY_ID", "value": "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 for the step-by-step process.
| Symptom | Cause | Solution |
|---|---|---|
| Transaction rejected: "no key limits found" | No active policy exists for the asset | Create a policy for the asset. Verify status is LIMIT_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.023: "limit policy already exists" | Policy with same wallet, limit type, symbol, 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 keylimit:read, keylimit:create, or keylimit:delete scope as needed. |
Policy shows active: false | Policy awaiting approval | Check if status is LIMIT_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.023 when creating a replacement policy | Rejected or deleted policy still counts for uniqueness | A policy in LIMIT_REJECTED or LIMIT_DELETED status still occupies its uniqueness slot. Add a different matcher to create a distinct policy. |
| 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.
- Policy concepts – Core concepts and lifecycle
- Policy reference – Complete API reference
- Manage policies – Step-by-step guide
- Approvals – Configure approval groups