ActionBoxDOCS

Approval Policies

Require any reviewer, every reviewer, or a quorum before a restricted Action can resolve.

Approval Policies let a Team Source collect votes from several named reviewers. Each reviewer gets one vote for the current Action version. One rejection closes the Action. Approvals close it when the policy reaches its threshold.

Before you create a policy

An Approval Policy needs:

  • a Team workspace;
  • visibility: "restricted";
  • one list of 1 to 15 active owners, admins, or approvers, supplied by email or user ID; and
  • a boolean interaction or a single-choice interaction with exactly two options.

Owners and admins can see a restricted Action, but they can vote only when they are named as reviewers. The assignee helps the team coordinate work and does not change the voting threshold.

ModeApproval threshold
anyOne named reviewer approves.
allEvery named reviewer approves.
quorumrequired_approvals named reviewers approve.

Create a 2-of-3 policy

Send the request with a Source API key. Use one stable Idempotency-Key for the logical Action and reuse it if the request outcome is uncertain.

POST /v1/actions HTTP/1.1
Host: api.actionbox.cloud
Authorization: Bearer axb_live_...
Idempotency-Key: deploy-build-8492
Content-Type: application/json

{
  "title": "Approve the production deploy",
  "description": "Build 8492 passed the staging checks.",
  "visibility": "restricted",
  "reviewer_emails": [
    "release@example.com",
    "security@example.com",
    "operations@example.com"
  ],
  "options": [
    {"id": "approve", "label": "Approve", "style": "primary"},
    {"id": "reject", "label": "Reject", "style": "destructive"}
  ],
  "approval_policy": {
    "schema_version": 1,
    "mode": "quorum",
    "required_approvals": 2,
    "approval_option_id": "approve",
    "rejection_option_id": "reject",
    "allow_source_override": false
  }
}

Use reviewer_user_ids instead of reviewer_emails when you store reviewer identities in configuration. IDs are available under Settings > Team. Do not send both reviewer fields.

Any reviewer

Use a boolean interaction when the answers are simply approve and reject:

{
  "title": "Approve the maintenance window",
  "visibility": "restricted",
  "reviewer_user_ids": ["usr_primary", "usr_backup"],
  "interaction": {"type": "boolean", "label": "Proceed?"},
  "approval_policy": {
    "schema_version": 1,
    "mode": "any",
    "allow_source_override": false
  }
}

Every reviewer

Change the mode to all when every named reviewer must approve:

{
  "title": "Approve privileged access",
  "visibility": "restricted",
  "reviewer_emails": ["security@example.com", "operations@example.com"],
  "interaction": {"type": "boolean", "label": "Grant access?"},
  "approval_policy": {
    "schema_version": 1,
    "mode": "all",
    "allow_source_override": false
  }
}

Creation fields

FieldTypeRules
visibilitystringMust be restricted.
reviewer_emailsstring arrayOne to 15 active workspace reviewers. Mutually exclusive with reviewer_user_ids.
reviewer_user_idsstring arrayOne to 15 stable ActionBox user IDs. Mutually exclusive with reviewer_emails.
approval_policyobjectImmutable policy described below.

Approval policy schema

FieldTypeRequiredRules
schema_versionintegerNoThe current value is 1.
modeany, all, or quorumYesSelects the approval threshold.
required_approvalsintegerFor quorumMust be between 1 and the reviewer count. Omit it for any and all.
approval_option_idstringFor single choiceMust match one of the two option IDs.
rejection_option_idstringFor single choiceMust match the other option ID.
allow_source_overridebooleanNoDefaults to false. It cannot be changed later.

Boolean Actions use true for approval and false for rejection, so they do not use option mapping fields. Text, number, rating, multiple-choice, form, and acknowledgment Actions cannot use an Approval Policy.

Policy Actions fail closed

Do not configure on_expire.resolve. An incomplete policy expires without creating a human decision. To change the policy itself, cancel the Action and create another one.

Read approval progress

Create, get, list, wait, webhook, and terminal responses can include approval_policy and approval_progress:

{
  "approval_policy": {
    "schema_version": 1,
    "mode": "quorum",
    "required_approvals": 2,
    "approval_option_id": "approve",
    "rejection_option_id": "reject",
    "allow_source_override": false
  },
  "approval_progress": {
    "state": "pending",
    "required_approvals": 2,
    "approval_count": 1,
    "rejection_count": 0,
    "remaining_approvals": 1,
    "eligible_reviewer_count": 3,
    "votes": [
      {
        "id": "avt_...",
        "reviewer_user_id": "usr_release",
        "outcome": "approve",
        "option_id": "approve",
        "response": null,
        "reason": "Staging evidence reviewed.",
        "action_version": 1,
        "created_at": "2026-09-06T18:42:00Z"
      }
    ]
  }
}
Progress fieldMeaning
statepending, approved, rejected, overridden, expired, or cancelled.
required_approvalsNumber of approvals needed for the current roster.
approval_countCurrent-version approval votes.
rejection_countCurrent-version rejection votes. A rejection is terminal.
remaining_approvalsAdditional approvals needed.
eligible_reviewer_countReviewers currently eligible to vote.
votesVotes for the current Action version. Older invalidated votes remain in event history.

User-session responses can include reviewer profiles already visible in the workspace. Source responses, callbacks, and receipts use stable user IDs and do not expose reviewer emails or display names.

Record a vote

Reviewers normally vote in the web or mobile inbox. A user-session integration can call the same endpoint. Source API keys cannot cast votes.

POST /v1/actions/act_8492/votes HTTP/1.1
Host: api.actionbox.cloud
Authorization: Bearer axb_usr_...
Idempotency-Key: vote-act-8492-release-v1
Content-Type: application/json

{
  "action_version": 1,
  "fingerprint": "sha256:<64 lowercase hex characters>",
  "option_id": "approve",
  "reason": "Staging evidence and rollback plan reviewed."
}

For a boolean Action, replace option_id with a typed response:

{"response": {"type": "boolean", "value": true}}

A successful vote returns the updated Action, the recorded vote, and the current resolution state:

{
  "data": {
    "id": "act_8492",
    "status": "open",
    "action_version": 1,
    "fingerprint": "sha256:<64 lowercase hex characters>",
    "approval_policy": {"schema_version": 1, "mode": "quorum", "required_approvals": 2},
    "approval_progress": {
      "state": "pending",
      "required_approvals": 2,
      "approval_count": 1,
      "rejection_count": 0,
      "remaining_approvals": 1,
      "eligible_reviewer_count": 3,
      "votes": [
        {
          "id": "avt_...",
          "reviewer_user_id": "usr_release",
          "outcome": "approve",
          "option_id": "approve",
          "response": null,
          "reason": "Staging evidence and rollback plan reviewed.",
          "action_version": 1,
          "created_at": "2026-09-06T18:42:00Z"
        }
      ]
    }
  },
  "vote": {
    "id": "avt_...",
    "reviewer_user_id": "usr_release",
    "outcome": "approve",
    "option_id": "approve",
    "reason": "Staging evidence and rollback plan reviewed.",
    "action_version": 1,
    "created_at": "2026-09-06T18:42:00Z"
  },
  "resolution": "pending",
  "idempotent_replay": false
}

The first accepted rejection returns resolution: "resolved". An approval does the same when it reaches the threshold. Until then, the Action remains open.

Reuse the same idempotency key only for the same vote request. An identical retry returns the original vote, including after resolution. A different request with the same key or a second vote from the same reviewer fails with a conflict.

Vote on the version you reviewed

A material Action update or reviewer change invalidates earlier votes. Fetch the current action_version and fingerprint, review the latest content, and submit a new vote.

Change the reviewer roster

An owner or admin can replace the roster while the Action is open:

PATCH /v1/actions/act_8492/reviewers HTTP/1.1
Host: api.actionbox.cloud
Authorization: Bearer axb_usr_...
Content-Type: application/json

{"reviewer_user_ids": ["usr_primary", "usr_backup"]}

Replacing the roster increments the Action version, changes its fingerprint, and invalidates every earlier vote. The reviewers must inspect the updated Action and vote again. If a membership change leaves too few eligible reviewers to reach the threshold, the Action stays open and workspace managers are notified. An owner or admin must install a valid roster before voting can continue.

Removing a member, changing a reviewer's role, or leaving the workspace can also invalidate votes. The service keeps those older votes in event history and excludes them from current progress.

If a reviewer later deletes their ActionBox account, retained database-backed vote and event records replace that account identifier with null. Current votes are invalidated before access is removed. A signed receipt that was already issued remains immutable and retains the pseudonymous reviewer ID captured when it was signed.

Notifications and visibility

ActionBox notifies every pending named reviewer when the Action is created. After someone votes, reminders stop for that reviewer and continue for the reviewers who have not voted. A material update or roster change creates a distinct Approval required again notification for the current roster. It uses each reviewer's New Actions and re-review requests push preference; the in-app record is retained even when push is disabled.

For a terminal Action, required_approvals and eligible_reviewer_count remain the snapshot used to make that decision. If a reviewer later deletes their account, their vote is anonymized but the historical threshold does not change.

Workspace owners and admins retain management visibility. A manager-copy preference can send them notifications, but it does not give them a vote. A Team downgrade blocks new policy Actions and roster changes. Existing open policy Actions can still finish, expire, or be cancelled.

Use the CLI

Repeat either reviewer flag. Do not mix email and user ID flags.

actionbox ask "Approve the production deploy" \
  --private \
  --reviewer-email release@example.com \
  --reviewer-email security@example.com \
  --reviewer-email operations@example.com \
  --interaction-json '{"type":"boolean","label":"Deploy?"}' \
  --approval-mode quorum \
  --required-approvals 2 \
  --wait \
  --json

--wait continues through partial votes and returns only when the Action is terminal or the local timeout is reached.

Use the Python SDK

from actionbox import Actionbox

client = Actionbox("axb_live_...")
action = client.create(
    title="Approve the production deploy",
    visibility="restricted",
    reviewer_emails=[
        "release@example.com",
        "security@example.com",
        "operations@example.com",
    ],
    interaction={"type": "boolean", "label": "Deploy?"},
    approval_policy={
        "schema_version": 1,
        "mode": "quorum",
        "required_approvals": 2,
        "allow_source_override": False,
    },
)

action.wait(timeout=1800)
print(action.data["approval_progress"])

Use the TypeScript SDK

import { ActionboxClient } from "@actionbox/sdk";

const client = new ActionboxClient({ apiKey: process.env.ACTIONBOX_API_KEY! });
const action = await client.create({
  title: "Approve the production deploy",
  visibility: "restricted",
  reviewer_emails: [
    "release@example.com",
    "security@example.com",
    "operations@example.com",
  ],
  interaction: { type: "boolean", label: "Deploy?" },
  approval_policy: {
    schema_version: 1,
    mode: "quorum",
    required_approvals: 2,
    allow_source_override: false,
  },
});

const terminal = await client.wait(action.id);
console.log(terminal.approval_progress);

Use MCP

Pass the same fields to ask_human. Human votes still happen in ActionBox, not through the Source-authenticated MCP server.

{
  "title": "Approve the production deploy",
  "private": true,
  "reviewer_emails": [
    "release@example.com",
    "security@example.com",
    "operations@example.com"
  ],
  "interaction": {"type": "boolean", "label": "Deploy?"},
  "approval_policy": {
    "schema_version": 1,
    "mode": "quorum",
    "required_approvals": 2,
    "allow_source_override": false
  }
}

Call get_action to read partial progress. Waiting and polling continue until the Action reaches a terminal state.

Source override, cancellation, and expiry

Normal Source resolution returns APPROVAL_POLICY_BLOCKS_SOURCE_RESOLUTION. If the policy allowed an override at creation time, a Source can send an explicit, version-bound override:

{
  "approval_override": true,
  "reason": "Emergency rollback authorized under the incident procedure.",
  "action_version": 1,
  "fingerprint": "sha256:<64 lowercase hex characters>",
  "option_id": "approve"
}

CLI users can pass --approval-override to actionbox resolve. Python, TypeScript, and MCP use approval_override: true in the resolve input. Cancellation remains available and is recorded as cancellation, not approval.

Errors to handle

CodeHTTPMeaning
APPROVAL_POLICIES_DISABLED403Creating policy Actions is not available.
RESTRICTED_ACTIONS_PLAN_REQUIRED403The workspace does not have the required Team entitlement.
INVALID_REVIEWER, REVIEWER_EMAIL_NOT_FOUND400A reviewer is missing, inactive, or outside the workspace.
REVIEWER_CANNOT_REVIEW400A named reviewer does not have an owner, admin, or approver role.
APPROVAL_VOTE_REQUIRED409A client called /decision for a policy Action. Use /votes.
APPROVAL_POLICY_NOT_CONFIGURED409A client called /votes for an ordinary Action. Use /decision.
ACTION_REVIEWER_REQUIRED403The signed-in user is not on the current reviewer roster.
APPROVAL_ALREADY_RECORDED409This reviewer already voted for the current Action version.
ACTION_CHANGED409The submitted version or fingerprint is stale. Fetch and review the Action again.
APPROVAL_POLICY_BLOCKS_SOURCE_RESOLUTION409A Source tried to resolve without an allowed explicit override.
APPROVAL_OVERRIDE_NOT_ALLOWED403The immutable policy did not permit Source override.
APPROVAL_OVERRIDE_BINDING_REQUIRED400An override omitted the current action_version or fingerprint.
APPROVAL_OVERRIDE_REASON_REQUIRED400An override omitted its required reason.
APPROVAL_POLICY_IMMUTABLE409A deduplicated create tried to change an existing policy. Cancel and recreate the Action.
APPROVAL_POLICIES_PLAN_REQUIRED403The workspace cannot create policy Actions or change a policy roster without Team access.
INSUFFICIENT_REVIEWERS_FOR_POLICY400A roster update would leave fewer reviewers than the quorum requires.
APPROVAL_POLICY_REQUIRES_RETURN_EXPIRED400A patch tried to add automatic expiry resolution. Policy Actions always expire without a decision.

Webhooks and receipts

Each accepted vote emits action.approval_recorded. A material change can emit action.approvals_invalidated, and an override emits action.approval_overridden. Only the terminal transition emits action.resolved.

Policy resolutions include a version 2 signed receipt with the policy snapshot, active votes, final threshold state, and resolution method. Existing version 1 receipts remain valid. See Webhooks and Decision receipts for signature verification and delivery behavior.

Test the complete flow

Use a 2-of-3 boolean policy for the first test:

  1. Create the Action and confirm that it is open, with approval_count: 0 and remaining_approvals: 2.
  2. Approve as the first named reviewer. The response should remain pending with one approval.
  3. Repeat the same request with the same idempotency key. The API should return the original vote with idempotent_replay: true.
  4. Approve as a second named reviewer. The Action should resolve and include a version 2 receipt.
  5. Confirm that callbacks contain two action.approval_recorded events and one terminal action.resolved event.
  6. Create another Action and reject it as any named reviewer. It should resolve immediately as rejected.

Also try one failure case before release: submit a vote with an old fingerprint, vote as a user outside the roster, or submit a second decision with a different idempotency key. Each request must fail without changing the recorded result.

Approval Policies are parallel and belong to one Action. Sequential stages and reusable workspace policies are not part of this version.

On this page