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 execution reaches a step configured with manual_approval, it pauses and the execution status becomes AWAITING_APPROVAL
- An approval record is created and the timeout clock starts; the console receives a real-time approval_request over WebSocket, and the reviewer can also receive a notification email
- 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
The console'sApprovalspage 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
With email notifications on, the reviewer (the person who started the execution when approvers isn't specified) receives an email at their account address containing: the workflow and step names, the execution ID, the number of reviewable actions, a timeout notice ("auto-rejected in about N hours"), the approval_message text, and a link to the approval list.
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: Zero Changes
The core promise of the approval gate is that the system performs no action 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
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.