Manual Approval
Add a manual_approval gate to any step: respond via the in-app inbox, email, or API, edit values inline, and auto-reject on timeout.
manual_approval encodes "wait for someone to confirm before continuing" as a step modifier: when execution reaches a step configured with it, it pauses and only runs the step body after approval. Until then, the system performs no action for that step or anything downstream of it.
Configuration
- step: apply_changes
agent: executor
input: "把已审核的变更应用到线上"
manual_approval:
enabled: true
approvers: # 平台用户 ID;留空 = 发起人 / 有执行权限者可批
- "usr_team_lead"
timeout: 86400 # 秒;24 小时无人批复自动拒绝
approval_message: |
请确认是否应用本轮变更。
拒绝或超时不会执行当前应用步骤。It can attach to any step (agent, code, sub_workflow, etc.), and is often used on a lightweight code step that just prints status, acting as a pure "human gate."
Parameters
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | — | Whether to enable the approval gate (required) |
approvers | string[] | [] | Specify reviewers (platform user IDs). When non-empty, only users on the list and admins can decide — not even the person who started the execution, which naturally enforces separation of duties; when empty, the person who started the execution or any collaborator with execute permission on the workflow can decide |
timeout | long | 3600 | Timeout in seconds. If no one decides by the deadline, it's auto-rejected and marked TIMED_OUT |
approval_message | string | null | The note shown to the reviewer, appearing on the approval card and in notification emails |
reviewable_actions | array | [] | Editable approval-form groups, where the reviewer can select a subset and edit values before approving (see below) |
Approval Process
- When the run reaches a step with manual_approval, that step moves into the waiting state (step status AWAITING_APPROVAL); the execution itself stays RUNNING, it simply stops advancing
- Create an approval record and start the timeout; the console receives an approval_request real-time reminder through WebSocket. The system will only send additional emails when the approval email notification is enabled in the deployment and the recipient account has an email address.
- The reviewer decides through one of three channels: the in-app approval list, a one-click link in a message, or the API
- Approve → the step continues; reject → the step is treated as failed (you can wire a notification branch with on_failure); timeout → auto-rejected
Channel 1: The In-App Approval List
ConsoleApprovalspage lists all approvals visible to you, with card and table views and status filtering. You can attach a note when approving or rejecting, and the note is saved in the approval record. Approvals configured with reviewable_actions expand into a selectable, editable table.
Channel 2: Email Notifications and One-Click Links
Approval email is controlled by a deployment-level setting and may be disabled by default. When enabled, the system sends to the approvers list, or to the execution initiator if the list is empty. The email can include the workflow and step names, execution ID, number of reviewable actions, timeout information, approval_message, and—when a public site URL is configured—a link to the approval list. Email delivery failure does not prevent creation of the approval record.
In messages that can't set custom headers, like email, Slack, and Telegram, you can also embed a one-click decision link:
GET https://braidrun.com/api/webhook-trigger/approval/{approvalId}/approve?api_key=dyk_...
GET https://braidrun.com/api/webhook-trigger/approval/{approvalId}/reject?api_key=dyk_...The link is authenticated with an API Key (APPROVAL_RESPOND scope), placed in the api_key query parameter. To prevent accidental triggers by link-preview bots (Slack unfurls, email-client prefetching), the first open only renders a confirmation page and performs no action; the decision is only made when you click the confirm button and the request repeats with confirm=1. The page carries no-store and noindex headers to prevent caching or search-engine replay.
Channel 3: The API
The v1 open API provides 5 approval endpoints, all requiring the APPROVAL_RESPOND scope:
| Endpoint | Purpose |
|---|---|
GET /api/v1/approvals | The approval list, filterable by status |
GET /api/v1/approvals/{id} | Approval Details |
GET /api/v1/approvals/execution/{executionId} | Look up the approvals associated with an execution |
POST /api/v1/approvals/{id}/approve | Approve; the body can include comment and editedItems |
POST /api/v1/approvals/{id}/reject | Reject; the body can include comment |
# 批准(可附备注)
curl -X POST https://braidrun.com/api/v1/approvals/<approval-id>/approve \
-H "Authorization: Bearer dyk_..." \
-H "Content-Type: application/json" \
-d '{"comment": "确认执行"}'
# 拒绝
curl -X POST https://braidrun.com/api/v1/approvals/<approval-id>/reject \
-H "Authorization: Bearer dyk_..." \
-H "Content-Type: application/json" \
-d '{"comment": "本轮预算已用完"}'editedItems submits the selected/edited subset by group name, matching the structure of the editable approval form in the next section; omitting it approves everything as-is.
Editable Approval Forms (reviewable_actions)
Some approvals are per-recommendation: out of a batch of recommendations you run only some, and a few rows even need value changes. reviewable_actions turns these approvals into a table — the reviewer checks the rows to run, edits columns marked editable: true directly (for example changing a bid), and then clicks approve.
manual_approval:
enabled: true
timeout: 86400
approval_message: |
请审核本轮调价建议:可勾选要执行的行,并直接修改建议出价。
reviewable_actions:
- name: raise_bid
title: 加价建议
source_var: raise_bid_file # 变量值 = 建议清单 JSON 数组文件路径
output_var: raise_bid_approved_file # 批准后 = 勾选/编辑后的子集文件路径
key_field: item_id
columns:
- { key: item_name, label: 名称, editable: false }
- { key: current_bid, label: 当前出价, type: number, editable: false }
- { key: proposed_bid, label: 建议出价, type: number, editable: true }
- { key: reason, label: 理由, editable: false }name— The group's internal name, also used as the output-file prefix (after approval the engine writes reviewed_<name>.json)source_var— Points to a variable whose value is the recommendation list generated by an upstream step (the path to a JSON array file)output_var— A variable set after approval, whose value is the path to the selected/edited subset file; have the downstream execution step read it instead, and it will process only the approved rowskey_field— The row-identity field, used to align rows after editing; if absent, rows are aligned by array indexcolumns— The table's column definitions; columns with editable: true can be changed by the reviewer, and type: number renders as a numeric input
The submitted subset is validated server-side: data that doesn't belong to any group is filtered out; when key_field is defined, each row must map to an entry in the original list, so fabricating a nonexistent row can't get through; columns with editable: false keep their original values, and changes to them are ignored. When a group has no rows checked, an empty array is written out, and the downstream step handles it as "empty, skip."
Rejection and timeout: the gated step does not run
The platform does not run the gated step or any downstream steps before approval. On rejection or timeout:
- The step body doesn't run, and downstream steps that depend on it aren't triggered
- You can use an on_failure branch to attach an "approval rejected" notification or archival step
- A timeout is auto-rejected by the system, with the decider recorded as system; every approval decision (including decider, time, and note) is written to the audit log
Upstream steps completed before approval will not be automatically rolled back. If irreversible side effects may also occur upstream, approval gating should be placed before these actions.
Approval records are stored durably. After a service restart, pending approvals are restored as-is, the timeout clock continues to the original deadline, and reviewers can decide as usual.