Automation nodes and triggers
Every block you can drop on the automation canvas, what it does, the fields it takes and where it can continue to. This page is generated from the same table the visual builder and the MCP server read, so it cannot drift from the product.
How a flow runs
A flow starts at a trigger and walks from node to node along its outputs. A node that waits for a reply pauses the flow until the contact answers; a terminal node ends it. If a node needs something the network does not support, it is skipped at runtime rather than breaking the flow.
Triggers
What starts a flow.
| Trigger | What starts it |
|---|---|
keyword | Fires when an incoming direct message matches one of the keywords. |
comment | Fires on a comment on your posts. Instagram and Facebook only — they are the ones that allow answering a comment by direct message. |
story | Fires when someone replies or reacts to a story, which arrives as a direct message. |
welcome | Fires once, on the contact's first ever conversation. |
manual | Never fires on its own. For flows reached from another one with a goto_flow node (keep it active). |
Nodes
start
Entry point of the flow. Every graph has exactly one.
Outputs: next (Node to continue to.)
message
Send a plain text message.
| Field | What it does | ||
|---|---|---|---|
text · text · required | Message body. Every text a flow sends (messages, captions, button and question texts, public comment replies, handoff notices) accepts variables: {{first_name}}, {{last_name}}, {{name}}, {{username}} (of whoever wrote or commented), {{email}}, any custom field by its slug ({{phone}}), or a question node's variable. A fallback goes after " | ": {{first_name | there}}. A variable with no value and no fallback is left out. |
Outputs: next (Node to continue to.)
image
Send an attachment (image, video, audio or file) with an optional caption.
dmAttachments capability on the target network.| Field | What it does |
|---|---|
url · string · required | Public https URL of the file. |
caption · text | Text sent alongside the attachment. |
mediaType · select · one of: image, video, audio, file · default image | Kind of attachment. |
mediaButtonTitle · string | Label of the link button used to deliver a document (mediaType "file") on Instagram and Facebook, where an attached document opens behind a Meta interstitial. Defaults to the file extension. |
Outputs: next (Node to continue to.)
button_message
Message with up to 3 buttons. Each button either opens a URL or branches the flow. With media it renders as a rich card, without it as a bubble with buttons. This is the button node to use.
dmButtons capability on the target network.| Field | What it does | |
|---|---|---|
text · text · required | Message body. | |
buttons · messageButton[] · required · max 3 | Each: {title, kind:"url" | "reply", url (when url), payload (when reply), next (branch of THIS button, reply only)}. |
mediaUrl · string | Public https URL of an attachment sent with the message. | |
mediaType · select · one of: image, video, audio, file · default image | Kind of attachment. Networks differ: check dmAttachments in get_network_rules. | |
mediaButtonTitle · string | Label of the link button used to deliver a document (mediaType "file"). On Instagram and Facebook a message with buttons only carries an image, so documents travel as a link button to their public URL. Defaults to the file extension. |
Outputs: next (Continuation / fallback: no reply buttons, or an unmatched tap.) · buttons[].next (Branch taken when that specific reply button is tapped.)
buttons
Legacy node: text plus 1-3 link buttons. Does not branch.
dmButtons capability on the target network. Superseded by button_message; still runs, but prefer the newer one.| Field | What it does |
|---|---|
text · text · required | Message body. |
buttons · linkButton[] · required · max 3 | Each: {title, url}. |
mediaUrl · string | Public https URL of an attachment sent with the message. |
mediaType · select · one of: image, video, audio, file · default image | Kind of attachment. Networks differ: check dmAttachments in get_network_rules. |
mediaButtonTitle · string | Label of the link button used to deliver a document (mediaType "file"). On Instagram and Facebook a message with buttons only carries an image, so documents travel as a link button to their public URL. Defaults to the file extension. |
Outputs: next (Node to continue to.)
reply_buttons
Legacy node: text plus up to 3 reply buttons; waits for a tap and stores it.
dmButtons capability on the target network. Superseded by button_message; still runs, but prefer the newer one.| Field | What it does |
|---|---|
text · text · required | Message body. |
replies · quickReply[] · required · max 3 | Each: {title, payload}. Without payload the title is used. |
variable · string | Store the answer under this name. |
mediaUrl · string | Public https URL of an attachment sent with the message. |
mediaType · select · one of: image, video, audio, file · default image | Kind of attachment. Networks differ: check dmAttachments in get_network_rules. |
mediaButtonTitle · string | Label of the link button used to deliver a document (mediaType "file"). On Instagram and Facebook a message with buttons only carries an image, so documents travel as a link button to their public URL. Defaults to the file extension. |
Outputs: next (Node to continue to.)
carousel
Horizontal carousel of cards. Instagram and Facebook only.
carousel capability on the target network.| Field | What it does |
|---|---|
elements · card[] · required · max 10 | Each: {title, subtitle, imageUrl, buttons:[{title,url}] (max 3 link buttons per card)}. |
Outputs: next (Node to continue to.)
catalog
Show the workspace catalogue as a carousel. Items come from the catalogue itself, not from the node — manage them with manage_catalog.
carousel capability on the target network.| Field | What it does |
|---|---|
category · string | Free-text SEARCH, not an exact filter: it matches as a substring against item name, description or category. |
Outputs: next (Node to continue to.)
booking
Book an appointment with buttons, without AI: service → (person, if the business lets people choose) → day → time → the required details, then the appointment is created (Google Calendar event included) and the flow continues. Services, hours and what to ask are configured in Appointments in the app (get_booking_settings), not here. Needs appointments in the plan (Blaze and up) and turned on.
dmButtons capability on the target network.| Field | What it does |
|---|---|
serviceIds · string[] | Restrict to these service ids. Empty = every bookable service. |
Outputs: next (Taken once the appointment is booked.) · noSlotsNext (Taken when there is nothing to offer (no free slots, appointments off or not in the plan) or the person keeps writing instead of tapping. Falls back to next.)
delay
Pause the flow, then resume: for a number of minutes, or until a date (fixed, or taken from a field or answer). Optionally only within some days and hours.
| Field | What it does |
|---|---|
minutes · number · required | Minutes to wait (mode "duration"; ignored with mode "until", pass 0). Decimals allowed: 0.5 = 30 seconds, 99 = 1.65 hours. |
mode · select · one of: duration, until · default duration | "until" waits until a date instead: untilDate (YYYY-MM-DD) or the date in untilField (a contact field slug or a question variable), at untilTime (HH:MM, default 09:00), plus offsetMinutes (negative = before, e.g. -1440 = one day before). If that moment has passed or there is no valid date, the flow continues at once. |
untilDate · string | Fixed date, YYYY-MM-DD. |
untilField · string | Field slug or question variable holding the date. |
untilTime · string | Time of day, HH:MM, in the organization time zone. |
offsetMinutes · number | Shift from that date; negative = before. |
window · window | {"days":[1-7, 1=Monday],"from":"09:00","to":"18:00"} in the organization time zone. If the wait ends outside it, the flow continues at the start of the next window. from > to crosses midnight. |
Outputs: next (Node to continue to.)
delay_random
Pause a random number of minutes between two bounds, so replies do not look automated.
| Field | What it does |
|---|---|
minMinutes · number · required | Lower bound in minutes, inclusive. Decimals allowed (0.5 = 30 seconds). |
maxMinutes · number · required | Upper bound in minutes, inclusive. Decimals allowed (99 = 1.65 hours). |
Outputs: next (Node to continue to.)
question
Ask something and wait for the answer, optionally validating and storing it.
| Field | What it does |
|---|---|
text · text · required | The question. |
variable · string | Store the answer under this name. |
validate · select · one of: email, phone, number, url, date | Require a valid answer of that kind. On a bad answer it resends retryText and asks again, then gives up and keeps whatever came so the user is never trapped. The answer is stored normalized: number with a dot and no thousands separator ("1.234,5" → 1234.5), url with https://, date as YYYY-MM-DD (typed day first: 15/3/26). That is what lets a later condition compare it with gt/lt. |
retryText · text | Message sent when validation fails. |
timeoutMinutes · number | Minutes to wait for an answer before taking the timeout branch. Without it, someone who never answers stays waiting forever and the flow ends there in silence — and not answering is the normal case, so this branch is usually the difference between capturing a contact and losing it. |
timeoutNext · nodeRef | Node to continue to when the wait runs out. Needed for timeoutMinutes to do anything. Typically a message that asks again. If the person answers before the time is up, this branch never fires. |
Outputs: next (Node to continue to.) · timeoutNext (Taken when nobody answered within timeoutMinutes.)
condition
Branch on the user's last answer, the contact's tags and custom fields, the network, or the day and time. Takes the first branch that matches.
| Field | What it does | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
branches · branch[] · required | Each: {keywords:[…], rules?:[…], match?:"all" | "any", next}. keywords checks the last answer (case and accents ignored, substrings hit, a leading "-" excludes). rules adds checks, combined with the keywords by match (default "all"): {"kind":"tag","op":"has" | "has_not","tag":"vip"} · {"kind":"field","slug":"email","op":"is" | "is_not" | "contains" | "not_contains" | "is_set" | "is_empty" | "gt" | "lt","value":"…"} (email and name also read the native contact data; gt/lt compare numbers or dates) · {"kind":"platform","op":"is" | "is_not","platform":"instagram"} · {"kind":"time","days":[1-7, 1=Monday],"from":"09:00","to":"18:00"} (organization time zone; from > to crosses midnight) · {"kind":"text","op":"contains" | "not_contains" | "is" | "starts_with","value":"yes, ok"} (comma-separated list: contains = any of them, "-word" excludes; not_contains = none of them; same as keywords, which is the older way to write text·contains) · {"kind":"seen","op":"yes" | "no","scope"?:"post"} (had this person already been through THIS flow before this time — on this same post with scope "post"; counted like oncePerContact on the trigger, so you can send something else instead of staying silent). Without a contact, tags and fields count as empty. With no answer (after a delay) keywords never match but rules are still checked. |
Outputs: branches[].next (Branch whose keywords matched.) · fallbackNext (Taken when nothing matched.)
follower
Branch on whether the person follows the account. Instagram only exposes this; anywhere else it takes the yes branch.
Outputs: yesNext (Follows, or unknown. Unknown counts as yes so nobody gets stuck.) · noNext (Only when Instagram explicitly confirms they do not follow.)
ab_split
Split runs between two branches at random and count each one, for real A/B stats.
| Field | What it does |
|---|---|
percentage · number · default 50 | Percentage going to branch A, 0-100. The rest go to B. |
Outputs: aNext (Branch A.) · bNext (Branch B.)
randomize
Send one of several text variants at random, to avoid sounding repetitive.
| Field | What it does |
|---|---|
texts · string[] · required | Variants. Empty ones are ignored. |
Outputs: next (Node to continue to.)
tag
Add tags to the contact, or remove them. Silent: nothing is sent to the user.
| Field | What it does |
|---|---|
tags · string[] · required | Tags to add or remove. Adding merges with the existing ones, it does not replace them. Removing ignores case and accents. |
mode · select · one of: add, remove · default add | "add" adds the tags; "remove" takes them off the contact if present. |
Outputs: next (Node to continue to.)
field
Set a custom field on the contact, or clear it. Silent.
| Field | What it does |
|---|---|
slug · string · required | Field slug, from list_custom_fields. |
value · string | Value to store. Accepts variables, e.g. {{size}} from an earlier question. |
valueFromAnswer · boolean · default false | Store the user's LAST answer instead of value: the usual question → field pattern. |
mode · select · one of: set, clear · default set | "clear" empties the field on the contact (the field definition stays); value is ignored. Clearing email also empties the native email. |
Outputs: next (Node to continue to.)
subscribe
Enrol the contact in a drip sequence, or take them out of it. Silent.
| Field | What it does |
|---|---|
sequenceId · string · required | Sequence id, from list_sequences. |
mode · select · one of: subscribe, unsubscribe · default subscribe | "unsubscribe" removes the contact from the sequence: no further steps are sent. |
Outputs: next (Node to continue to.)
handoff
Hand the conversation to a human and STOP the bot for it. Terminal: no automation runs on that conversation again until someone clears it.
| Field | What it does |
|---|---|
message · text | Optional notice sent to the user. |
assignMode · select · one of: auto, user, team · default auto | auto lets Ignix route it; user or team forces an owner. |
assignUserId · string | Required when assignMode is user. |
assignTeamId · string | Required when assignMode is team. |
ai
Answer ONCE with the AI, using the workspace knowledge base and the contact's memory, then carry on with the flow.
| Field | What it does |
|---|---|
instructions · text | Extra steer for THIS point of the flow, e.g. "focus on booking a visit". |
fallbackText · text | Sent when the AI is unavailable or over its fair-use ceiling. |
retrieveLimit · number | How many knowledge passages to retrieve. |
escalateOnUnknown · boolean · default false | Take escalateNext when the AI could not answer from knowledge. |
Outputs: next (Node to continue to.) · fallbackNext (Taken when the AI was unavailable.) · escalateNext (Taken when the user asks for a human, or on unknown if enabled. Wire it to a handoff node.)
ai_agent
Hand the conversation to the AI and let it answer EVERY incoming message, turn after turn, until it escalates. Use this instead of ai for open-ended conversations.
| Field | What it does |
|---|---|
instructions · text | Standing steer for the conversation. |
fallbackText · text | Sent when the AI is unavailable. |
retrieveLimit · number | How many knowledge passages to retrieve. |
escalateOnUnknown · boolean · default false | Escalate when the AI could not answer from knowledge. |
Outputs: escalateNext (Taken when the user asks for a human, or on unknown if enabled. Without escalation the node keeps waiting for the next message.)
comment_reply
Post a PUBLIC reply to the comment that triggered the flow. Only does anything on comment triggers.
commentReply capability on the target network.| Field | What it does |
|---|---|
text · text · required | The public reply. |
texts · string[] | Variants; one is picked at random per comment. Falls back to text. |
Outputs: next (Node to continue to.)
random_split
Send each person down one of 2–6 paths at random, by weight. With sticky the same person always gets the same path, also on later runs. No stats: use ab_split to measure.
| Field | What it does |
|---|---|
paths · string[] · required | [{"weight":50,"next":"n1"},{"weight":50,"next":"n2"}]. Weights are relative (they do not need to add up to 100). |
sticky · boolean · default false | Same person → same path every time (deterministic by node and contact). |
Outputs: paths[].next (The path drawn.)
team
A team action, nothing is sent to the user: notify the team, change the conversation status in the inbox, or pause the bot for this contact.
| Field | What it does |
|---|---|
action · select · required · one of: notify, stage, pause | "notify" emails/pushes message (accepts variables) to the conversation owner (or OWNER/ADMIN), or to notifyUserId / notifyTeamId with notifyMode "user"/"team". "stage" sets the inbox status to stage. "pause" silences the bot for this contact for pauseHours (none = until someone turns it back on); the rest of the current pass still runs. |
message · text | For notify. |
notifyMode · select · one of: auto, user, team · default auto | For notify. |
notifyUserId · string | For notify with notifyMode "user", from list_team. |
notifyTeamId · string | For notify with notifyMode "team". |
stage · select · one of: open, pending, resolved | For stage. |
pauseHours · number | For pause. Empty = until turned back on. |
Outputs: next (Node to continue to.)
http
Call an external service (Zapier, Make, a webhook, an API) and continue. Silent for the user. Only https, never internal addresses; 8 s timeout, no redirects.
| Field | What it does |
|---|---|
method · select · required · one of: GET, POST, PUT, PATCH, DELETE | HTTP method. |
url · string · required | https URL. Accepts variables ({{email}}), URL-encoded. |
headers · string[] | [{"key":"Authorization","value":"Bearer …"}]. Values accept variables. |
body · text | Request body for POST/PUT/PATCH, usually JSON. Variables are escaped for JSON: {"email":"{{email}}","name":"{{first_name}}"}. Content-Type defaults to application/json. |
save · string[] | [{"path":"data.order.status","variable":"order_status","field":"order_status"}]: copies a value from the JSON response into a run variable (usable later as {{order_status}}) and/or a contact field. |
Outputs: next (Got a 2xx response.) · errorNext (Anything else (error status, timeout, blocked URL). Without it, the flow goes on through next.)
goto_flow
Jump to ANOTHER automation and continue there, in the same conversation. Terminal: what happens next is up to the target. Only ACTIVE automations run (an inactive target ends the flow); its trigger, accounts and once-per-person setting are ignored — automations with a "manual" trigger exist to be reached this way.
| Field | What it does |
|---|---|
flowId · string · required | Id of the target automation, from list_automations (same workspace). |
end
End the flow.