Automations
Build visual flows that trigger on social events and orchestrate multi-step conversations across Instagram, Messenger, WhatsApp, and Telegram.
Overview
Automations are RelayAPI's flow-based engine. A flow reacts to events on a connected social channel — comments, DMs, keywords, scheduled times, tag changes, third-party webhooks — and executes a directed graph that can send messages, capture replies, branch on conditions, mutate the contact, call external APIs, and hand control off to another flow.
Supported channels: Instagram, Facebook Messenger, WhatsApp, and Telegram. Each automation is bound to exactly one channel; the builder surfaces channel-aware warnings when you use a feature that channel doesn't support.
Core concepts
An automation has four moving parts:
- Graph — the flow itself. Stored as a single JSONB document on the automation row (
nodes,edges,root_node_key). Nodes have typed input and output ports; edges connect one node's output port to another node's input port. No label fallbacks, no implicit wiring — if a port has no outgoing edge, the run ends there. - Entrypoints — the events that start a run. A flow can have multiple entrypoints (several keywords, a DM kind, a webhook) and the matcher picks the most specific one on each event.
- Bindings — runtime or provider-profile attachments on a social account:
default_reply,welcome_message, Facebook Get Started, Instagram/Facebook persistent menus, and Instagram ice breakers. - Runs — execution instances. One row per enrolled contact per flow, with append-only per-node logs in
automation_step_runsfor inspection.
Edges are port-based
Every edge connects four things:
{ "from_node": "greet", "from_port": "button.btn_large", "to_node": "order_large", "to_port": "in" }There is no label field and no fallback — the port key IS the routing primitive. A message node with two branch buttons exposes button.<id> output ports; the runner follows the edge matching the port the handler exited through. If you don't wire a button port, pressing that button ends the run on that path. Ports are derived deterministically from node config on every save, so adding a button creates its port, deleting one removes the port and prunes orphan edges with a warning.
Lifecycle
draft → active → paused → archived- draft — initial state for newly created automations; nothing fires.
- active — entrypoints and bindings match live events.
- paused — no new runs start; in-flight runs keep going unless stopped.
- archived — read-only; no runs start.
There is no revisions / draft-published split. Every automation has exactly one live graph. Edits are instantly live. If an in-flight run's current node disappears after an edit, the run exits cleanly with exit_reason = "graph_changed" — it never corrupts state.
Quickstart
The fastest path is a template. One POST creates the flow, generates the graph, and attaches an initial entrypoint:
curl -X POST https://api.relayapi.dev/v1/automations \
-H "Authorization: Bearer $RELAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Link-in-bio giveaway",
"channel": "instagram",
"template": {
"kind": "comment_to_dm",
"config": {
"post_ids": ["17895..."],
"keyword_filter": ["link", "giveaway"],
"public_reply": "Sent!",
"dm_message": { "blocks": [{ "id": "b1", "type": "text", "text": "Here you go, {{contact.name}}!" }] },
"once_per_user": true
}
}
}'The call returns the full automation with its generated graph as a draft. Inspect, tweak if needed via PUT /v1/automations/{id}/graph, then activate with POST /v1/automations/{id}/activate.
See Templates for the full catalog and per-template config shapes.
Authoring a custom graph
For anything a template doesn't cover, create a blank automation and replace the graph:
# 1. Create
POST /v1/automations
{ "name": "Welcome + ask email", "channel": "instagram", "template": { "kind": "blank", "config": {} } }
# 2. Replace graph
PUT /v1/automations/{id}/graph
{
"graph": {
"schema_version": 1,
"root_node_key": "greet",
"nodes": [
{
"key": "greet",
"kind": "message",
"config": {
"blocks": [{ "id": "b1", "type": "text", "text": "Hey {{contact.name}}! Want my free guide?" }],
"wait_for_reply": false
},
"ports": []
},
{
"key": "ask_email",
"kind": "input",
"config": { "field": "email", "input_type": "email", "max_retries": 2, "timeout_min": 60 },
"ports": []
}
],
"edges": [
{ "from_node": "greet", "from_port": "next", "to_node": "ask_email", "to_port": "in" }
]
}
}
# 3. Add an entrypoint
POST /v1/automations/{id}/entrypoints
{ "kind": "dm_received", "channel": "instagram", "social_account_id": "acc_abc123" }
# 4. Activate
POST /v1/automations/{id}/activateOn save, the server derives each node's ports array, runs the validator, and stores the canonicalized graph. Validation errors block activation and auto-pause the automation; warnings (orphan button ports, etc.) don't.
Merge tags
Message text/captions, button labels, HTTP request fields, and selected action values support {{...}} substitution:
{{contact.name}},{{contact.email}},{{contact.phone}}{{context.<key>}}— values captured earlier in the run, including input fields and webhook mappings{{context.fields.<slug>}}— a hydrated contact custom-field value{{state.<key>}}— compatibility alias for the same run context{{name}}— shorthand for{{contact.name}}
Unresolved paths render as an empty string.
Availability notes
- AI nodes — dropped from v1.
ai_step,ai_agent, andai_intent_routerare archived and will return in a later release. - Broadcasts are a separate system and are not part of automations.
Related
- Nodes — the 12 node kinds, ports, config shapes, and action catalog.
- Triggers — entrypoint kinds, bindings, conflict resolution, webhook entrypoints.
- Runs — run lifecycle, step-run inspection, stopping a run.
- Templates — the 8 starter templates and their config shapes.
Found something wrong? Help us improve this page.