
# 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](../automations/automations.md) page. This guide covers reading and building them over the API.

- **Base URL**: `https://api.dmchamp.com/v1`
- **Authentication**: your API key (see [Authentication](authentication.md))
- **Fields and bodies**: every parameter and response field is in the [API Reference](reference.md#tag/automations)
- **Errors**: see [Errors & Pagination](errors-and-pagination.md) and [Errors](#errors) below

> **Agencies:** every endpoint on this page accepts `sub_account_id` as a query parameter, so you can read and build a client's automations with your agency key. A `sub_account_id` that is not one of your client accounts returns `404`. See [API for Agencies](../agency/api-for-agencies.md).

---

## How it works: draft first

The API is built so that nothing starts running by accident.

1. **Read the catalogue.** `GET /automations/steps` lists every trigger, action and step kind you can use, plus the grammar for writing a graph (`graph_rules`).
2. **Create a draft.** `POST /automations` stores the automation **switched off**. Nothing runs yet.
3. **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.
4. **Switch it on.** `POST /automations/{automationId}/enable` is 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](../settings/team-management.md): 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.

---

## Endpoints

| Task | Method + path | Explorer |
|---|---|---|
| List automations | `GET /automations` | [Open in API Reference](reference.md#tag/automations/GET/automations) |
| Get an automation with its graph | `GET /automations/{automationId}` | [Open in API Reference](reference.md#tag/automations/GET/automations/{automationId}) |
| Read the step catalogue | `GET /automations/steps` | [Open in API Reference](reference.md#tag/automations/GET/automations/steps) |
| Create an automation (draft) | `POST /automations` | [Open in API Reference](reference.md#tag/automations/POST/automations) |
| Update an automation | `PUT /automations/{automationId}` | [Open in API Reference](reference.md#tag/automations/PUT/automations/{automationId}) |
| Switch it on | `POST /automations/{automationId}/enable` | [Open in API Reference](reference.md#tag/automations/POST/automations/{automationId}/enable) |
| Switch it off | `POST /automations/{automationId}/disable` | [Open in API Reference](reference.md#tag/automations/POST/automations/{automationId}/disable) |
| Read the run history | `GET /automations/{automationId}/runs` | [Open in API Reference](reference.md#tag/automations/GET/automations/{automationId}/runs) |
| List folders | `GET /automations/folders` | [Open in API Reference](reference.md#tag/automations/GET/automations/folders) |
| Create a folder | `POST /automations/folders` | [Open in API Reference](reference.md#tag/automations/POST/automations/folders) |
| Rename a folder | `PUT /automations/folders/{folderId}` | [Open in API Reference](reference.md#tag/automations/PUT/automations/folders/{folderId}) |
| Delete a folder | `DELETE /automations/folders/{folderId}` | [Open in API Reference](reference.md#tag/automations/DELETE/automations/folders/{folderId}) |
| Move an automation to a folder | `PUT /automations/{automationId}/folder` | [Open in API Reference](reference.md#tag/automations/PUT/automations/{automationId}/folder) |

---

## Reading automations

`GET /automations` returns summaries (no steps), most recently changed first; `folder_id=none` lists the ones in no folder. `GET /automations/{automationId}` adds the full `graph`: each step (`nodes[]`) with its `kind`, `label`, `alias`, `action`, `params` and `config`, and the connections (`edges[]`) with `from`, `to` and, after a condition or similar, `branch`.

```bash
curl "https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f?apiKey=YOUR_API_KEY"
```

**Good to know**

- **`alias` is how later steps read a step's output**, as `${nodes.<alias>.<field>}`. A step without one is referenced by its id.
- `params` is kept for older readers; `config` carries the same values and more (a condition's rules, an AI step's prompt, a delay's duration, an action's slug and parameters).
- **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.
- 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.

### The step catalogue

`GET /automations/steps` returns everything an automation can be built from: the 8 `node_kinds`, every trigger with its settings `fields` and `output_keys`, the built-in `actions` with their params, the `api_actions` (operations of this API an action step can call), the `graph_rules` grammar, and `counts`. 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.

- Without `q`, the several hundred API actions come back as labels grouped by area in `api_actions_by_tag` and the flat list is empty.
- With `q` (a case-insensitive word), `api_actions` is the flat list of matches, capped at 60 (`api_actions_truncated` says when). Actions for connected third-party apps are searched too and come back in `app_actions`: search by the app's name, for example `q=hubspot`. Each says whether the account has that app `connected`, and `connected_apps` lists the connected ones. `app_actions_unavailable` is `true` when the app actions could not be searched this time; the other lists are still complete.

---

## Creating and editing

### Create a draft

```bash
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" }]
    }
  }'
```

```json
{ "success": true, "automation": { "id": "8d2c7f1e-…", "enabled": false, "…": "…" }, "draft": true, "open_path": "/automations/8d2c7f1e-…", "warnings": [] }
```

`draft: true` and `enabled: false` are normal, not a failure: a new automation is always a draft until you enable it. `warnings` lists what the builder could not finish, as short labels (for example `agent_function:not_linked`).

**Writing the graph.** `graph_rules` from the catalogue is the full grammar. In short:

- Each node has an `id` (unique in the graph), a `type` (one of the 8 [step kinds](#step-kinds)), an optional `label`, and its settings in `data` under a key named after its type (`data.trigger`, `data.action`, `data.condition`, `data.delay`, `data.ai`, `data.loop`, `data.transform`, `data.respond`).
- 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.
- Edges leaving a condition carry `sourceHandle` `true` or `false`; a loop `body` or `done`; a Wait for reply delay `replied` or `timed_out`. Other edges have none.
- 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](../automations/automations.md#using-what-an-earlier-step-returned) and [Expressions](../automations/automations.md#expressions).
- The graph is accepted in two spellings: the builder's own (above) and the one a read answers with (`kind`, `config`, `from`, `to`, `branch`). So you can read an automation, change a step and send it back.
- **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

`PUT /automations/{automationId}` changes `name`, `description` and/or `graph` (at least one, or `400`). A `graph` replaces the **whole** graph, so always work from what is stored: read it, change what you need, send the whole graph back.

```bash
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)" }'
```

- **Keep every step's `alias` exactly as you got it.** Change or drop it and every reference to that step comes out empty. See [Naming your steps](../automations/automations.md#naming-your-steps).
- **Leave `"[redacted]"` values as they are**, and **restore shortened texts** (a text 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 it is on, a change that would make it trigger itself is refused: disable it first.

### 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. 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.

```bash
curl -X POST "https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f/enable?apiKey=YOUR_API_KEY"
```

`POST /automations/{automationId}/disable` pauses it: runs already in progress finish, no new run starts. Disabling is always allowed. Neither call takes a body.

---

## Run history

`GET /automations/{automationId}/runs` returns the runs of one automation, newest first (`limit` 1 to 50, default 10): when each 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.

```bash
curl "https://api.dmchamp.com/v1/automations/8d2c7f1e-2a3b-4c5d-9e8f-0a1b2c3d4e5f/runs?apiKey=YOUR_API_KEY&limit=20"
```

- `mode` is `live` for a real run, or `safe` for a **Safe run** from the editor. A Safe run sends nothing, so it is no proof the automation works in real use.
- An empty `runs` list is an answer, not a gap. The response then carries a `note`: either the trigger has never fired, or the automation is switched off.

---

## 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](../automations/automations.md#organizing-automations-in-folders).

```bash
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" }'
```

- Listing folders also returns `unfiled_count`, how many automations are in no folder.
- A folder name is required, up to 100 characters, and unique on the account (`409`, `code: "folder_name_taken"`). You can also file an automation by passing `folder_id` when you create it.
- Renaming leaves the automations where they are.
- **Deleting a folder deletes the folder only.** Its automations move to no folder and keep running exactly as before; the response says how many moved (`moved_to_no_folder`).
- On a move, `folder_id` is required; send `null` to take the automation out of its folder.

---

## 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](../automations/automations.md#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](../automations/automations.md#triggers) 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`: `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, a required field is missing (`name` or `graph` on a create, `folder_id` on a move), an update sent nothing to change, an 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. |
| `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. |

::: master-only
## Using these endpoints over MCP

Every endpoint on this page is also a tool for an AI assistant you [connect over MCP](../integrations/connect-ai-clients.md), 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](../automations/automations.md): how triggers, steps, variables and the run history work in the builder.
- [API for Agencies](../agency/api-for-agencies.md): build automations on a client account with `sub_account_id`.
- [API Reference](reference.md): the full interactive endpoint explorer.
