Core ConceptsApprovals

Approvals

The approval request lifecycle: an approver snapshot taken at creation, m-of-n, veto, and returning the reserve.

When the Policy Engine returns queue, the action isn't executed right away. Instead, it becomes an approval request and waits for the required number of approvals. This page covers how a request moves from creation to resolution. For who puts an action in the queue and why, see Policy Engine.

Where an approval request comes from

A state-changing action (a transfer, address deletion, a counterparty change) returns one of two responses:

  • 200: the policy allowed the action (allow), and it was executed immediately.
  • 202: the policy queued the action (queue), and an approval request was created. The response contains its id.

The request carries a snapshot of the decision as of its creation: which policy made the decision (decisionPolicyId), who is allowed to approve, and what the threshold is. That's why later policy edits don't affect requests that already exist.

Lifecycle

StatusMeaningReserve (for transfers)
PENDINGawaiting votesheld in reserved
APPROVEDthreshold reached, action applieddebited
REJECTEDvetoed by an approver, or stalereturned to available
CANCELLEDcancelled by the initiator (transfers: /cancel)returned to available

How voting works (m-of-n)

Votes are cast by members of the request's approver group. The initiator can't vote on their own request (unless the policy explicitly allows it), and everyone gets one vote. For example, with a 2 of 3 threshold, two approve votes are needed; a single reject is a veto, and the request immediately becomes REJECTED.

Voting on a request:

curl -X POST "[BASE_URL]/api/v1/vaults/acme-otc/approval-requests/REQUEST_ID/approvals" \
  -H "Authorization: Bearer $V3_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "approve" }'

The final approve that reaches the threshold applies the target action in the same request. The response contains the final status and a resolvedResultId (or an applyError if the action couldn't be applied).

There are two voting surfaces:

SurfaceUsed forPending status
approval-requests/{id}/approvalsany gated actionPENDING
transfers/{id}/approvalstransfers onlyawaiting_approval

reject is a veto: a single rejection is enough to make the request REJECTED. The threshold is reached only with approve votes. Check the name of the vote field (decision here) in the API Reference.

Snapshots and race protection

Two mechanisms make the outcome predictable:

  • Approver snapshot. A request remembers the approvers and threshold as they were at the time of creation. If the policy changes afterward, requests that are already open aren't affected.
  • Optimistic lock. The final approve applies the action atomically. If the target object has changed since the request was created, the request is marked REJECTED (stale) instead of applying an outdated action. This rules out races between concurrent changes.

Composite requests

Several related actions can be combined into a single request with one approval flow:

  • POST /api/v1/vaults/{code}/approval-requests/composite creates a chain of steps as a single entity. Each step is gated by its own policy; an approve vote counts toward the steps the voter is authorized for, and the request becomes APPROVED once every step reaches its own threshold. A reject vetoes the entire chain. If all steps are allowed immediately, the chain is applied without a request (applied: true).

Where to find approval requests

  • GET /api/v1/vaults/{code}/approval-requests returns the requests visible to you (as the initiator, an approver, or an admin).
  • GET /api/v1/vaults/{code}/approval-requests/{id} returns the details of a single request.

Feed filters:

FilterWhat it filters
statusPENDING / APPROVED / REJECTED / CANCELLED
actionaction type
mine=truerequests where I'm the initiator
pendingMine=truerequests awaiting my vote
from / totime window by creation time
resolvedFrom / resolvedTotime window by resolution time
initiatorId / approverId / decisionPolicyIdby participant and policy

pendingMine=true answers the question "what's waiting for my vote specifically," which is handy for an approver's dashboard.

Terms

Next steps