# Node Types

> All 27 workflow node types — trigger, llm_call, logic, mcp_tool, send_message, action, loop, code_block, agent_loop, confidence_router, guardrail, function_call, auth_gate, schedule_message, send_proactive, delegate_to_bot, structured_output, knowledge_retrieval, wait_delay, sub_workflow, llm_router, memory_store, vision_analyze, document_parse, image_generate, data_transform, parallel.

Workflows are built from 27 node types across 4 categories. Each performs a specific function and can pass data to downstream nodes via shared variables.

---

## trigger

The entry point for every workflow. At least one trigger node is required.

| Field | Description |
|-------|-------------|
| `triggerType` | Currently only `message_received` is supported |

When a message arrives, the trigger node sets the following built-in variables and starts the workflow:

| Variable | Value |
|----------|-------|
| `message` | The user's message text |
| `user_id` | Platform-specific user identifier |
| `user_name` | User's display name (if available) |
| `channel_id` | Platform-specific channel identifier |
| `platform` | `telegram`, `discord`, `whatsapp`, `slack`, or `custom` |

```json
{
  "id": "trigger_1",
  "type": "trigger",
  "data": {
    "triggerType": "message_received",
    "label": "On Message"
  }
}
```

---

## llm_call

Sends a prompt to the bot's configured LLM and stores the response.

| Field | Required | Description |
|-------|----------|-------------|
| `prompt` | Yes | The prompt to send. Supports `{{variable}}` interpolation. |
| `outputVariable` | No | Variable name to store the LLM response. |
| `label` | No | Display label for the node. |

The response object contains `text`, `tokensInput`, and `tokensOutput`. Access the response text downstream with `{{outputVariable.text}}`.

```json
{
  "id": "classify_1",
  "type": "llm_call",
  "data": {
    "prompt": "Classify this customer message into exactly one category: billing, refund, other.\n\nMessage: {{message}}\n\nRespond with only the category name.",
    "outputVariable": "intent",
    "label": "Classify Intent"
  }
}
```

LLM call nodes use the bot's configured provider and model. Temperature is set to 0.3 for more deterministic outputs.

**Use case:** Intent classification, data extraction, response generation, summarization, translation.

---

## logic

Conditional branching. Routes execution to different downstream nodes based on conditions.

### if_else

Evaluates a condition and follows the `true` or `false` edge.

```json
{
  "id": "check_1",
  "type": "logic",
  "data": {
    "logicType": "if_else",
    "condition": "{{intent.text}} == billing",
    "label": "Is Billing?"
  }
}
```

Connect edges with `sourceHandle`:
```json
{ "source": "check_1", "target": "billing_node", "sourceHandle": "true" },
{ "source": "check_1", "target": "other_node", "sourceHandle": "false" }
```

### switch

Matches a variable against multiple cases.

```json
{
  "id": "switch_1",
  "type": "logic",
  "data": {
    "logicType": "switch",
    "variable": "intent",
    "cases": ["billing", "support", "general"],
    "label": "Route by Intent"
  }
}
```

Connect edges with `sourceHandle: "case_billing"`, `sourceHandle: "case_support"`, etc. Unmatched values follow `sourceHandle: "case_default"`.

### wait

Pauses execution until the next user message. Currently a placeholder for future multi-turn workflow support.

See [Variables & Conditions](/bots/workflows/variables) for the full condition syntax reference.

---

## mcp_tool

Calls any MCP tool from the bot's workspace.

| Field | Required | Description |
|-------|----------|-------------|
| `toolName` | Yes | Fully qualified tool name (e.g., `stripe__get_payment`) |
| `arguments` | No | JSON string with `{{variable}}` interpolation. Default: `{}` |
| `outputVariable` | No | Variable name to store the tool result. |

The result is truncated to 3000 characters and stored in `outputVariable`.

```json
{
  "id": "lookup_1",
  "type": "mcp_tool",
  "data": {
    "toolName": "orders__get_order",
    "arguments": "{\"order_id\": \"{{order_id}}\"}",
    "outputVariable": "order_data",
    "label": "Look Up Order"
  }
}
```

If the tool name is not found in the workspace, or if the arguments JSON is malformed, the node returns an error object in the output variable rather than throwing an exception. Always check the result before using it in downstream nodes.

**Use case:** Query databases, process payments, create tickets, search knowledge bases, call any external API exposed through an MCP server.

---

## send_message

Sends a message to the user.

| Field | Required | Description |
|-------|----------|-------------|
| `message` | No | Message text with `{{variable}}` interpolation. If omitted, sends the last AI response. |
| `useTemplate` | No | Whether to treat the message as a template (default: true). |

```json
{
  "id": "respond_1",
  "type": "send_message",
  "data": {
    "message": "Your order #{{order_id}} is currently {{order_data.status}}. Shipped on {{order_data.shipped_date}}.",
    "label": "Send Order Status"
  }
}
```

If no `message` is provided, the node sends the value of `ctx.variables.ai_response` (the most recent LLM call output).

A workflow can have multiple `send_message` nodes. All messages are collected and sent in order when the workflow completes.

---

## action

Performs side effects that don't produce user-facing messages.

### set_variable

Sets a variable in the execution context.

```json
{
  "type": "action",
  "data": {
    "actionType": "set_variable",
    "variable": "greeting",
    "value": "Hello, {{user_name}}!",
    "label": "Set Greeting"
  }
}
```

### end_conversation

Stops the workflow and closes the conversation.

```json
{
  "type": "action",
  "data": {
    "actionType": "end_conversation",
    "label": "End"
  }
}
```

### human_handoff

Pauses the workflow and notifies a human reviewer. The reviewer receives the conversation context, the escalation reason, and approve/reject controls. See [Human Handoffs](/bots/handoffs) for the complete guide.

```json
{
  "type": "action",
  "data": {
    "actionType": "human_handoff",
    "reason": "Refund request over $500 for order {{order.id}}",
    "label": "Escalate to Finance"
  }
}
```

The workflow pauses at this point. When the reviewer approves, execution resumes on the `approved` path. When rejected, execution follows the `rejected` path.

### create_ticket

Creates a support ticket notification and continues.

```json
{
  "type": "action",
  "data": {
    "actionType": "create_ticket",
    "subject": "Issue with order {{order_id}}",
    "label": "Create Ticket"
  }
}
```

The `create_ticket` action sends a notification message. For creating actual tickets in external systems (Zendesk, Jira, etc.), use an `mcp_tool` node connected to the appropriate MCP server.

---

## loop

Iterates over arrays or a fixed count.

### for_each

Iterates over an array variable.

| Field | Description |
|-------|-------------|
| `loopType` | `for_each` |
| `arrayVariable` | Name of the variable containing the array |
| `itemVariable` | Variable name for the current item (default: `item`) |
| `maxIterations` | Maximum iterations (capped at 50) |

```json
{
  "type": "loop",
  "data": {
    "loopType": "for_each",
    "arrayVariable": "order_items",
    "itemVariable": "current_item",
    "maxIterations": 10,
    "label": "Process Each Item"
  }
}
```

Connect edges with `sourceHandle: "loop_body"` (for the loop body) and `sourceHandle: "loop_done"` (for after the loop completes).

**Use case:** Process each item in a shopping cart, send a message for each search result, iterate over a list of tickets.

### count

Iterates a fixed number of times.

| Field | Description |
|-------|-------------|
| `loopType` | `count` |
| `iterations` | Number of iterations |
| `itemVariable` | Variable name for the current index (default: `index`) |

### while

Iterates while a condition is true.

| Field | Description |
|-------|-------------|
| `loopType` | `while` |
| `condition` | Condition string (same syntax as logic nodes) |

---

## code_block

Runs JavaScript code in a sandboxed context.

| Field | Description |
|-------|-------------|
| `code` | JavaScript code to execute |

**Available in the sandbox:**

| Object | Available |
|--------|-----------|
| `ctx.variables` | Read/write workflow variables |
| `ctx.message` | User's message text |
| `ctx.user_id` | User identifier |
| `JSON`, `Math`, `Date`, `String`, `Number`, `Array`, `Object` | Yes |
| `parseInt`, `parseFloat`, `isNaN` | Yes |
| `encodeURIComponent`, `decodeURIComponent` | Yes |
| `fetch`, `require`, `import` | **No** (blocked) |

```json
{
  "type": "code_block",
  "data": {
    "code": "const items = ctx.variables.order_items || []; ctx.variables.total = items.reduce((sum, item) => sum + item.price, 0); ctx.variables.item_count = items.length;",
    "label": "Calculate Total"
  }
}
```

**Use case:** Data transformation, calculations, string manipulation, filtering arrays, formatting output, business logic that does not require external calls.

Code blocks run in a sandboxed environment. They cannot make network requests or access the filesystem. Do not expose code block configuration to untrusted users.

---

## auth_gate

Runs a multi-turn identity verification flow inside the conversation. The bot prompts the user for their email or phone number, sends a one-time code via one of the supported providers, and verifies the code before allowing the workflow to continue.

| Field | Description |
|-------|-------------|
| `provider` | `resend`, `ses`, `twilio`, `msg91`, or `custom_http` |
| `contact_type` | `email` or `phone` |
| `outputVariable` | Variable to store the verified identity |

**State machine:**

```mermaid
stateDiagram-v2
    [*] --> awaiting_contact: Bot asks for email/phone
    awaiting_contact --> awaiting_otp: User provides contact, OTP sent
    awaiting_otp --> verified: User provides correct code
    awaiting_otp --> failed: Max attempts exceeded
    verified --> [*]
    failed --> [*]
```

```json
{
  "type": "auth_gate",
  "data": {
    "provider": "resend",
    "contact_type": "email",
    "outputVariable": "verified_identity",
    "label": "Verify Customer Email"
  }
}
```

Connect edges with `sourceHandle: "verified"` and `sourceHandle: "failed"`:
```json
{ "source": "auth_1", "target": "proceed_node", "sourceHandle": "verified" },
{ "source": "auth_1", "target": "deny_node", "sourceHandle": "failed" }
```

After verification, `{{verified_identity}}` contains the verified email or phone number.

**Use case:** Verify identity before accessing account data, processing refunds, or changing settings. Required for any workflow that handles sensitive personal information.

Supported providers:
- **Resend** — Email OTP via Resend API
- **SES** — Email OTP via Amazon SES
- **Twilio** — Phone OTP via Twilio SMS
- **MSG91** — Phone OTP via MSG91
- **Custom HTTP** — Your own OTP endpoint

---

## schedule_message

Sends a message at a future time.

| Field | Description |
|-------|-------------|
| `message` | Message text with `{{variable}}` interpolation |
| `delay_minutes` | Minutes from now to send the message |
| `scheduled_time` | ISO 8601 timestamp for exact scheduling |

```json
{
  "type": "schedule_message",
  "data": {
    "message": "Reminder: Your appointment is in 1 hour. Reply to confirm or cancel.",
    "delay_minutes": 1380,
    "label": "24h Reminder"
  }
}
```

**Use case:** Appointment reminders, follow-up messages after support interactions, daily digests, shift notifications.

---

## send_proactive

Sends a message to a different channel or user than the one who triggered the workflow.

| Field | Description |
|-------|-------------|
| `message` | Message text with `{{variable}}` interpolation |
| `target_channel_id` | The channel/user to send to |
| `target_platform` | The platform to send on (can differ from trigger platform) |

```json
{
  "type": "send_proactive",
  "data": {
    "message": "Alert: Customer {{user_name}} reported a critical issue. Details: {{message}}",
    "target_channel_id": "ops-alerts-channel",
    "target_platform": "slack",
    "label": "Notify Ops Channel"
  }
}
```

**Use case:** Notify a team channel when a customer reports an issue, alert on-call engineers, send summaries to management channels, cross-platform notifications.

---

## delegate_to_bot

Calls another bot, passing the current message and context. The target bot processes the message using its own workspace and tools, and the response is returned to the current workflow.

| Field | Description |
|-------|-------------|
| `target_bot_id` | The bot ID to delegate to |
| `outputVariable` | Variable to store the delegated bot's response |
| `context` | Optional JSON context to pass to the target bot |

```json
{
  "type": "delegate_to_bot",
  "data": {
    "target_bot_id": "bt_billing_specialist",
    "outputVariable": "specialist_response",
    "context": "{\"customer_id\": \"{{verified_identity}}\"}",
    "label": "Route to Billing Bot"
  }
}
```

Maximum **3 delegation hops** to prevent infinite loops. See [Bot Teams & Delegation](/bots/bot-teams) for architecture patterns and examples.

**Use case:** Reception bot routes to specialist bots (billing, technical support, HR). Each specialist has its own workspace with domain-specific tools.

---

## error_handler

Catches errors from upstream nodes and provides a fallback path.

```json
{
  "type": "error_handler",
  "data": {
    "label": "Handle Errors",
    "fallback_message": "Something went wrong while processing your request. A support agent will follow up shortly."
  }
}
```

If a node throws an error, the error is recorded in the execution log and the workflow continues following the error_handler's edges.

**Use case:** Send a graceful fallback message instead of leaving the user with no response. Log the error for debugging.

---

## parallel

A structural node for fan-out execution. When the graph walker encounters a node with multiple outgoing edges, it follows all of them in breadth-first order. The parallel node is a visual marker for this behavior.

```json
{
  "type": "parallel",
  "data": { "label": "Fan Out" }
}
```

Connect multiple outgoing edges from the parallel node to execute multiple paths simultaneously:

```json
{ "source": "parallel_1", "target": "lookup_order" },
{ "source": "parallel_1", "target": "lookup_account" },
{ "source": "parallel_1", "target": "check_inventory" }
```

**Use case:** Fetch data from multiple MCP tools at the same time, send notifications to multiple channels, run independent processing steps in parallel.

---

## Node Type Summary

### Core Nodes

| Type | Inputs | Outputs | Side Effects |
|------|--------|---------|-------------|
| `trigger` | Platform message | Built-in variables | None |
| `llm_call` | Prompt template | `outputVariable` with text, tokens | LLM API call |
| `logic` | Condition/variable | Edge routing (if_else or switch) | None |
| `mcp_tool` | Tool name, arguments | `outputVariable` with tool result | MCP tool call |
| `send_message` | Message template | None | Sends message to user |
| `action` | Action-specific fields | Variable (set_variable) | Depends on action type |
| `loop` | Array/count/condition | `itemVariable` per iteration | None |
| `code_block` | JavaScript code | Via `ctx.variables` or return value | None |

### AI Nodes

| Type | Inputs | Outputs | Side Effects |
|------|--------|---------|-------------|
| `agent_loop` | System prompt, tools | `outputVariable` with final response | Multi-turn LLM + tool calls |
| `confidence_router` | Input variable, thresholds | Edge routing by confidence score | LLM call for scoring |
| `guardrail` | Input variable, guardrail type | Pass/fail + `outputVariable` | LLM-based content check |
| `llm_router` | Models list, prompt | `outputVariable` with response | Tries models in sequence |
| `structured_output` | JSON schema, prompt | `outputVariable` with typed JSON | LLM call |
| `vision_analyze` | Image URL, prompt | `outputVariable` with analysis | Vision LLM call |
| `image_generate` | Prompt, model | `outputVariable` with image URL | Image generation API |
| `document_parse` | Document URL | `outputVariable` with extracted text | Document processing |
| `knowledge_retrieval` | Query, vectorize index | `outputVariable` with RAG results | Vectorize search |
| `memory_store` | Key, value | None | Writes to persistent KV |

### Integration Nodes

| Type | Inputs | Outputs | Side Effects |
|------|--------|---------|-------------|
| `function_call` | Function ID, arguments | `outputVariable` with result | Calls deployed function via dispatch |
| `auth_gate` | Provider, contact type | `outputVariable` with identity | Sends OTP/magic link |
| `delegate_to_bot` | Target bot ID | `outputVariable` with response | Calls another bot |
| `sub_workflow` | Workflow ID, input vars | `outputVariable` with result | Runs nested workflow |
| `data_transform` | JavaScript expression | `outputVariable` with result | None |

### Flow Control Nodes

| Type | Inputs | Outputs | Side Effects |
|------|--------|---------|-------------|
| `parallel` | None | None | Fan-out marker |
| `wait_delay` | Duration (seconds) | None | Pauses execution |
| `schedule_message` | Message, delay | None | Queues future message |
| `send_proactive` | Message, target | None | Sends to different channel |
| `error_handler` | None | None | Catches upstream errors |
