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:
| Field | Description | Example |
|---|---|---|
| Title | Short summary | ”Approve invoice payment” |
| Description | Context for reviewer | ”Verify invoice details before processing payment” |
| Input source | Data to display for review | $.nodes.extract_invoice.output |
| Required approvers | Number of approvals needed | 1 (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:
| Option | Description | Example |
|---|---|---|
| Timeout duration | Time before auto-action | 24 hours, 7 days |
| Timeout action | Auto-approve, auto-reject, or escalate | auto-reject |
| Escalation target | Who to notify on timeout | manager@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:
- In-app queue - OPERATE → Reviews in M3 Forge sidebar
- Email notification - Sent when task is assigned
- API integration - Fetch via tRPC
reviews.getPendingendpoint
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 OPERATE → Events or run details.
Best Practices
- Set clear approval criteria - Provide enough context for reviewers to make informed decisions
- Use appropriate timeouts - Match business requirements (24h for routine, 7d for complex)
- Configure reminders - Send reminders before timeout to prevent auto-rejections
- Display relevant data - Show fields reviewers need; hide irrelevant details
- Add comments - Encourage reviewers to explain their decisions for audit trail
- Test assignment logic - Verify correct reviewers are assigned before going to production
- 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:
OPERATE → Reviews
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'
});Related Nodes
- HITL Correction Node - Allow data editing
- HITL Router Node - Route to specific reviewers
- Guardrail Node - Automated quality gates