Skip to Content
WorkflowsNodesHITL Approval Node

HITL Approval Node

Pause workflow execution for human approval or rejection.

Overview

The HITL (Human-in-the-Loop) Approval Node pauses workflow execution and sends a task to human reviewers for approval or rejection. It enables:

  • Compliance gates - Require legal or compliance sign-off
  • Quality assurance - Manual verification before critical operations
  • High-stakes decisions - Human judgment for sensitive actions
  • Audit trails - Record who approved/rejected and why

Workflows remain paused until a reviewer takes action.

When to Use

Use a HITL Approval Node when you need:

  • Human judgment on decisions automated systems can’t make reliably
  • Compliance requirements mandating human oversight
  • Risk mitigation for high-value or irreversible operations
  • Quality gates before exposing outputs to customers
  • Escalation paths when automated validation fails

For data correction (not just yes/no), use HITL Correction Node.

Configuration

Approval Criteria

Define what reviewers are approving:

FieldDescriptionExample
TitleShort summary”Approve invoice payment”
DescriptionContext for reviewer”Verify invoice details before processing payment”
Input sourceData to display for review$.nodes.extract_invoice.output
Required approversNumber of approvals needed1 (single approval), 2 (dual control)

Reviewer Assignment

Specify who can approve:

Assign to specific users:

{ "reviewers": ["user_id_123", "user_id_456"], "assignment_type": "any" }

Assign to role/team:

{ "reviewers": ["role:compliance", "team:finance"], "assignment_type": "any" }

Assignment types:

  • any: Any reviewer can approve
  • all: All reviewers must approve (consensus)
  • majority: More than 50% must approve
  • specific: Specific user(s) must approve

Timeout Settings

Configure what happens if no one approves:

OptionDescriptionExample
Timeout durationTime before auto-action24 hours, 7 days
Timeout actionAuto-approve, auto-reject, or escalateauto-reject
Escalation targetWho to notify on timeoutmanager@company.com

Display Configuration

Control what reviewers see in the approval UI:

Data fields to display:

{ "display_fields": [ { "label": "Invoice Number", "value": "$.nodes.extract.output.invoice_number" }, { "label": "Total Amount", "value": "$.nodes.extract.output.total_amount", "format": "currency" }, { "label": "Vendor", "value": "$.nodes.extract.output.vendor" } ] }

Supporting documents:

{ "attachments": [ { "name": "Original Invoice", "url": "$.data.document_url" } ] }

Review Interface

Reviewers access pending approvals via:

  1. In-app queue - OPERATEReviews in M3 Forge sidebar
  2. Email notification - Sent when task is assigned
  3. API integration - Fetch via tRPC reviews.getPending endpoint

Review UI displays:

  • Task title and description
  • Configured display fields (invoice number, amount, etc.)
  • Workflow context (which workflow, execution timestamp)
  • Supporting attachments (original documents, screenshots)
  • Action buttons - Approve, Reject, Escalate

Reviewers can add comments explaining their decision.

Output

The HITL Approval Node produces results accessible in downstream nodes:

{ "approval_result": { "action": "approved", "reviewer_id": "user_xyz", "reviewer_email": "john.doe@company.com", "timestamp": "2026-03-19T14:32:15Z", "comment": "Invoice verified against PO-12345, approved for payment", "selected_path_id": "approved" } }

Access fields via JSONPath:

  • $.nodes.approval_node.output.approval_result.action - “approved” or “rejected”
  • $.nodes.approval_node.output.approval_result.reviewer_id - Who acted
  • $.nodes.approval_node.output.approval_result.comment - Reviewer notes

Routing Paths

Configure different paths based on approval decision:

{ "paths": [ { "path_id": "approved", "target_node_ids": ["process_payment", "notify_vendor"] }, { "path_id": "rejected", "target_node_ids": ["notify_requester", "archive_rejected"] }, { "path_id": "escalated", "target_node_ids": ["escalation_approval"] } ] }

Workflow continues along the path matching the reviewer’s action.

Example Configurations

Invoice Payment Approval

{ "node_id": "approve_payment", "node_type": "HITL_APPROVAL", "definition": { "title": "Approve Invoice Payment", "description": "Review invoice details and approve payment processing", "input_source": "$.nodes.extract_invoice.output", "reviewers": ["role:accounts_payable"], "assignment_type": "any", "required_approvers": 1, "timeout_hours": 48, "timeout_action": "reject", "display_fields": [ { "label": "Invoice Number", "value": "$.nodes.extract_invoice.output.invoice_number" }, { "label": "Vendor", "value": "$.nodes.extract_invoice.output.vendor" }, { "label": "Amount", "value": "$.nodes.extract_invoice.output.total_amount", "format": "currency" }, { "label": "Due Date", "value": "$.nodes.extract_invoice.output.due_date", "format": "date" } ], "attachments": [ { "name": "Invoice PDF", "url": "$.data.document_url" } ], "paths": [ { "path_id": "approved", "target_node_ids": ["process_payment"] }, { "path_id": "rejected", "target_node_ids": ["notify_requester"] } ] } }

Dual Control Approval

Require two approvers for high-value transactions:

{ "node_id": "dual_approval", "node_type": "HITL_APPROVAL", "definition": { "title": "High-Value Payment Approval", "description": "Dual approval required for payments over $50,000", "reviewers": ["role:finance_manager", "role:cfo"], "assignment_type": "all", "required_approvers": 2, "timeout_hours": 72, "timeout_action": "escalate", "escalation_target": "role:ceo", "paths": [ { "path_id": "approved", "target_node_ids": ["execute_payment"] }, { "path_id": "rejected", "target_node_ids": ["cancel_payment"] }, { "path_id": "escalated", "target_node_ids": ["ceo_approval"] } ] } }

Conditional Approval

Only require approval if amount exceeds threshold:

Use a Branch node before HITL Approval to implement conditional logic.

Notifications

Reviewers receive notifications when:

  • Task assigned - Email or in-app notification
  • Reminder - Configurable reminders before timeout (e.g., 24h, 6h, 1h before)
  • Escalation - Notify escalation target on timeout
  • Decision made - Notify requester of approval/rejection

Configure notification settings:

{ "notifications": { "on_assignment": { "email": true, "in_app": true }, "reminders": [ { "hours_before_timeout": 24 }, { "hours_before_timeout": 6 } ], "on_decision": { "notify_requester": true, "notify_watchers": ["manager@company.com"] } } }

Audit Trail

All approval actions are logged in the event_tracking table:

  • Task created - When workflow reaches HITL node
  • Reviewer assigned - Who can approve
  • Viewed - When reviewer opens task
  • Action taken - Approve/reject/escalate with timestamp and comment
  • Timeout - If timeout occurs

Access audit trail via OPERATEEvents or run details.

Best Practices

  1. Set clear approval criteria - Provide enough context for reviewers to make informed decisions
  2. Use appropriate timeouts - Match business requirements (24h for routine, 7d for complex)
  3. Configure reminders - Send reminders before timeout to prevent auto-rejections
  4. Display relevant data - Show fields reviewers need; hide irrelevant details
  5. Add comments - Encourage reviewers to explain their decisions for audit trail
  6. Test assignment logic - Verify correct reviewers are assigned before going to production
  7. Monitor aging tasks - Set up alerts for tasks approaching timeout

Paused workflows consume minimal resources but increase end-to-end latency. Design workflows to minimize approval bottlenecks.

Integration with Review Queue

Pending approvals appear in the centralized review queue:

OPERATEReviews

Features:

  • Filter by workflow, reviewer, age, priority
  • Sort by due date, amount, requester
  • Bulk actions - Approve/reject multiple tasks
  • Delegation - Reassign to other reviewers
  • Export - Download pending tasks as CSV

API Access

Programmatically manage approvals:

// Get pending approvals for current user const pending = await trpc.reviews.getPending.query(); // Approve a task await trpc.reviews.approve.mutate({ task_id: 'task_abc123', comment: 'Invoice verified, approved for payment' }); // Reject a task await trpc.reviews.reject.mutate({ task_id: 'task_abc123', comment: 'Duplicate invoice, rejecting' }); // Escalate a task await trpc.reviews.escalate.mutate({ task_id: 'task_abc123', escalation_target: 'user_xyz', comment: 'Requires senior approval' });
Last updated on