Skip to main content
These templates hold larger transfers from an app-owned wallet until the right people approve them, and can deny the largest outright. Approval requirements are JSON only, because the Developer Portal forms cannot express them.
Para turns on conditional approvals per API key, so your beta and production keys are enabled separately. Until it does, validating or saving a policy with an approvalRequirement fails with permissions_v2_extension_unavailable.

Set up approvers

An approval comes from a person signed in to Para, never from your server. Your backend decides who those people are before the first case opens.
1

Assign roles

Once the policy is active, give the roles it names, such as approver, to the Para users who will review requests. Your backend assigns roles inside an authorization scope, and Para accepts only roles that an active policy declares.
2

Bind the wallet

Bind the app-owned wallet to the same authorization scope, so its cases go to the people you assigned.
3

Review in Para

Approvers open each case in Para and approve or reject it.
The request comes from your backend, so Para does not know which person asked for it. If a team member asked for the transfer, pass their Para user ID as initiatorUserId on the signing request, and Para leaves them out of every stage. The user must be linked to your app, or the request fails with user is not linked to this app. Once cases are open, a change to role assignments or to the wallet’s binding that would invalidate them fails with 409 APPROVAL_CASES_AFFECTED and lists the affectedCaseIds. Send the change again with force: true to apply it anyway.

Finish or cancel a request

A request that needs approval opens an approval case and does not sign. Para rejects the signing request with POLICY_REVIEW_REQUIRED. The case ID is approvalCaseId, at the top level of the REST response body or in error.data from the server SDK. Every stage needs its approvals before the case is completed. One rejection from an eligible approver ends the case, and a case that is still open after 7 days expires. When the case is completed, send the request again. Through REST, use the same Idempotency-Key as the first attempt. Through the server SDK, pass the case ID as resumeApprovalCaseId. Para still matches the case if you rebuilt the transaction with a new nonce or new gas values. A completed case covers one signature. The conditional approvals guide for your platform shows how to track the case. To drop a request that is still waiting, cancel its case with cancelPartnerApprovalCase(caseId) in the REST SDK. The case ends as cancelled and the request never signs.
An approval case that has not been used to sign is invalidated when the active policies that apply to its request change, for example when a policy is replaced, activated or turned off. An invalidated case cannot complete, so start a new request to open a new case.

Require approval above a threshold

Use this when routine transfers should flow and large ones need a second pair of eyes. ETH transfers on Base above 1 ETH wait for one person with the approver role.
guardrail-approval-threshold.json
Test it on your Development key: assign approver to a test user and bind the wallet, then sign a 2 ETH transfer with an Idempotency-Key. Approve the case as that user, send the request again with the same key, and it signs.

Sign, approve or deny by amount

Use this to split transfers into three tiers: up to 1 ETH signs, more than 1 ETH and up to 10 ETH waits for an approver, and more than 10 ETH is denied.
guardrail-approval-tiers.json
A 15 ETH transfer matches both rules. Para ranks deny above require_approval, so the transfer is denied without opening a case. The order of the rules in the policy does not change this.
Test it on your Development key: sign 0.5 ETH, 5 ETH and 15 ETH transfers. Only the 5 ETH transfer opens a case.

Add approvers as the amount grows

Use this when larger transfers need more senior approval on top of the usual one. Every transfer above 1 ETH needs an approver, and transfers of 10 ETH or more also need a finance-lead.
guardrail-approval-amount-bands.json
amountFact names the amount Para compares with each stage’s minimumAmount. The first stage has no minimum and applies to every transfer the rule matches. A later stage joins when the amount is at least its minimum, so higher amounts add stages instead of replacing them. Stages run in order. The safeKnobs entry lets your backend, or a configuration manager you name, move the finance-lead threshold for an authorization scope without publishing a new policy version. min and max bound the value, so here it can sit anywhere from 5 ETH to 20 ETH. The published 10 ETH must fall inside that range, and saving a value outside it fails with safe_knob_value_invalid. Without max, the threshold could only be lowered.
A person counts once per case. Someone who approved the first stage cannot also approve the second, even if they hold both roles, so assign finance-lead to someone other than your approvers.
Test it on your Development key: sign a 10 ETH transfer and check that the case moves to the finance-lead stage only after the approver stage completes.

Publish your tiers

On beta, roles go to test users. Production is a separate environment, so once the tiers behave as expected, request a production review and set up the production roles and wallet binding there too.