# Policy best practices

This page provides recommended patterns for planning, configuring, and maintaining transaction policies.

## Planning your policy strategy

**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:


```json
{"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](/products/wallet/user-interface/security-controls/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".


## Configuration examples

The following examples show common policy configurations for different wallet types.

**Hot wallet** – Process many small transactions automatically with tight limits:


```json
{"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"}
```

Multiple rolling policies
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:


```json
{"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:


```json
{"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:


```json
{"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"}]}
```

## Ongoing operations

**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:


```json
{"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](/products/wallet/user-interface/policies/policies-manage#modify-a-policy) for the step-by-step process.

## Troubleshooting

| 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](/products/wallet/user-interface/policies/policies-reference#combining-matchers) for details. |


Known display issue
Rolling limit rejection messages may display "0-hour" regardless of the actual duration configured. The enforcement uses the correct duration.

## Related documentation

- [Policy concepts](/products/wallet/user-interface/policies/policies-concepts) – Core concepts and lifecycle
- [Policy reference](/products/wallet/user-interface/policies/policies-reference) – Complete API reference
- [Manage policies](/products/wallet/user-interface/policies/policies-manage) – Step-by-step guide
- [Approvals](/products/wallet/user-interface/security-controls/approvals) – Configure approval groups