Automations API
An automation is a workflow made of steps: one or more triggers that start it, then actions, conditions, delays, AI steps and the rest, connected into a graph. It is the same automation you see and edit on the Automations page. This guide covers reading and building them over the API.
- Base URL:
https://api.dmchamp.com/v1 - Authentication: your API key (see Authentication)
- Errors: see Errors & Pagination and Errors below
All examples below show the ?apiKey= query form in cURL and the X-API-Key header in JavaScript and Python. Either works on every endpoint.
Agencies: every endpoint on this page accepts
sub_account_idas a query parameter, so you can read and build a client’s automations with your agency key. Asub_account_idthat is not one of your client accounts returns404. See API for Agencies.
How it works: draft first
The API is built so that nothing starts running by accident.
- Read the catalogue.
GET /automations/stepslists every trigger, action and step kind you can use, plus the grammar for writing a graph (graph_rules). - Create a draft.
POST /automationsstores the automation switched off. Nothing runs yet. - Check it. Read it back, open it in the builder (the response gives you its
open_path), and make sure the person who owns the account has seen what it does and to whom. - Switch it on.
POST /automations/{automationId}/enableis its own call. This is the step that makes real things happen: messages sent, records changed, tools given to an AI agent.
Editing never changes whether an automation is on or off. That also means an edit to an automation that is on takes effect right away, on the live automation. To change a running automation safely, disable it, edit it, check it, and enable it again.
Every graph you send goes through the same checks the builder canvas applies. A graph it cannot open or run is refused with a 400 that lists exactly what to fix.
Access
- The account needs Automations. Without it, every call on this page returns
403. - A team member’s access follows their Automations area in Team Management: reading (list, get, steps, runs, folders) needs View, everything that changes something needs Edit.
- On a client account whose plan leaves out creating and editing automations, the write calls return
403, exactly as the builder does in the app.
The automation object
The list returns a summary of each automation, without its steps:
{
"id": "8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f",
"name": "Price question auto-reply",
"description": "Replies with the price list when someone asks about prices.",
"enabled": false,
"updated_at": "2026-10-27T09:40:00.000Z",
"trigger_kind": "incoming_message",
"folder_id": "f1a2b3c4-5d6e-7f80-91a2-b3c4d5e6f708",
"folder_name": "Sales"
}
| Field | Meaning |
|---|---|
id |
The automation’s id. |
name |
What it is called in the list. |
description |
One to three sentences on what it does, or null. |
enabled |
Whether it is switched on. A disabled automation never runs. |
updated_at |
When it was last changed. |
trigger_kind |
What sets it off, for example incoming_message, schedule, webhook, agent_function or manual. null when the graph has no trigger yet. |
folder_id, folder_name |
The folder it is filed in, or null for no folder. |
Reading one automation adds created_at and its graph:
{
"id": "8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f",
"name": "Price question auto-reply",
"description": "Replies with the price list when someone asks about prices.",
"enabled": false,
"created_at": "2026-10-27T09:12:00.000Z",
"updated_at": "2026-10-27T09:40:00.000Z",
"trigger_kind": "incoming_message",
"folder_id": null,
"folder_name": null,
"graph": {
"nodes": [
{
"id": "trigger_1",
"kind": "trigger",
"label": "Incoming message",
"alias": "incoming_message",
"action": "incoming_message",
"params": { "message_filter_contains": "price" },
"config": { "kind": "incoming_message", "message_filter_contains": "price" }
},
{
"id": "send_prices",
"kind": "action",
"label": "Send message",
"alias": "send_message",
"action": "SEND_MESSAGE",
"params": { "contact_id": "${trigger.contact.id}", "body": "Hi ${trigger.contact.first_name}, here is our price list: https://example.com/prices" },
"config": { "provider": "native", "slug": "SEND_MESSAGE", "params": { "contact_id": "${trigger.contact.id}", "body": "Hi ${trigger.contact.first_name}, here is our price list: https://example.com/prices" } }
}
],
"edges": [{ "from": "trigger_1", "to": "send_prices" }]
}
}
Each step (nodes[]) carries:
| Field | Meaning |
|---|---|
id |
The step’s id, unique within the graph. |
kind |
The kind of step: trigger, action, condition, transform, ai, delay, loop or respond. See Step kinds. |
label |
The name shown on the step in the builder. |
alias |
The step’s reference name. Later steps read its output as ${nodes.<alias>.<field>}. null on a step that has none (it is then referenced by its id). |
action |
What the step does: the action it runs, or on a trigger step the trigger kind. null on other steps. |
params |
The action’s parameters on an action step, or the trigger’s settings on a trigger step. null on every other kind. Kept for older readers: config carries the same values and more. |
config |
The step’s full settings as the builder stores them, for every kind of step: a condition’s rules, an AI step’s prompt, a delay’s duration, an action’s slug and parameters. |
Each connection (edges[]) has from and to (step ids), and branch when it follows one branch of a step, for example true or false after a condition.
Two things in config are changed on the way out:
- Secrets are hidden. Values that look like credentials (webhook secrets, tokens, API keys, passwords, header values) come back as
"[redacted]". Send"[redacted]"back unchanged and the stored value is kept. - Long texts are shortened. A single text longer than about 4,000 characters is cut short and ends with an ellipsis. If you send that step back, put its full text back in first.
List automations
GET /automations returns the automations on the account, most recently changed first, as summaries (no steps).
Query parameters (all optional): folder_id (only the automations in that folder, an id from List folders, or none for the ones in no folder), sub_account_id.
cURL
curl "https://api.dmchamp.com/v1/automations?apiKey=YOUR_API_KEY&folder_id=none"
JavaScript
const res = await fetch("https://api.dmchamp.com/v1/automations?folder_id=none", {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const { automations } = await res.json();
Python
res = requests.get(
"https://api.dmchamp.com/v1/automations",
headers={"X-API-Key": "YOUR_API_KEY"},
params={"folder_id": "none"},
)
automations = res.json()["automations"]
Response (200)
{ "success": true, "automations": [{ "id": "8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f", "name": "Price question auto-reply", "enabled": false, "...": "..." }] }
If the automation service cannot be reached you get a 503, never an empty list in its place. An empty list always means there are no automations.
Get an automation
GET /automations/{automationId} returns one automation with its full graph: every step’s kind, label, alias, action, parameters and settings, plus the connections between them.
curl "https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f?apiKey=YOUR_API_KEY"
const res = await fetch("https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f", {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const { automation } = await res.json();
res = requests.get(
"https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f",
headers={"X-API-Key": "YOUR_API_KEY"},
)
automation = res.json()["automation"]
Response (200)
{ "success": true, "automation": { "id": "8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f", "name": "Price question auto-reply", "graph": { "nodes": ["..."], "edges": ["..."] } } }
An id that is not on this account returns 404.
Step catalogue
GET /automations/steps returns everything an automation can be built from:
node_kinds: the 8 kinds of step, each with a label and a description.triggers: every trigger that can start an automation, with its settings fields (fields) and the values it hands to later steps (output_keys).actions: the built-in actions an action step can run, each with itskey(the slug you use in a graph) and itsparams, markedrequiredwhere they are.api_actions: the operations of this API that an action step can call. Withoutqthey come back as labels grouped by area inapi_actions_by_tag, and the flat list is empty, because there are several hundred of them.graph_rules: the grammar for writing a graph: the data each step kind carries, every trigger kind with its settings keys, the action providers, the branch names and the template syntax. Read it before you create or edit one.counts: the totals per list, whatever you searched for.
This is what the builder offers, not what the account has built. Check it before you conclude that a trigger or action does not exist.
Query parameters (all optional): q, a word to search every list for (case-insensitive), and sub_account_id.
With q:
api_actionsis the flat list of matches, capped at 60.api_actions_truncatedistruewhen the cap applied. The first matches carry their params.- Actions for connected third-party apps are searched too and come back in
app_actions. Search by the app’s name, for exampleq=hubspot. Each one says whether the account has that app connected (connected), andconnected_appslists the apps the account has connected. app_actions_unavailableistruewhen the app actions could not be searched this time. The other lists are still complete.
curl "https://api.dmchamp.com/v1/automations/steps?apiKey=YOUR_API_KEY&q=tag"
const res = await fetch("https://api.dmchamp.com/v1/automations/steps?q=tag", {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const { triggers, actions, graph_rules } = await res.json();
res = requests.get(
"https://api.dmchamp.com/v1/automations/steps",
headers={"X-API-Key": "YOUR_API_KEY"},
params={"q": "tag"},
)
catalogue = res.json()
Response (200, shortened)
{
"success": true,
"q": "tag",
"node_kinds": [{ "kind": "trigger", "label": "Trigger", "description": "Starts the automation when an event happens." }],
"triggers": [
{
"kind": "tag_applied",
"label": "Tag applied",
"group": "Contacts",
"fields": ["tag_name", "channel", "applied_by"],
"output_keys": ["contact.id", "contact.first_name", "..."]
}
],
"actions": [
{
"key": "SET_CONTACT_TAG",
"label": "Set contact tag",
"group": "Contacts",
"provider": "native",
"params": [{ "key": "contact_id", "required": true }, { "key": "tag", "required": true }]
}
],
"api_actions": ["..."],
"app_actions": [],
"graph_rules": "GRAPH: { nodes: [{ id, type, label?, data }], edges: [{ source, target, sourceHandle? }] } ...",
"counts": { "node_kinds": 8, "triggers": 40, "actions": 44, "api_actions": 454 }
}
The lists of step kinds and triggers below are taken from this endpoint. The endpoint itself is always the current list.
Create an automation
POST /automations creates an automation from a name and a graph. It is stored switched off, as a draft.
Body
| Field | Required | Meaning |
|---|---|---|
name |
Yes | What it is called in the list. Short and specific. |
description |
No | One to three sentences on what it does and when it runs. |
graph |
Yes | { nodes, edges }, see below. |
folder_id |
No | The folder to file it in, an id from List folders. Left out or null, it goes in no folder. |
The graph. graph_rules from the step catalogue is the full grammar. In short:
- Each node has an
id(unique in the graph, for exampletrigger_1orsend_reminder), atype(one of the 8 step kinds), an optionallabel, and its settings indata, under a key named after its type:data.trigger = { kind, ... },data.action = { provider, slug, params },data.condition = { ... },data.delay = { seconds },data.ai = { prompt },data.loop = { over },data.transform = { language, code },data.respond = { status, body, content_type }. - An automation needs at least one trigger. The live grammar allows up to 10, and the automation starts when any of them fires.
- Every other step must be reachable from a trigger, and no connection may lead back to an earlier step.
- Each edge has a
sourceand atarget. Edges leaving a condition carrysourceHandletrueorfalse. Edges leaving a loop carrybodyordone. Edges leaving a Wait for reply delay carryrepliedortimed_out. Other edges have nosourceHandle; the grammar lists the rest. - Action slugs and params come from the catalogue. Never guess an id (an agent, a tag, a list, a stage): look it up with the API first, or leave the field as
""for the person to pick in the builder. - Positions are optional. Steps without one are laid out automatically.
- Values from earlier steps are written as
${trigger.<field>}and${nodes.<step>.<field>}. See Using what an earlier step returned and Expressions in the Automations guide.
The graph is accepted in two spellings: the builder’s own (nodes with type and data, edges with source and target, as below) and the one Get an automation answers with (nodes with kind, config, action, params; edges with from, to, branch). So you can read an automation, change a step and send it back.
curl -X POST "https://api.dmchamp.com/v1/automations?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Price question auto-reply",
"description": "Replies with the price list when someone asks about prices.",
"graph": {
"nodes": [
{ "id": "trigger_1", "type": "trigger", "label": "Incoming message",
"data": { "trigger": { "kind": "incoming_message", "message_filter_contains": "price" } } },
{ "id": "send_prices", "type": "action", "label": "Send message",
"data": { "action": { "provider": "native", "slug": "SEND_MESSAGE",
"params": { "contact_id": "${trigger.contact.id}", "body": "Hi ${trigger.contact.first_name}, here is our price list: https://example.com/prices" } } } }
],
"edges": [{ "source": "trigger_1", "target": "send_prices" }]
}
}'
const res = await fetch("https://api.dmchamp.com/v1/automations", {
method: "POST",
headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify({ name: "Price question auto-reply", graph: { nodes, edges } }),
});
const { automation, draft, open_path, warnings } = await res.json();
res = requests.post(
"https://api.dmchamp.com/v1/automations",
headers={"X-API-Key": "YOUR_API_KEY"},
json={"name": "Price question auto-reply", "graph": {"nodes": nodes, "edges": edges}},
)
draft = res.json()
Response (201)
{
"success": true,
"automation": { "id": "8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f", "name": "Price question auto-reply", "enabled": false, "graph": { "...": "..." } },
"draft": true,
"open_path": "/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f",
"warnings": []
}
automationis the automation as stored, in the same shape as Get an automation.draftistruewhile it is switched off. A new automation is always a draft until you enable it, soenabled: falsehere is normal, not a failure.open_pathis where it opens in the app’s builder.warningslists things the builder could not finish, as short labels (for exampleagent_function:not_linked). It is empty when everything is clean.
A create is not safe to repeat blindly. If a request times out, list your automations before you send it again, or you may end up with two drafts.
Update an automation
PUT /automations/{automationId} changes the name, the description and/or the graph. Send only the fields you want to change.
Body: name, description, graph (all optional, at least one).
A graph replaces the whole graph, so always work from what is stored:
- Read the automation with
GET /automations/{automationId}. - Change what you need in that graph.
- Send the whole graph back, every step included.
When you send it back:
- Keep every step’s
aliasexactly as you got it. Later steps refer to a step by its alias (${nodes.<alias>.<field>}). Change or drop it and every reference to that step comes out empty. See Naming your steps. - Leave
"[redacted]"values as they are. A value shown as"[redacted]"keeps its stored value. - Restore shortened texts. A text that came back ending in an ellipsis was cut short in the read.
Whether the automation is on is never changed here. If it is on, your edit is live as soon as it is saved. While an automation is on, a change that would make it trigger itself is refused: disable it first.
curl -X PUT "https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Price question auto-reply (EN)" }'
// Read, edit, send back the whole graph
const get = await fetch(`https://api.dmchamp.com/v1/automations/${id}`, { headers: { "X-API-Key": "YOUR_API_KEY" } });
const { automation } = await get.json();
const step = automation.graph.nodes.find((n) => n.id === "send_prices");
step.config.params.body = "Hi ${trigger.contact.first_name}, our new price list: https://example.com/prices";
await fetch(`https://api.dmchamp.com/v1/automations/${id}`, {
method: "PUT",
headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify({ graph: automation.graph }),
});
url = f"https://api.dmchamp.com/v1/automations/{automation_id}"
automation = requests.get(url, headers={"X-API-Key": "YOUR_API_KEY"}).json()["automation"]
# ...edit automation["graph"] here, keeping every alias...
requests.put(url, headers={"X-API-Key": "YOUR_API_KEY"}, json={"graph": automation["graph"]})
Response (200): the same shape as a create (automation, draft, open_path, warnings). Sending none of name, description or graph returns 400.
Enable and disable
POST /automations/{automationId}/enable switches an automation on, so it runs on its trigger from now on. Call it only once the person who owns the account has seen what the automation does and to whom, and has agreed to switch it on. The builder refuses to switch on a graph it cannot run (no trigger, a loop, a step that triggers itself) with a 400 that says why.
POST /automations/{automationId}/disable switches it off (pauses it). Runs already in progress finish; no new run starts. Disabling is always allowed.
Neither call takes a body.
curl -X POST "https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f/enable?apiKey=YOUR_API_KEY"
await fetch("https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f/disable", {
method: "POST",
headers: { "X-API-Key": "YOUR_API_KEY" },
});
requests.post(
"https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f/enable",
headers={"X-API-Key": "YOUR_API_KEY"},
)
Response (200): the automation as stored now, in the same shape as a create, with draft: false after an enable.
Run history
GET /automations/{automationId}/runs returns the runs of one automation, newest first: when each run started and finished, what set it off, whether it succeeded, and the error when it failed. Reading the automation tells you what it would do. The run history tells you what it did, and it is the only way to tell an automation that never ran from one that runs and fails every time.
Query parameters (all optional): limit (1 to 50, default 10), sub_account_id.
curl "https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f/runs?apiKey=YOUR_API_KEY&limit=20"
const res = await fetch("https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f/runs?limit=20", {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const { runs, note } = await res.json();
res = requests.get(
"https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f/runs",
headers={"X-API-Key": "YOUR_API_KEY"},
params={"limit": 20},
)
runs = res.json()["runs"]
Response (200)
{
"success": true,
"automation": { "id": "8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f", "name": "Price question auto-reply", "enabled": true, "trigger_kind": "incoming_message" },
"runs": [
{
"id": "run_7f3a",
"status": "failed",
"trigger_source": "incoming_message",
"started_at": "2026-10-27T10:02:11.000Z",
"finished_at": "2026-10-27T10:02:12.000Z",
"error": "Contact is on Do Not Disturb list",
"cancel_reason": null,
"mode": "live"
}
]
}
modeislivefor a real run, orsafefor a Safe run from the editor. A Safe run sends nothing, so it is no proof the automation works in real use.- An empty
runslist is an answer, not a gap. The response then carries anote: either the trigger has never fired, or the automation is switched off, so nothing has run.
Folders
Folders only group automations in the list. They never change what an automation does or whether it is on. See Organizing automations in folders.
List folders
GET /automations/folders returns the folders, each with how many automations it holds, plus unfiled_count: how many automations are in no folder.
curl "https://api.dmchamp.com/v1/automations/folders?apiKey=YOUR_API_KEY"
{
"success": true,
"folders": [{ "id": "f1a2b3c4-5d6e-7f80-91a2-b3c4d5e6f708", "name": "Sales", "automation_count": 3 }],
"unfiled_count": 5
}
Create a folder
POST /automations/folders with { "name": "Appointment reminders" }. The name is required, up to 100 characters, and must be unique on the account. File automations in it with Move an automation, or pass folder_id when you create one.
curl -X POST "https://api.dmchamp.com/v1/automations/folders?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Appointment reminders" }'
Response (201): { "success": true, "folder": { "id": "...", "name": "Appointment reminders", "automation_count": 0 } }. A name another folder already has returns 409 with code: "folder_name_taken".
Rename a folder
PUT /automations/folders/{folderId} with { "name": "..." }. The automations in it stay where they are.
Response (200): { "success": true, "folder": { ... } }.
Delete a folder
DELETE /automations/folders/{folderId} deletes the folder only. The automations in it are not deleted: they move to no folder and keep running exactly as before.
curl -X DELETE "https://api.dmchamp.com/v1/automations/folders/f1a2b3c4-5d6e-7f80-91a2-b3c4d5e6f708?apiKey=YOUR_API_KEY"
{ "success": true, "deleted": true, "moved_to_no_folder": 3, "message": "Folder deleted. 3 automations were moved to No folder; no automation was deleted." }
Move an automation to a folder
PUT /automations/{automationId}/folder with { "folder_id": "<folder id>" }, or { "folder_id": null } to take it out of its folder. folder_id is required. Only where the automation is listed changes.
curl -X PUT "https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f/folder?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "folder_id": "f1a2b3c4-5d6e-7f80-91a2-b3c4d5e6f708" }'
Response (200): { "success": true, "automation_id": "...", "folder_id": "..." }.
Catalogue
Step kinds
| Kind | What it does |
|---|---|
trigger |
Starts the automation when something happens. |
action |
Runs one step: a built-in action, an API action, a connected app’s action, an HTTP request or an email. |
condition |
Splits the flow on a rule, into a true and a false branch. |
transform |
Runs a short piece of code to reshape data between steps. |
ai |
Asks the AI with a prompt you write. It can return text, separate fields, or pick one of several branches. |
delay |
Waits for a set time, or waits for the contact to reply (with a timeout). |
loop |
Repeats the steps inside it for each item in a list. |
respond |
Returns a response to the webhook call or the AI agent function call that started the run. |
Triggers
These are the 40 triggers the catalogue lists today, grouped as the builder groups them. The Settings column holds the trigger’s own settings keys (fields), which narrow when it fires. Every trigger also takes respect_quiet_hours, run_hours, reentry and stop_on_response; see Automation settings.
Messaging
Trigger (kind) |
Fires when | Settings |
|---|---|---|
Incoming message (incoming_message) |
A contact sends a message on any channel. | campaign (limits it to one AI agent), channel, message_filter_contains (keyword), message_filter_regex (pattern) |
Missed call (missed_call) |
Someone calls one of your WhatsApp Web numbers or your own SMS number and nobody picks up. | to_number |
Message delivered (message_delivered) |
A message you sent is confirmed delivered by the channel. | channel, agent |
Message failed (message_failed) |
A message you sent could not be delivered, with the error. | channel, agent |
Message sent (message_sent) |
Any message leaves your account: the AI’s, yours, a broadcast, a follow-up. Filter it, or it runs on all of them. | channel, sent_by |
Follow-up sent (follow_up_sent) |
An automatic follow-up goes out. | none |
Follow-up skipped (follow_up_skipped) |
A follow-up was held back because its send condition failed. | none |
Follow-ups finished (follow_up_exhausted) |
A contact got the last follow-up in the sequence and nothing more is queued. | none |
Conversation closed (conversation_closed) |
A chat is closed, with how it ended and a summary. | channel, agent |
Broadcast finished (broadcast_completed) |
A broadcast has finished sending, with how many went out and how many failed. | none |
Lead reply sorted (outreach_reply_labelled) |
A cold email lead’s reply is sorted, for example as Interested. | label |
Skool comment received (skool_comment_received) |
A member comments under a Skool post one of your comment rules watches. | none |
Contacts
Trigger (kind) |
Fires when | Settings |
|---|---|---|
New contact (contact_created) |
A new person reaches you for the first time on any channel, and also for imports, the API and contacts you create by hand. | none |
Contact updated (contact_updated) |
A contact field changes. | field |
Contacts you add here (contact_added) |
Only for contacts you put into this automation yourself from the Contacts page. One run per contact. | none |
Tag applied (tag_applied) |
A tag is added to a contact, by your AI agent, a person, an automation or an import. | tag_name, channel, applied_by |
Tag removed (tag_removed) |
A tag is taken off a contact. | tag_name, channel |
Skool member joined (skool_member_joined) |
Someone joins a Skool community you own or run, with their profile, answers and fit score. | group_id, create_contact |
Skool member qualified (skool_member_qualified) |
A new Skool member’s answers score at or above your threshold. | min_score |
Deals
Trigger (kind) |
Fires when | Settings |
|---|---|---|
Deal created (deal_created) |
A new deal is added to one of your pipelines. | pipeline_id, stage_id |
Deal stage changed (deal_stage_changed) |
A deal moves to a different stage, naming the stage it left and the one it entered. | pipeline_id, to_stage_id, from_stage_id |
Deal updated (deal_updated) |
A deal field changes. | field |
Appointments
Trigger (kind) |
Fires when | Settings |
|---|---|---|
Appointment booked (appointment_booked) |
A new appointment is booked. | none |
Appointment cancelled (appointment_cancelled) |
A booked appointment is cancelled. | none |
Appointment rescheduled (appointment_rescheduled) |
A booked appointment moves to another time, with the old time alongside the new one. | event_id |
Before an appointment (appointment_upcoming) |
A set time before each confirmed appointment, for reminders. Up to five reminders per automation. | offsets_minutes |
Tasks
Trigger (kind) |
Fires when | Settings |
|---|---|---|
Task created (task_created) |
A task is created. | task_type |
Task completed (task_completed) |
A task is moved into a completed stage. | none |
Account
Trigger (kind) |
Fires when | Settings |
|---|---|---|
Low credit balance (low_credit_balance) |
Your credit balance drops below your alert threshold. | none |
Channel connected (channel_connected) |
One of your channels finishes connecting. | channel |
Channel disconnected (channel_disconnected) |
One of your channels drops: logged out, unlinked or cut off. A WhatsApp Web number that reconnects by itself only counts if it is still down after 30 minutes. | channel |
Agency
Trigger (kind) |
Fires when | Settings |
|---|---|---|
Sub-account created (sub_account_created) |
A new client account is created, from your dashboard, the API or your signup page. | none |
Sub-account activity (sub_account_event) |
Something happens on one of your client accounts: credits running low, a channel connecting, a plan change. | event, threshold |
General and apps
Trigger (kind) |
Fires when | Settings |
|---|---|---|
On schedule (schedule) |
On a cron schedule. | cron, timezone |
Webhook (webhook) |
An outside system calls the automation’s webhook address. | webhook_secret |
AI Agent Function (agent_function) |
Your AI agent calls this automation as a function during a conversation. | function_name, function_description, parameters, agent_ids, campaign_ids |
New feed item (rss) |
A new item appears in an RSS or Atom feed. | feed_url |
Email received (email_received) |
An email arrives in your own connected mailbox. | from_contains, subject_contains |
App event (app_event) |
Something happens in a connected app: a deal stage changes, a payment comes in, a form is submitted. | toolkit, trigger_slug, connected_account_id, config |
Manual run (manual) |
Only when it is started by hand. | none |
An automation can hold at most one trigger each of webhook, app_event, agent_function, rss, schedule, email_received, appointment_upcoming and contact_added. A trigger that fires when a record is created can’t be paired with the action that creates the same record, because the automation would set itself off.
For what each trigger hands to later steps (output_keys) and the full list of actions with their params, read GET /automations/steps. The Automations guide explains each trigger in the builder’s words.
Errors
Every error has the usual shape { "success": false, "error_code": <status>, "error": "..." }. A refused graph adds code and errors:
{
"success": false,
"error_code": 400,
"code": "invalid_graph",
"error": "<the first problem, as one sentence>",
"errors": ["<the first problem, as one sentence>", "<the next problem>"]
}
errors lists every problem, one fixable sentence each, and error is the first one. Fix exactly what is listed and send the whole graph again. code is one of invalid_graph, automation_graph_has_a_cycle, self_triggering_automation, no_trigger, or another code from the builder.
| Status | When |
|---|---|
400 |
The graph was refused (see above), a required field is missing (name or graph on a create, folder_id on a move), an update sent nothing to change, an automation or folder id is not valid, or a folder name is missing, empty or too long (code: "invalid_name"). |
403 |
The account does not have Automations; a client account’s plan does not include creating and editing automations (writes only); or the team member’s Automations access is too low (View for reads, Edit for writes). |
404 |
No automation with that id on this account, no folder with that id (code: "folder_not_found"), or a sub_account_id that is not one of your client accounts. |
409 |
A folder with that name already exists (code: "folder_name_taken"). |
413 |
The automation is larger than the 1 MB the builder accepts. Move large code or data out of its steps. |
422 |
The builder rejected the request for another reason. Check the graph against GET /automations/steps; sending the same request again will not help. |
503 |
The automation service could not be reached. Try again in a moment. For a create, list your automations first so you don’t make a second draft. |
Using these endpoints over MCP
Every endpoint on this page is also a tool for an AI assistant you connect over MCP, with the same rules: a new automation is a draft until it is enabled, and an edit to a running automation is live.
| Tool | Endpoint |
|---|---|
list_automations |
GET /automations |
get_automation |
GET /automations/{automationId} |
list_automation_steps |
GET /automations/steps |
create_automation |
POST /automations |
update_automation |
PUT /automations/{automationId} |
enable_automation |
POST /automations/{automationId}/enable |
disable_automation |
POST /automations/{automationId}/disable |
list_automation_runs |
GET /automations/{automationId}/runs |
list_automation_folders |
GET /automations/folders |
create_automation_folder |
POST /automations/folders |
rename_automation_folder |
PUT /automations/folders/{folderId} |
delete_automation_folder |
DELETE /automations/folders/{folderId} |
move_automation_to_folder |
PUT /automations/{automationId}/folder |
Next steps
- Automations: how triggers, steps, variables and the run history work in the builder.
- API for Agencies: build automations on a client account with
sub_account_id. - API Reference: the full interactive endpoint explorer.