Skip to main content

Flows

Orbit Flows is a visual workflow builder that lets you design complex communication automations without writing code. Connect triggers, conditions, channels, and AI agents into multi-step workflows using a drag-and-drop interface.

What Are Flows?

Flows are directed graphs of nodes that execute in sequence or parallel based on conditions. Each node performs an action — sending a message, calling an API, running an AI agent, or evaluating a condition.

Core Concepts

Use Cases

  • Welcome series — onboard new users with a multi-step SMS + email sequence
  • Appointment reminders — send reminders 24h and 1h before, handle confirmations
  • Lead nurturing — qualify leads with AI, route hot leads to sales
  • Support escalation — try AI agent first, escalate to human if unresolved
  • Order updates — notify customers at each stage of fulfillment
  • Survey collection — send surveys post-interaction, analyze responses with AI

Trigger Catalog

Every flow starts from exactly one trigger. The Flow Builder exposes nine trigger types; each maps to the API trigger_type shown in the right column. Two more trigger_type values exist for programmatic starts: manual (the Start Flow API or the dashboard’s Run action) and workflow (started by another flow).

Trigger payloads

When a flow starts, everything the trigger supplies becomes the initial execution context. Keys mentioned below are the ones the runtime wires in itself; inbound routings may add more (for example a keyword hit appends keyword). Variables anywhere in the flow resolve against this context plus contact fields, using {{...}} placeholders — flat keys like {{first_name}}, not {{contact.first_name}}. A placeholder whose key is absent renders as an empty string.

Validate a Flow

GET /flows/:id/validate checks a saved flow’s definition for structural correctness without running it. Run it before activation (and before forecasting) so obvious graph problems surface at build time:
The call returns valid and an errors list. Checks include:
  • The definition has at least one node, and node id + type are present
  • A trigger node exists
  • No node is disconnected from every edge
  • abTest / experiment nodes define at least two variants, and any explicit weights sum to 100
  • No graph cycle that passes through no delay/wait node (cyclic drip loops that include a wait node are allowed)
When valid is true, errors is an empty array. The same checks also run automatically on POST /flows and PUT /flows/:id.

Node Taxonomy

Action nodes

AI nodes

Logic nodes

Data nodes

An unrecognized node type is skipped at runtime: the executor logs it as unhandled and takes the node’s default outgoing edge, so a node does nothing unless its type matches one the executor implements.

Visual builder vs API-only

Everything in the Trigger Catalog above and the Action / AI / Logic core is in the builder’s palette; the builder page lists them with their config panels in Flow Builder. Two categories are API-only today — define them in a flow’s definition.nodes over the API and the runtime executes them identically:
  • Messaging extrassendViber, sendLine, sendTelegram, sendInstagram, sendMessenger, sendPush, sendFax
  • Data + optimization nodesFunctions, Update Record, Send Survey, Send-Time Optimization, A/B Test

Edge Semantics

Each edge connects a source node to a target node and may carry a sourceHandle that says which exit route it represents. The executor picks the next node by handle, not by drawing order: When no handle matches, the executor falls back to the node’s unlabeled default edge. When that default is absent too, the run ends there. Every handle-and-fallback decision lands in the step trace the Executions API returns, so a mis-routed branch is inspectable per node.

Where Flows Fire

A flow reaches live traffic through one of these entry points:
  • Inbound routing rules — match an incoming message by keyword or sender, then start the flow. These rules are configured under the inbound-routing surface (sms_inbound_routes family); the transport split between inbound and outbound paths is covered in Transport architecture.
  • Webhooks — an external system POSTs the payload to the flow’s webhook URL.
  • Schedule — a cron schedule you attach as trigger_type: "schedule" with cron_expr and an optional cron_tz.
  • Campaigns, contact/audience events — platform events and campaign sends start event-triggered flows.
  • API / dashboardmanual flows start from the Start Flow call or the dashboard’s Run action.

Create a Flow via API

The top-level body requires name, trigger_type (one of manual, webhook, schedule, event, workflow), and definition — an object holding the flow graph (nodes, edges, and any trigger detail). For trigger_type: "schedule" also supply cron_expr (5-field cron) and an optional cron_tz (IANA timezone, defaults to UTC).

Node shape

Each node has an id, a type, and a data object that carries the node’s settings — the runtime reads node settings from data, not from the node’s top level. Edges connect nodes with source and target (the node ids), plus the optional sourceHandle routing described in Edge Semantics.

Forecast a Flow Before Activation

POST /flows/:id/simulate dry-runs a flow against an audience before you activate it. It projects the per-step drop-off funnel, the channel mix, the predicted send cost, and a conversion estimate — without enrolling a contact, emitting a send, or wiring a provider. The projection is pure; it is a POST only because it carries a body. This is the same call the builder’s Test/Forecast toolbar action uses, so a what-if in the dashboard and an API forecast return the same shape.
Body fields (all optional except that one of the audience inputs is required): The forecast math walks the flow’s nodes in traversal order and applies a retention factor at each step: a sendRetentionRate after send/call nodes, a delayRetentionRate across delay/sto_wait nodes, and a branchRetentionRate as the share continuing the primary path at condition/split/abTest nodes. Defaults are deliberately conservative (0.99 / 0.9 / 0.5); per-request assumptions override them. When use_historical applies, a node’s real step-conversion rate from the analytics funnel (below) overrides the assumption for that node. Response (excerpt):
In each step, step_retention_rate is the factor applied when leaving the node, from_history marks whether it came from real history or an assumption, and sends is the projected send/call count at the node. Send costs are priced through the tenant’s own configured rates for the given default_country, per channel (voice calls are multiplied by avg_call_minutes before pricing). A missing rate yields a zero cost, never an error. The forecast is a read-only estimate — no ledger row is written.

Per-Flow Conversion Analytics

GET /flows/:id/analytics returns the post-activation read of a flow: how many executions entered and completed, a per-node drop-off funnel in traversal order, and how many contacts converted against the flow’s goal.
Query parameters (all optional): The conversion goal is normally configured on the flow itself as definition.conversionGoal (set it via PUT /flows/:id), either { "type": "event", "event": "order.completed", "windowDays": 7 } or { "type": "trait", "trait": "plan", "value": "pro", "windowDays": 7 }. The query params above override it for an ad-hoc funnel. A contact converts when it fires the goal event (or has the trait set) within the window after its execution reaches a terminal state.
Per funnel step: reached is the number of executions that traversed the node, dropped_off the loss versus the previous step, step_conversion_rate is versus the previous step (null at the entry step), and overall_conversion_rate is versus the funnel entry. All counting is a read-side aggregation over the recorded executions — the executions log itself is untouched.

A/B Test Results

GET /flows/:id/ab-results rolls up every abTest (or experiment) node in the flow: per-variant assigned, completed, and converted counts with rates, plus the leading variant. It accepts the same since/until/window_days/goal-override parameters as /analytics.
The winner is the variant with the highest conversion rate when a goal is configured, or the highest completion rate when it is not. A tie at the top — as well as having no assignments yet — yields winner_variant_id: null, so the dashboard never declares a false winner.

How variant assignment works

Each abTest node pins its variant deterministically per contact: a hash of the contact’s key and the node id picks the bucket, so a contact that re-enters the flow re-derives the same variant — one sticky subject per variant, no stored assignment state. Weights come from each variant’s weight_pct (aliases weight / percentage are accepted); when no positive weights are set, the split is even. The executor records the assignment on the node’s own step output ({ ab_test: true, variant_id, variant_label } in the Executions step trace), and the ab-results rollup above reads assignments straight back off that trace — which matches the abTest row in the Edge Semantics table, where each variant id (a/b, or the variant’s handle id) routes over its own sourceHandle edge. Save-time validation (the GET /flows/:id/validate checks above) also runs on create/update, so a flow with fewer than two variants, or with explicit weights that do not sum to 100, is rejected before it ever runs. By default a variant is sticky for the contact. An abTest node with data.bandit_mode: true instead routes adaptively: it asks the real-time decisioning engine on each visit which variant currently leads (fed by the same assigned/converted counts ab-results reports) and routes toward that variant. Static validation of the variant shape still applies.

Conversation Flows for AI Agents

Classic Flows route CONTACTS through channel automations. AI agents get a second, in-conversation map: a conversation flow. Attach one under an agent’s config.conversation_flow and every turn routes between NAMED NODES (per-state instructions) instead of relying on a single static system prompt — keyword or regex edge conditions against the customer’s latest message decide which node’s instructions are active, validation rejects malformed graphs at save, and the route replays deterministically every turn over the conversation history. The feature lives in the Agents doc family — see Conversation flows for graph anatomy, conditions and fallbacks, the save-validation contract, versioning and A/B experiment pinning, deterministic replay, and worked billing/triage and escalation examples.

Next Steps