Skip to main content
DocsRunManual Approval

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

yaml
  - 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

FieldTypeDefaultDescription
enabledbooleanWhether to enable the approval gate (required)
approversstring[][]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
timeoutlong3600Timeout in seconds. If no one decides by the deadline, it's auto-rejected and marked TIMED_OUT
approval_messagestringnullThe note shown to the reviewer, appearing on the approval card and in notification emails
reviewable_actionsarray[]Editable approval-form groups, where the reviewer can select a subset and edit values before approving (see below)

Approval Process

  1. When execution reaches a step configured with manual_approval, it pauses and the execution status becomes AWAITING_APPROVAL
  2. 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
  3. The reviewer decides through one of three channels: the in-app approval list, a one-click link in a message, or the API
  4. 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.

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:

text
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:

EndpointPurpose
GET /api/v1/approvalsThe 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}/approveApprove; the body can include comment and editedItems
POST /api/v1/approvals/{id}/rejectReject; the body can include comment
bash
# 批准(可附备注)
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.

yaml
    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 rows
  • key_field — The row-identity field, used to align rows after editing; if absent, rows are aligned by array index
  • columns — The table's column definitions; columns with editable: true can be changed by the reviewer, and type: number renders as a numeric input
Server-Side Validation

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
When Service Restarts

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.