Portal Community

When to Use

CRITICAL — Workflow suspension: The Approval node suspends workflow execution at the waiting port. The workflow remains suspended until a decision is made or the deadline expires. Always configure deadlineSeconds and autoOnTimeout to prevent workflows from hanging indefinitely.

Two-Phase Execution

Phase 1 — Suspend

Request Creation

  • Creates an EngageSession for this execution
  • Creates an InboxItem for each actor in the actors array
  • Sends notification to each actor (in-app, email, push)
  • Workflow execution suspends and yields CPU
  • waiting port fires immediately
  • Deadline countdown begins if configured
Phase 2 — Resume

Decision Processing

  • Actor opens inbox and reviews approval request
  • Actor submits decision: approve, reject, escalate, or abstain
  • BizFirst engine resumes the suspended workflow
  • Decision data (actor, time, comments) injected into output
  • Execution continues on the matching output port

Configuration

SettingRequiredDescription
titleOptionalThe heading shown to approvers in their inbox. Default: "Approval Required". Supports BizFirst expressions.
descriptionOptionalSupporting text explaining what is being approved and any context needed for the decision. Supports BizFirst expressions. Rendered as formatted text in the inbox.
requireReasonOptionalBoolean, default false. When true, approvers must enter a text reason/comment before submitting any decision. Enforced for compliance workflows (SOX, ISO 27001).
allowAbstainOptionalBoolean, default false. When true, approvers may choose "Abstain" as a valid decision. Useful for committee-style approvals where abstention is a distinct recorded outcome.
strategyOptionalVoting strategy. One of: "Anonymous" (default — all actors must approve), "AnyOne" (first actor to decide determines the outcome), "NofM" (N of M actors must approve, where N is set by requiredCount).
requiredCountOptionalInteger. Required when strategy is "NofM". Minimum value: 1. Must be less than or equal to the total number of actors. Example: 2 for a 2-of-3 approval requirement.
deadlineSecondsOptionalInteger. Default 0 (no deadline). When positive, the workflow automatically takes the autoOnTimeout action if no decision is reached within this many seconds. Example: 86400 (24 hours).
autoOnTimeoutOptionalAction when deadline expires. One of: "rejected" (routes to rejected port), "approved" (routes to approved port), "escalated" (routes to escalated port and notifies escalateToUserId).
escalateToUserIdOptionalString user ID. Required when autoOnTimeout is "escalated". This user receives an escalation notification when the deadline expires.
actorsRequiredJSON array of actor objects. At least one actor is required. Each actor: { "id": "user_or_role_id", "actorType": "User"|"Role", "label": "display name", "correlationField": "optional" }. Role actors expand to all current members of that role.
watchersOptionalJSON array of watcher objects (same format as actors). Watchers receive a read-only notification of the approval request and all decisions but cannot vote. Useful for audit trail participants like compliance officers.

Approval Strategy Comparison

StrategyHow It WorksWhen to UseExample
Anonymous All actors in the list must approve. A single rejection from any actor immediately rejects the request. Abstentions do not count as approvals. High-value or regulated decisions requiring unanimous consent. Compliance sign-offs, contract execution. Both Legal AND Finance must approve before a contract is sent for signature.
AnyOne The first actor to submit any decision determines the outcome. Other actors' pending requests are automatically cancelled once a decision is made. Routing to a role where any team member can handle the request. Support team approvals, first-available manager confirmation. Any member of the Finance team can approve an expense claim — assigned to role:finance.
NofM N specific actors out of M total actors must approve. The workflow waits until N approvals or any rejection that makes N impossible to reach. Committee-style voting. Change Advisory Boards, multi-signer documents, quorum-required decisions. 3 of 5 board members must approve a strategic investment decision.

Output Ports

PortWhen It Fires
waitingPhase 1 — fires immediately when the approval request is created and the workflow suspends. Use this to notify stakeholders that a decision is pending.
approvedThe voting strategy's approval threshold is met. Route to the "proceed" branch: update systems, notify requesters, continue the process.
rejectedAn actor rejects the request, or the strategy determines rejection is inevitable (e.g., in NofM, enough rejections make approval impossible). Route to rejection handling: notifications, alternative paths, requester feedback.
escalatedAn actor explicitly escalates the request, or autoOnTimeout is "escalated" and the deadline expires. Route to a higher-authority approval node or escalation notification.
abstainedAn actor abstains (only available when allowAbstain is true). In committee approvals, abstention is a distinct recorded outcome — route accordingly.
errorInternal error: an actor ID cannot be resolved, a required configuration is invalid, or a critical system failure occurs. Route to error notification and alerting.

Actor Types

Actor TypeBehaviourWhen to Use
User Creates an InboxItem for a single, specific user identified by their user ID. That user alone can make the decision. Named individual approvers: the CFO, a specific manager, a named compliance officer.
Role Expands to all current members of the specified role. Each member receives an InboxItem. With AnyOne strategy, the first member to respond decides; with Anonymous, all must respond. Team-based approvals where any qualified team member can decide. Finance team, Legal team, IT support.
Never use System actor type for real approvals: The System actor type is reserved for automated testing and synthetic workflow simulations. Using it in a production approval workflow bypasses the human-in-the-loop requirement entirely and invalidates the approval audit trail.

Output Fields (on approved, rejected, escalated, abstained ports)

FieldTypeDescription
decisionstringThe decision outcome: "approved", "rejected", "escalated", or "abstained".
decided_bystringUser ID of the actor who made the final decision.
decided_atstringISO 8601 timestamp of when the decision was submitted.
commentsstringOptional free-text comments provided by the approver. Empty string if no comment was provided or if requireReason was false.
all_decisionsarrayFor multi-actor approvals, an array of all individual decisions recorded, each with actorId, decision, comments, and timestamp.

Sample Configuration

{
  "nodeType": "approval",
  "formId": 11035,
  "settings": {
    "title": "Purchase Order Approval — {{ $output.poNode.poNumber }}",
    "description": "PO for {{ $output.poNode.vendorName }} totalling {{ $output.poNode.totalAmount | currency }}.\nRaised by: {{ $output.poNode.requestedBy }}\nCost centre: {{ $output.poNode.costCentre }}\nBusiness justification: {{ $output.poNode.justification }}",
    "requireReason": true,
    "allowAbstain": false,
    "strategy": "AnyOne",
    "deadlineSeconds": 86400,
    "autoOnTimeout": "escalated",
    "escalateToUserId": "user:cfo-deputy",
    "actors": [
      { "id": "role:finance-director", "actorType": "Role", "label": "Finance Director" }
    ],
    "watchers": [
      { "id": "user:procurement-manager", "actorType": "User", "label": "Procurement Manager" }
    ]
  }
}

Sample Output

Success Port — Approved

{
  "decision": "approved",
  "approvedBy": [
    {
      "actorId": "usr_abc123",
      "actorName": "Jane Smith",
      "decidedAt": "2025-03-15T14:32:00Z",
      "reason": "Budget within quarterly limit. Approved.",
      "vote": "approved"
    }
  ],
  "rejectedBy": [],
  "abstainedBy": [],
  "totalActors": 1,
  "requiredCount": 1,
  "outcome": "approved",
  "completedAt": "2025-03-15T14:32:00Z",
  "timeoutAt": null,
  "sessionId": "approval_session_xyz789"
}

Rejected Port

{
  "decision": "rejected",
  "rejectedBy": [
    {
      "actorId": "usr_def456",
      "actorName": "Bob Johnson",
      "decidedAt": "2025-03-15T15:10:00Z",
      "reason": "Exceeds authority limit. Please escalate to VP Finance.",
      "vote": "rejected"
    }
  ],
  "approvedBy": [],
  "outcome": "rejected",
  "completedAt": "2025-03-15T15:10:00Z"
}

Expression Reference

ExpressionValue
{{ $output.approvalNode.decision }}The outcome string: "approved" or "rejected".
{{ $output.approvalNode.decided_by }}User ID of the decision-maker.
{{ $output.approvalNode.decided_at }}ISO timestamp of the decision.
{{ $output.approvalNode.comments }}The approver's comment text.

Node Policies & GuardRails

PolicyRationale
Always configure autoOnTimeoutWithout a timeout action, the workflow suspends indefinitely if an approver is on leave, ignores their inbox, or leaves the organization. All production approval nodes must have a deadline and a defined timeout action.
Use Role actor type for scalable team approvalsSpecifying individual user IDs creates a brittle configuration that breaks when people change roles. Using a Role actor means the approval group automatically reflects the current team members without workflow changes.
Enable requireReason for compliance workflowsFor SOX, GDPR, ISO 27001, and internal audit trails, approvers must document why they approved or rejected. This creates an irrefutable audit record that the decision was deliberate and informed.
Use Anonymous strategy for contractual and regulatory approvalsWhen unanimous consent is legally or policy-required, Anonymous ensures every named approver's decision is on record. AnyOne is faster but may not satisfy regulatory requirements for specific approvers.
Connect all six output portsLeaving the error or escalated ports unconnected means those workflow paths silently stall. Every output port must connect to at least a notification or logging node.
Log all_decisions for multi-actor NofM approvalsIn committee-style approvals, persist the complete decision array to an audit table immediately after the success/rejection port fires. Individual vote records are essential for governance reviews.

Pattern Examples

Pattern 1 — Two-Person Purchase Order Sign-Off (Anonymous)

Both the Finance Director AND the CFO must approve before a high-value PO is submitted to the ERP. A single rejection from either immediately rejects the request.

{
  "strategy": "Anonymous",
  "deadlineSeconds": 172800,
  "autoOnTimeout": "rejected",
  "requireReason": true,
  "title": "PO Approval Required: {{ $output.poNode.poNumber }}",
  "actors": [
    { "id": "user:finance-director-001", "actorType": "User", "label": "Finance Director" },
    { "id": "user:cfo-001",              "actorType": "User", "label": "CFO" }
  ]
}

Pattern 2 — IT Change Advisory Board 3-of-5 Vote (NofM)

An infrastructure change requires at least 3 of 5 CAB members to approve before the deployment pipeline is triggered. The node waits up to 48 hours for the quorum.

{
  "strategy": "NofM",
  "requiredCount": 3,
  "deadlineSeconds": 172800,
  "autoOnTimeout": "rejected",
  "allowAbstain": true,
  "requireReason": true,
  "title": "CAB Vote: {{ $output.changeNode.changeTitle }}",
  "actors": [
    { "id": "user:cab-member-1", "actorType": "User", "label": "CAB Member 1" },
    { "id": "user:cab-member-2", "actorType": "User", "label": "CAB Member 2" },
    { "id": "user:cab-member-3", "actorType": "User", "label": "CAB Member 3" },
    { "id": "user:cab-member-4", "actorType": "User", "label": "CAB Member 4" },
    { "id": "user:cab-member-5", "actorType": "User", "label": "CAB Member 5" }
  ]
}

Pattern 3 — Leave Request with 24-Hour Manager SLA (AnyOne)

Any member of the management role can approve or reject a leave request. After 24 hours with no decision, the request escalates to the HR director.

{
  "strategy": "AnyOne",
  "deadlineSeconds": 86400,
  "autoOnTimeout": "escalated",
  "escalateToUserId": "user:hr-director",
  "title": "Leave Request: {{ $output.formNode.form_data.employeeName }}",
  "description": "{{ $output.formNode.form_data.employeeName }} has requested {{ $output.formNode.form_data.leaveType }} leave from {{ $output.formNode.form_data.startDate }} to {{ $output.formNode.form_data.endDate }}. Remaining balance: {{ $var.leaveBalance }} days.",
  "actors": [
    { "id": "role:line-managers", "actorType": "Role", "label": "Line Managers" }
  ]
}