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. For the full request and response schemas, see the Policies operations in the Ripple Custody (Wallet) API reference.
| 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 | Policy rules API only (see Rolling) | 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.
| 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 | 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 |
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. A transaction that equals the threshold passes.
The examples on this page show the definition object. A LIMIT rule request also needs trigger set to OUTBOUND_INITIATED and an assetId outside definition. Without them, the API rejects the rule with PAL006.040. See Create a policy rule via the API.
Rolling and Max total value totals count the asset's outbound transactions that match the rule and aren't rejected or failed. That includes pending transactions, such as ones waiting for approval, and transactions made before you created the rule, so a new Max total value rule can already be at its limit when it becomes active. Sweeps and passkey transfers don't count toward these totals.
Sweeps, passkey transfers, and some signing actions aren't evaluated against limit rules. See Operations that bypass policies.
Limits the maximum amount for any single transaction.
| Field | Value |
|---|---|
aggregation | "SINGLE_TX" (wallet limits: limitType "PER_TX") |
duration | Not used. The policy rules API rejects a duration with PAL006.040. The wallet limits endpoints return "0s", or the duration sent when the limit was created, which has no effect. |
Example: Limit single ETH transactions to 10 ETH maximum.
{
"kind": "LIMIT",
"limit": {
"amount": { "threshold": "10", "operator": "GT", "aggregation": "SINGLE_TX" }
}
}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.
{
"kind": "LIMIT",
"limit": {
"amount": { "threshold": "100", "operator": "GT", "aggregation": "ROLLING", "duration": "86400s" }
}
}Sets a lifetime cap on total withdrawals. Once withdrawals reach the cap, the rule permits no further withdrawals.
| Field | Value |
|---|---|
aggregation | "TOTAL" (wallet limits: limitType "CONSTANT") |
duration | Not used. The policy rules API rejects a duration with PAL006.040. The wallet limits endpoints return "0s", or the duration sent when the limit was created, which has no effect. |
Example: Limit total ETH withdrawals to 1,000 ETH ever.
{
"kind": "LIMIT",
"limit": {
"amount": { "threshold": "1000", "operator": "GT", "aggregation": "TOTAL" }
}
}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 singlevaluestring, which is deprecated. Responses echo each matcher with bothvaluesandvalue. - 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.
- A rule can carry at most one of
USERorAPI_CREDENTIAL, and at most one destination matcher fromCOUNTERPARTY_ID,ADDRESS_ID,WALLET_ID, andALL_COUNTERPARTIES. Combining two of them returnsPAL006.020("duplicate matcher type"). - A rule with no matchers is a base rule. A base limit rule applies only when no limit rule with matchers applies to the transaction. When any limit rule with matchers applies, Ripple Custody (Wallet) ignores all base limit rules for that asset for that transaction, including base rolling and Max total value rules, even if their limits are lower. This replacement happens within each rule kind: base approval rules still apply. A scoped rule can therefore loosen a base limit, and a wallet-wide cap needs a matching scoped rule as well. The console Confirm step warns you when a rule overrides matching rules. See Evaluation order.
- Transactions that a dApp submits through WalletConnect aren't checked against the address book. Ripple Custody (Wallet) records their destination as external, so
COUNTERPARTY_ID,ADDRESS_ID,WALLET_ID, andALL_COUNTERPARTIESmatchers never match them, even when the destination is in your address book. They still need transaction approvers, and limit rules still evaluate them. - The API rejects the deprecated
COUNTERPARTYtype. UseCOUNTERPARTY_IDinstead. ALL_COUNTERPARTIEStakes no values. It matches only destinations that resolve to a counterparty.APPROVALrules don't support matchers.
| 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 |
Applies the rule only to transactions of a specific type.
Values by blockchain:
| Blockchain | Transaction types |
|---|---|
| Ethereum/EVM | Transfer, ContractCall, ContractCreation |
| XRP Ledger | Payment, OfferCreate, OfferCancel, TrustSet, AccountSet, SignerListSet, AMMCreate, AMMDeposit, AMMWithdraw, Clawback. Raw-signed transactions can have any other XRP Ledger transaction type, for example EscrowCreate or EscrowFinish. |
| Bitcoin | Transfer |
Transaction type values are case-sensitive and use PascalCase (for example, OfferCreate, not OFFER_CREATE). Transfers that you create with the transfer endpoints use Payment on the XRP Ledger and Transfer on other blockchains. Raw-signed and WalletConnect transactions use the type that the blockchain connector reports, for example ContractCall for an EVM token transfer, Transfer for an EVM native-coin transfer, or TRANSFER for a native Solana transfer. A rule for Transfer therefore matches raw-signed and WalletConnect EVM native-coin transfers, but not EVM token transfers or Solana transfers.
An AMMCreate transaction withdraws both assets from the wallet. An AMMDeposit transaction withdraws both assets in the two-asset deposit modes, and one asset in the single-asset modes. See the XRPL AMMDeposit reference (last checked 2026-10-17). For an AMMDeposit transaction, the policy engine requires policies for each asset the wallet sends. For issued assets (such as RLUSD or USD), the policy must include the issuer. See the AMM APIs changelog for details.
{
"trigger": "OUTBOUND_INITIATED",
"assetId": "3::XRP",
"matchers": [
{ "type": "TRANSACTION_TYPE", "values": ["Payment"] }
],
"definition": {
"kind": "LIMIT",
"limit": {
"amount": { "threshold": "1000", "operator": "GT", "aggregation": "SINGLE_TX" }
}
}
}Applies the rule only when a specific user initiates the transaction.
{
"matchers": [
{
"type": "USER",
"values": ["8617b5b5-dfcf-4ffc-9f46-f7cec35a88bd"]
}
]
}Applies the rule only when a specific API credential submits the transaction.
{
"matchers": [
{
"type": "API_CREDENTIAL",
"values": ["cred-uuid-here"]
}
]
}Applies the rule only when sending to a specific address from your address book.
{
"matchers": [
{
"type": "ADDRESS_ID",
"values": ["019d1b00-c1f5-77a0-bda7-0a3d09c668b9"]
}
]
}Applies the rule only when sending to a specific counterparty.
{
"matchers": [
{
"type": "COUNTERPARTY_ID",
"values": ["019d1b00-c1f5-77a0-bda7-0a3d09c668b9"]
}
]
}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. When this rule applies to a transaction, it replaces the rules of the same kind that have no matchers for that transaction.
{
"matchers": [
{
"type": "ALL_COUNTERPARTIES",
"values": []
}
]
}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. Only an active rule with a SIGN_FOR matcher that lists that address authorizes a sign-for transaction. Base rules and rules with other matchers never authorize it. If no such rule exists, Ripple Custody (Wallet) rejects the transaction with PAL006.005. On an EVM wallet, each SIGN_FOR value must be an EIP-55 checksummed address. Ripple Custody (Wallet) rejects any other form with PAL006.028.
{
"matchers": [
{
"type": "SIGN_FOR",
"values": ["rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"]
}
]
}Authorizes cross-chain raw signing 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 one of the matcher's values. Limit rules without any matchers (base rules) never apply to cross-chain raw signing. A rule with other matchers applies when all of its matchers match the transaction, whether or not it includes a CHAIN_ID matcher. If no rule applies, Ripple Custody (Wallet) rejects the transaction with PAL006.005. To allow several chains, list each chain ID as a value in a single CHAIN_ID matcher. A rule can hold only one CHAIN_ID matcher: a second one returns PAL006.020 or PAL006.040.
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 applies to every wallet, including non-EVM wallets: in production, an XRP Ledger wallet also gets PAL006.028 for a CHAIN_ID of 1. Native chain IDs are the ones registered for the environment, so sandbox uses testnet IDs. A CHAIN_ID matcher on a non-EVM wallet never matches a transaction.
Example: Limit raw signing on the Optimism chain (chain ID 10) to 1 ETH per rolling hour.
{
"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" }
}
}
}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.
If you delete a counterparty, address, or user that a policy's matcher references, the policy continues to exist with the now-invalid reference. No transaction matches the deleted entity. If it was the only value in the matcher, the policy becomes effectively unmatchable. If the matcher lists other values, the policy still matches those. If it is the only policy for that asset, Ripple Custody (Wallet) rejects the transactions that it covered. Always check for policies that reference an entity before you delete it.
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.
{
"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" }
}
}
}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, read asset.id from the wallet's balances (GET /v2/vaults/{vaultId}/wallets/{walletId}/balances, which needs a WALLETS credential).
The wallet limits endpoints identify the token by symbol and contract instead. Include the contract field with the token's contract address:
{
"limitType": "PER_TX",
"symbol": "USDC",
"limitQty": "10000",
"contract": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}Always include the contract field with the token's contract address when you create a wallet limit for an ERC-20 token.
| 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 | This status is final. A rule that you delete while it's RULE_REJECTED or RULE_ERROR moves straight here, with no approval, and stays readable with this status. Deleting the rule again succeeds without changing anything. When approvers approve the deletion of an enabled rule, Ripple Custody (Wallet) removes the rule instead, and getting it returns PAL006.041. |
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.
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, 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, but viewers can't.
Travel Rule policies are available in sandbox. They aren't available in production yet.
| 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.005 | (varies) | No active LIMIT rule on the wallet applies to the transaction's asset and attributes. Limits are default-deny and apply per asset, and APPROVAL and TRAVEL_RULE rules don't satisfy this check, so a wallet that sends several assets needs a LIMIT rule for each one. |
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 delete endpoint also returns this code for a wallet limit ID that doesn't exist on the wallet. Getting a missing wallet limit returns PAL006.001 (HTTP 404). |
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. LIMIT rules are always available. An organization can have outbound Travel Rule rules available and still get this error for an inbound one. |
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. The check compares the matchers array exactly and in order, so Ripple Custody (Wallet) doesn't treat the same matchers in a different order as duplicates. |
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. Disconnecting Notabene doesn't remove existing Travel Rule rules. See Set up Travel Rule. |
Ripple Custody (Wallet) accepts an APPROVAL rule on a wallet that has compliance screening turned on. See What happens to a transaction.
For transaction rejection codes, see Transaction status changes.
- Policies – Understand rule kinds, scope, lifecycle, and evaluation
- Manage policies via the console – Create, change, and delete policy rules in the console
- Manage policy rules via the API – Create, list, get, and delete policy rules with the API
- Policy best practices – Recommended patterns and troubleshooting