# Policy reference

This page is a reference for policy rule limit types, matchers, statuses, permissions, and error codes. For the API operations, see [Manage policy rules via the API](/products/wallet/governance-and-controls/policies/manage-policies-api). For the full request and response schemas, see the **Policies** operations in the [Ripple Custody (Wallet) API](/products/wallet/api-docs/palisade-api/palisade-api) reference.

## Quick reference

### Limit types

| Limit type | Policy rules `aggregation` | Wallet limits `limitType` | Duration required | Description |
|  --- | --- | --- | --- | --- |
| Per transaction | `SINGLE_TX` | `PER_TX` | No | Maximum per single transaction |
| Rolling | `ROLLING` | `ROLLING_DURATION` | Yes | Maximum over a sliding time window |
| Max total value | `TOTAL` | `CONSTANT` | No | Lifetime cap on total withdrawals |


`LIMIT` rules support all three. `APPROVAL` and `TRAVEL_RULE` rules support `SINGLE_TX` only. For how the wallet limits endpoints show the other fields, see [Use the wallet limits endpoints](/products/wallet/governance-and-controls/policies/manage-policies-api#use-the-wallet-limits-endpoints-via-the-api).

### Matcher types

| Matcher | API value | Filters by |
|  --- | --- | --- |
| Transaction type | `TRANSACTION_TYPE` | Type of blockchain transaction |
| User | `USER` | User who initiated the transaction |
| API credential | `API_CREDENTIAL` | API credential that submitted the request |
| Address | `ADDRESS_ID` | Destination address (from address book) |
| Counterparty | `COUNTERPARTY_ID` | Counterparty receiving funds |
| All counterparties | `ALL_COUNTERPARTIES` | Any destination that resolves to a counterparty |
| Wallet | `WALLET_ID` | Destination wallet in your organization |
| Sign for | `SIGN_FOR` | Address being signed for (multi-sig) |
| Chain ID | `CHAIN_ID` | EVM chain ID for cross-chain raw signing |


### Duration values

| Duration | Value | Notes |
|  --- | --- | --- |
| 1 hour | `"3600s"` | 60 × 60 seconds |
| 4 hours | `"14400s"` | 60 × 60 × 4 seconds |
| 24 hours | `"86400s"` | 60 × 60 × 24 seconds |
| 7 days | `"604800s"` | 60 × 60 × 24 × 7 seconds |
| 30 days | `"2592000s"` | 60 × 60 × 24 × 30 seconds |


## Limit types

A `LIMIT` rule sets its limit type in `definition.limit.amount.aggregation`. Every amount condition uses `operator` `GT`: the rule limits transactions of more than `threshold`.

### Per transaction

Limits the maximum amount for any single transaction.

| Field | Value |
|  --- | --- |
| `aggregation` | `"SINGLE_TX"` (wallet limits: `limitType` `"PER_TX"`) |
| `duration` | Not used. The wallet limits endpoints return `"0s"`. |


**Example:** Limit single ETH transactions to 10 ETH maximum.

```json
{
  "kind": "LIMIT",
  "limit": {
    "amount": { "threshold": "10", "operator": "GT", "aggregation": "SINGLE_TX" }
  }
}
```

### Rolling

Limits the cumulative amount over a sliding time window.

| Field | Value |
|  --- | --- |
| `aggregation` | `"ROLLING"` (wallet limits: `limitType` `"ROLLING_DURATION"`) |
| `duration` | Required. Window size in seconds, less than one year. The policy rules API rejects a `ROLLING` rule without it with `PAL006.040`. The wallet limits endpoints don't enforce it: a `ROLLING_DURATION` limit sent without it is created with `"0s"`. |


**Common duration values:**

| Duration | Value |
|  --- | --- |
| 1 hour | `"3600s"` |
| 24 hours | `"86400s"` |
| 7 days | `"604800s"` |
| 30 days | `"2592000s"` |


**Example:** Limit ETH withdrawals to 100 ETH per 24 hours.

```json
{
  "kind": "LIMIT",
  "limit": {
    "amount": { "threshold": "100", "operator": "GT", "aggregation": "ROLLING", "duration": "86400s" }
  }
}
```

### Max total value

Sets a lifetime cap on total withdrawals. Once reached, no further withdrawals are permitted.

| Field | Value |
|  --- | --- |
| `aggregation` | `"TOTAL"` (wallet limits: `limitType` `"CONSTANT"`) |
| `duration` | Not used. The wallet limits endpoints return `"0s"`. |


**Example:** Limit total ETH withdrawals to 1,000 ETH ever.

```json
{
  "kind": "LIMIT",
  "limit": {
    "amount": { "threshold": "1000", "operator": "GT", "aggregation": "TOTAL" }
  }
}
```

## Matchers

Matchers filter which transactions a policy rule applies to. Add matchers to create rules that target specific scenarios.

- Send each matcher as `{ "type": "...", "values": ["..."] }`. Don't use the older single `value` string, which is deprecated. Responses echo each matcher with both `values` and `value`.
- Include each matcher type at most once, with up to 100 values. A transaction must match **every** matcher, and **any one** value in each matcher.
- The API rejects the deprecated `COUNTERPARTY` type. Use `COUNTERPARTY_ID` instead.
- `ALL_COUNTERPARTIES` takes no values. It matches only destinations that resolve to a counterparty. To apply a rule to any destination, leave out destination matchers.
- `APPROVAL` rules don't support matchers.


### Matcher types

| Type | Description | Value format |
|  --- | --- | --- |
| `TRANSACTION_TYPE` | Filter by transaction type | Transaction type string |
| `USER` | Filter by initiating user | User UUID |
| `API_CREDENTIAL` | Filter by API credential used | Credential UUID |
| `ADDRESS_ID` | Filter by destination address | Address book entry UUID |
| `COUNTERPARTY_ID` | Filter by counterparty | Counterparty UUID |
| `ALL_COUNTERPARTIES` | Filter by destinations that resolve to any counterparty | None (send an empty `values` array) |
| `WALLET_ID` | Filter by destination wallet in your organization | Wallet UUID |
| `SIGN_FOR` | Filter by address being signed for | Blockchain address |
| `CHAIN_ID` | Filter by EVM chain ID (cross-chain raw signing) | Positive integer |


### TRANSACTION_TYPE

Applies the rule only to transactions of a specific type.

**Values by blockchain:**

| Blockchain | Transaction types |
|  --- | --- |
| Ethereum/EVM | `Transfer` |
| XRP Ledger | `Payment`, `OfferCreate`, `OfferCancel`, `TrustSet`, `EscrowCreate`, `EscrowFinish`, `AMMCreate`, `AMMDeposit`, `AMMWithdraw` |
| Bitcoin | `Transfer` |


XRP Ledger transaction types
Transaction type values are case-sensitive and use PascalCase (for example, `OfferCreate`, not `OFFER_CREATE`). Transfers use `Payment` on the XRP Ledger and `Transfer` on other blockchains.

AMM transactions require multi-asset policies
`AMMCreate` and `AMMDeposit` transactions withdraw multiple assets from the wallet. The policy engine requires policies for **each affected asset**. For issued assets (such as RLUSD or USD), the policy must include the issuer. See the [AMM APIs changelog](/products/wallet/changelogs/xrp-ledger-amm-apis) for details.

```json
{
  "trigger": "OUTBOUND_INITIATED",
  "assetId": "3::XRP",
  "matchers": [
    { "type": "TRANSACTION_TYPE", "values": ["Payment"] }
  ],
  "definition": {
    "kind": "LIMIT",
    "limit": {
      "amount": { "threshold": "1000", "operator": "GT", "aggregation": "SINGLE_TX" }
    }
  }
}
```

### USER

Applies the rule only when a specific user initiates the transaction.

```json
{
  "matchers": [
    {
      "type": "USER",
      "values": ["8617b5b5-dfcf-4ffc-9f46-f7cec35a88bd"]
    }
  ]
}
```

### API_CREDENTIAL

Applies the rule only when a specific API credential submits the transaction.

```json
{
  "matchers": [
    {
      "type": "API_CREDENTIAL",
      "values": ["cred-uuid-here"]
    }
  ]
}
```

### ADDRESS_ID

Applies the rule only when sending to a specific address from your address book.

```json
{
  "matchers": [
    {
      "type": "ADDRESS_ID",
      "values": ["019d1b00-c1f5-77a0-bda7-0a3d09c668b9"]
    }
  ]
}
```

### COUNTERPARTY_ID

Applies the rule only when sending to a specific counterparty.

```json
{
  "matchers": [
    {
      "type": "COUNTERPARTY_ID",
      "values": ["019d1b00-c1f5-77a0-bda7-0a3d09c668b9"]
    }
  ]
}
```

### ALL_COUNTERPARTIES

Applies the rule to any transaction whose destination resolves to a counterparty. Use it to set one limit for all counterparty payments without listing each counterparty.

`ALL_COUNTERPARTIES` takes no values, and matches only destinations that resolve to a counterparty. To apply a rule to any destination, don't add a destination matcher.

```json
{
  "matchers": [
    {
      "type": "ALL_COUNTERPARTIES",
      "values": []
    }
  ]
}
```

### SIGN_FOR

Applies the rule only when signing for a specific address. Use this matcher for multi-signature scenarios where your wallet signs on behalf of another address.

```json
{
  "matchers": [
    {
      "type": "SIGN_FOR",
      "values": ["rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"]
    }
  ]
}
```

### CHAIN_ID

Authorizes [cross-chain raw signing](/products/wallet/transactions/raw-signing#cross-chain-raw-signing-evm) for a specific EVM chain ID that Ripple Custody (Wallet) doesn't natively integrate.

The rule applies only when the raw transaction's `chainId` matches the matcher value. Ripple Custody (Wallet) excludes rules **without** a `CHAIN_ID` matcher from cross-chain raw signing — you must add an explicit `CHAIN_ID` matcher to authorize the sign. Add one `CHAIN_ID` matcher per allowed chain.

**Value format:** positive integer. A value that isn't an integer returns `PAL006.028`. Ripple Custody (Wallet) rejects natively-integrated chain IDs (for example, Ethereum mainnet `1`, Arbitrum `42161`) — use the standard raw signing flow for those chains. This check doesn't apply to non-EVM wallets: an XRP Ledger wallet accepts a `CHAIN_ID` of `1`.

**Example:** Limit raw signing on the Optimism chain (chain ID `10`) to 1 transaction per hour.

```json
{
  "trigger": "OUTBOUND_INITIATED",
  "assetId": "2::ETH",
  "matchers": [
    { "type": "CHAIN_ID", "values": ["10"] }
  ],
  "definition": {
    "kind": "LIMIT",
    "limit": {
      "amount": { "threshold": "1", "operator": "GT", "aggregation": "ROLLING", "duration": "3600s" }
    }
  }
}
```

EVM wallets only
`CHAIN_ID` matchers apply only to EVM wallets and only to raw-signed transactions. The target chain ID must not be one Ripple Custody (Wallet) already integrates — those go through the standard raw signing flow instead.

Orphaned matcher references
If you delete a counterparty, address, or user that is referenced by a policy's matcher, the policy continues to exist with the now-invalid reference. No transaction will match the deleted entity, so the policy becomes effectively unmatchable. If it is the only policy for that asset, this silently blocks all matching transactions. Always check for policies referencing an entity before deleting it.

### Combining matchers

You can add multiple matchers to a single rule. The transaction must match **all** matchers for the rule to apply.

**Example:** Limit a specific user to 50 ETH per transaction when sending to a specific counterparty.

```json
{
  "trigger": "OUTBOUND_INITIATED",
  "assetId": "2::ETH",
  "matchers": [
    { "type": "USER", "values": ["user-uuid-here"] },
    { "type": "COUNTERPARTY_ID", "values": ["counterparty-uuid-here"] }
  ],
  "definition": {
    "kind": "LIMIT",
    "limit": {
      "amount": { "threshold": "50", "operator": "GT", "aggregation": "SINGLE_TX" }
    }
  }
}
```

## ERC-20 tokens

Policy rules identify a token by its registry ID in `assetId`, so the rule covers that token contract only. To find a token's registry ID, list assets from the registry:

```
GET /v2/registry/assets?filter.blockchain.eq=<blockchain>
```

The wallet limits endpoints identify the token by `symbol` and `contract` instead. Include the `contract` field with the token's contract address:

```json
{
  "limitType": "PER_TX",
  "symbol": "USDC",
  "limitQty": "10000",
  "contract": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
```

Contract address required
If you omit the `contract` field for an ERC-20 token in the wallet limits endpoints, the API may accept the request but the policy will not match any transactions. Always include the contract address for ERC-20 policies.

## Rule statuses

| Status | Enforcing (`active`) | Can delete | Description |
|  --- | --- | --- | --- |
| `RULE_CREATION_APPROVAL_PENDING` | No | No | The rule is waiting for approvers to approve it. |
| `RULE_ENABLED` | Yes | Yes | The rule is active and enforcing. |
| `RULE_REJECTED` | No | Yes | Approvers rejected the rule. It can't be enabled again. A rejected rule still blocks you from creating an equivalent rule until you delete it. Deleting it moves it straight to `RULE_DELETED`, with no approval. |
| `RULE_DELETION_APPROVAL_PENDING` | Yes | No | The rule is waiting for approvers to approve its deletion, and keeps enforcing until they do. If approvers reject the deletion, the rule returns to `RULE_ENABLED`. |
| `RULE_DELETED` | No | No | Approvers approved the deletion. This status is final. |
| `RULE_ERROR` | No | Yes | Ripple Custody (Wallet) couldn't process the rule. Delete the rule and create it again, or contact Ripple Support. |


A rule also passes through the short-lived statuses `RULE_CREATED`, `RULE_CREATION_APPROVAL_COMPLETE`, and `RULE_DELETION_APPROVAL_COMPLETE`. The `active` field is `true` only for `RULE_ENABLED` and `RULE_DELETION_APPROVAL_PENDING`.

The wallet limits endpoints report the same statuses with a `LIMIT_` prefix. See [Use the wallet limits endpoints](/products/wallet/governance-and-controls/policies/manage-policies-api#use-the-wallet-limits-endpoints-via-the-api).

## Permissions

The policy rules API uses the `policy` permission family, which is separate from `keylimit`. A `keylimit` permission doesn't give access to the policy rules API, and a `policy` permission doesn't give access to the `/policy-rules/limits` endpoints.

| Permission | Lets the credential |
|  --- | --- |
| `policy:create` | Create rules |
| `policy:read` | List and get rules |
| `policy:delete` | Delete rules |


The wallet limits endpoints use `keylimit:create`, `keylimit:read`, and `keylimit:delete` in the same way.

When you [create an API credential](/products/wallet/organization-and-access/api-credentials/manage-api-credentials), you can grant each permission at organization, vault, or wallet scope. In the console, owners and administrators can create and delete rules. Owners, administrators, proposers, approvers, and auditors can view rules. Viewers can't.

## Error codes

| Code | Message | Cause |
|  --- | --- | --- |
| `PAL000.002` | unauthorized request | The credential is missing the required permission, or you don't have access to the vault. The policy rules endpoints return HTTP 403 with this code without the `policy:*` permissions, even when the wallet limits endpoints work. |
| `PAL000.020` | invalid request | The request fails schema validation, for example a missing `trigger` or `operator`, an unknown `kind` or matcher type, a negative `threshold`, a `duration` of one year or more, values on an `ALL_COUNTERPARTIES` matcher, the deprecated `COUNTERPARTY` matcher type, or a `definition` object that doesn't match `kind`. |
| `PAL006.016` | the action you are attempting to perform is not allowed | You tried to delete a rule that is waiting for creation or deletion approval. The wallet limits endpoints also return this code for a wallet limit ID that doesn't exist on the wallet. |
| `PAL006.023` | limit policy already exists | Wallet limits endpoints only. HTTP 409. The wallet already has a limit, including a rejected one, with the same limit type, symbol, and matchers. The amount and duration don't count, so a limit that differs only in `limitQty` or `duration` is a duplicate. |
| `PAL006.028` | invalid matcher value | A matcher value has the wrong format, for example a `COUNTERPARTY_ID` value that isn't a UUID or a `CHAIN_ID` value that isn't an integer. |
| `PAL006.038` | this policy rule type cannot be created yet | This rule kind isn't available for your organization. |
| `PAL006.039` | an equivalent policy rule already exists | HTTP 409. The wallet already has a rule, not yet deleted, with the same kind, trigger, asset, aggregation, and matchers. The duration never counts. For `LIMIT` and `TRAVEL_RULE` rules, the threshold doesn't count either, so two `LIMIT` rules that differ only in amount or window are duplicates. For `APPROVAL` rules, the threshold does count, so a different threshold makes the rule distinct. Rules created through the wallet limits endpoints count too. |
| `PAL006.040` | *(varies)* | The rule is invalid. The message names the problem, for example `amount operator GTE is not yet supported`, `ROLLING aggregation requires a positive duration`, `an amount condition requires an explicit asset`, `limit rules are outbound only (OUTBOUND_INITIATED)`, or an asset on a different blockchain from the wallet. |
| `PAL006.041` | policy rule not found | HTTP 404. The rule doesn't exist on this wallet. |
| `PAL006.046` | travel-rule policy rules require a configured travel-rule connection | You tried to create a `TRAVEL_RULE` rule before you [connected Notabene](/products/wallet/compliance/travel-rule/set-up-travel-rule#connect-your-notabene-account-via-the-console). |
| `PAL006.048` | approval rules cannot be created on a wallet with enabled compliance integrations | You tried to create an `APPROVAL` rule on a wallet that has compliance screening integrations turned on. |


For transaction rejection codes, see [Transaction status changes](/products/wallet/changelogs/policy-engine-v2-transaction-status-changes#structured-rejection-details).

## Related documentation

- [Policies](/products/wallet/governance-and-controls/policies) – Understand rule kinds, scope, lifecycle, and evaluation
- [Manage policies via the console](/products/wallet/governance-and-controls/policies/manage-policies) – Create, change, and delete policy rules in the console
- [Manage policy rules via the API](/products/wallet/governance-and-controls/policies/manage-policies-api) – Create, list, get, and delete policy rules with the API
- [Policy best practices](/products/wallet/governance-and-controls/policies/best-practices) – Recommended patterns and troubleshooting