> For the complete documentation index, see [llms.txt](https://docs.graspil.com/en/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.graspil.com/en/api/automations.md).

# Automations

Methods for managing automations (workflows) programmatically: creating, editing, publishing, stopping, viewing, and deleting. These are the same actions available in the dashboard under ["Automations"](/en/app/automations.md) — useful if you want to create or change automations from your own system instead of using the visual builder.

{% hint style="info" %}
Automation methods are only available on the **Premium** plan.
{% endhint %}

### What is an automation

An automation is a rule of the form "when **X** happens → do **Y**". For example: "when a user completes the 'payment' event → send them a thank-you message", or "every day at 10:00 → send a request to an external server".

An automation is structured as a graph (diagram) made of nodes:

* **Trigger** — what starts the automation. Exactly one per automation: by event, by schedule, or manually.
* **Action** — what to do: send a message, make a request to an external server, change a user field, and so on.
* **Condition** — a branch: if the condition is true, execution follows one path; if not, it follows another (or stops).
* **Wait** — pause for a set amount of time, or wait until the user completes another event, then continue.

Nodes are connected to each other with arrows ("edges") — exactly the same way as in the visual automation builder in the dashboard. Through the API you're essentially building the same diagram you'd build with your mouse in the dashboard, just as JSON.

### How it works in a nutshell

1. You create an automation — the `create` method. At this stage you only need a name; the diagram can be added later.
2. You save the automation's diagram (graph) via `update` — as a draft. A draft can be saved as many times as you like and doesn't need to be complete (for example, it can have no actions yet).
3. Once the diagram is ready, publish it via `publish` (or pass `activate: true` to `update` to publish right away). Publishing runs strict validation: there must be exactly one trigger, at least one action, and all nodes must be connected to each other.
4. A published automation runs on its own — it watches for its trigger and executes its actions. There's no need to manually run it or monitor execution through the API in this version — that happens automatically.
5. To temporarily turn an automation off, use `unpublish`. To see the list and details, use `list` and `get`. To delete it permanently, use `delete`.

{% hint style="info" %}
A published diagram is never edited "in place" — any change made via `update` creates a new draft version, while the currently published version keeps running until you publish the new one.
{% endhint %}

### Authentication

If your API key is tied to a single bot (a regular key from the "My Bots" section — see [Authorization](/en/api/auth.md)), you don't need to specify anything else.

If you're using a key from the [API Keys](https://app.graspil.com/api-keys) section that's been issued for several bots at once, **every** request must explicitly state which bot the automation belongs to — via the `resource_key` field (for `POST`) or query parameter (for `GET`). Unlike broadcast and report methods, this field is always required for automations, even if your key is tied to a single bot.

{% hint style="warning" %}
The key must be issued to your account (the same one you use to sign in to the dashboard), not a "shared" resource key without an owner. If the role the key belongs to only has view access to automations (no edit permission), the write methods (`create`, `update`, `publish`, `unpublish`, `delete`) will return a `403` error.
{% endhint %}

### Response format

Successful response:

{% code overflow="wrap" %}

```json
{ "ok": true, "data": { ... } }
```

{% endcode %}

Error response:

{% code overflow="wrap" %}

```json
{ "ok": false, "error": "...", "error_code": 400 }
```

{% endcode %}

***

## The automation object

This is what's returned by the `create`, `get`, `publish`, and `unpublish` methods, and each item in `list`.

| Field               | Type         | Description                                           |
| ------------------- | ------------ | ----------------------------------------------------- |
| `id`                | string       | Automation ID                                         |
| `resource_key`      | string       | The bot the automation is tied to                     |
| `name`              | string       | Name (only visible to you, in the dashboard)          |
| `status`            | int          | Status — see the table below                          |
| `trigger_type`      | string       | Trigger type: `manual`, `event`, or `schedule`        |
| `graph_definition`  | object       | The current published diagram (nodes and connections) |
| `active_version_id` | string\|null | ID of the published diagram version                   |

#### Automation statuses

| Code | Meaning                   |
| ---- | ------------------------- |
| `0`  | Draft — not published yet |
| `1`  | Active — running          |
| `2`  | Stopped (via `unpublish`) |
| `3`  | Archived                  |
| `4`  | Error                     |

The `update` method additionally returns a **version** object — the specific diagram draft you're editing:

| Field              | Type   | Description                                  |
| ------------------ | ------ | -------------------------------------------- |
| `id`               | string | Version ID                                   |
| `workflow_id`      | string | ID of the automation this version belongs to |
| `graph_definition` | object | This version's diagram                       |
| `version_name`     | string | Version name (for your own reference)        |

***

## The automation diagram (`graph_definition`)

The diagram is a set of nodes (`nodes`) and connections between them (`edges`):

{% code overflow="wrap" %}

```json
{
  "nodes": [
    { "id": "trigger_1", "type": "trigger", "data": { "trigger_type": "event", "event_ids": [7] } }
  ],
  "edges": [
    { "source": "trigger_1", "target": "action_1" }
  ]
}
```

{% endcode %}

Each node has its own `id` (you choose it yourself — it just needs to be unique within the diagram) and a `type`, one of four: `trigger`, `action`, `condition`, `wait`, `wait_event`. What the node actually does lives in its `data` field and depends on the type.

Connections (`edges`) define execution order: `source` is the node the arrow comes from, `target` is the node it points to.

A node also has an optional `position` field (`{ "x": number, "y": number }`) — its coordinates on the visual editor canvas. If omitted, the editor automatically lays nodes out one after another without overlapping, so you can leave `position` out when building a diagram via the API/an agent.

{% hint style="info" %}
When saving a draft (`update`), the diagram doesn't need to be complete — you can save an empty diagram, or one without actions, and finish it later. Full validation (exactly one trigger, at least one action, all nodes connected) only runs at publish time (`publish`, or `update` with `activate: true`).
{% endhint %}

If the diagram fails validation at publish time, the response includes a list of errors:

{% code overflow="wrap" %}

```json
{
  "ok": false,
  "error": {
    "validation_errors": {
      "global": [
        { "code": "graph.multiple_triggers", "message": "Graph must contain exactly one trigger node." }
      ],
      "nodes": {
        "trigger_1": [
          { "code": "trigger.no_event", "message": "Trigger node requires event selection." }
        ]
      }
    }
  }
}
```

{% endcode %}

### Trigger node (`trigger`)

What starts the automation. The type comes from `data.trigger_type`:

| `trigger_type` | When it fires                                    | What to set in `data`                                                                      |
| -------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| `manual`       | Started manually (for testing)                   | —                                                                                          |
| `event`        | When a user completes one of the selected events | `event_ids` — an array of event IDs from your event catalog                                |
| `schedule`     | On a schedule                                    | `launch_frequency` (`day`, `week`, `month`, or `year`), `launch_time` (time as `HH:MM:SS`) |

Example — triggered by event ID `7`:

{% code overflow="wrap" %}

```json
{ "id": "trigger_1", "type": "trigger", "data": { "trigger_type": "event", "event_ids": [7] } }
```

{% endcode %}

{% hint style="info" %}
For `event` and `schedule` triggers, you can optionally narrow the audience — for example, only run the automation for users with a specific interface language. This is optional; the condition format is the same as the audience filter used for broadcasts — see "Broadcast audience" in the [broadcasts](/en/api/broadcasts.md) documentation.
{% endhint %}

### Action node (`action`)

What to execute. The type comes from `data.action_type`. All text fields in actions support variables like `{{user.first_name}}` or `{{event.value_num}}` — they're automatically substituted with real values at execution time.

| `action_type`         | What it does                                 | What to set in `data`                                                                                             |
| --------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `send_message`        | Send a message to the user on Telegram       | `type` (`sendMessage`, `sendPhoto`, `sendVideo`, etc.) and `message` — content, same format as broadcast messages |
| `webhook`             | Send an HTTP request to your server          | `method` (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`), `url`                                                         |
| `create_event`        | Record a new event in analytics              | `resource_key`, `event_name`                                                                                      |
| `change_custom_field` | Change a user's custom field                 | `field_key`, `operation` (`set`, `increase`, or `decrease`), `value`                                              |
| `amocrm` / `bitrix24` | Create or update a deal/contact in a CRM     | `operation`, `integration_key` (a pre-configured integration)                                                     |
| `yandex_metrika`      | Send an offline conversion to Yandex.Metrika | `integration_key`, `id_type`, `id_path`, `target`                                                                 |

A button in `message.reply_markup.inline_keyboard` with `"workflow_branch": true` and its own `"id"` is marked as a workflow branch — the server fills in its `callback_data` and links it to a `wait_event` node automatically (see "Buttons that continue the workflow" below).

#### Integrations (`amocrm`, `bitrix24`, `yandex_metrika`)

These three actions use a pre-configured integration from the "Integrations" section of the dashboard — its identifier is passed in `data.integration_key`. The node only references the integration; credentials (domain, token, counter ID) are never stored in the graph itself.

**`amocrm`** — create or update a deal in AmoCRM.

| `data` field        | Type   | Description                                                                                             |
| ------------------- | ------ | ------------------------------------------------------------------------------------------------------- |
| `integration_key`   | string | AmoCRM integration key                                                                                  |
| `operation`         | string | `create_lead`, `update_lead`, or `upload_utm`                                                           |
| `name_template`     | string | Deal name template (for `create_lead`/`update_lead`), supports `{{variables}}`                          |
| `user_fields`       | object | Map `{amoCRM_field_id: path_or_template}` — which deal fields to fill and where the value comes from    |
| `deal_id_path`      | string | Where to read the existing deal ID from (for `update_lead`/`upload_utm`); defaults to `amo_crm.deal_id` |
| `create_if_missing` | bool   | For `update_lead` — create a deal if none is found                                                      |
| `include_utms`      | bool   | Mix UTM tags into the custom fields                                                                     |
| `utm_field_map`     | object | Map `{utm_parameter: amoCRM_field_id}`                                                                  |
| `dry_run`           | bool   | Don't send the request to AmoCRM, return the request body instead (for debugging)                       |

{% code overflow="wrap" %}

```json
{
  "id": "action_amo",
  "type": "action",
  "data": {
    "action_type": "amocrm",
    "integration_key": "amo_main",
    "operation": "create_lead",
    "name_template": "Lead from {{user.first_name}}",
    "user_fields": { "123456": "{{user.username}}" },
    "include_utms": true,
    "utm_field_map": { "utm_source": "789012" }
  }
}
```

{% endcode %}

**`bitrix24`** — update deal/lead/contact fields in Bitrix24 (matched to the Telegram user via the IM field).

| `data` field      | Type   | Description                                                                                    |
| ----------------- | ------ | ---------------------------------------------------------------------------------------------- |
| `integration_key` | string | Bitrix24 integration key                                                                       |
| `operation`       | string | Only `update_deal` is supported right now                                                      |
| `update_targets`  | array  | Which entities to update: `deal`, `lead`, `contact` (multiple allowed); defaults to `["deal"]` |
| `user_fields`     | object | Map `{Bitrix24_field_code: template}`, e.g. `{"COMMENTS": "{{event.value_str}}"}`              |

{% code overflow="wrap" %}

```json
{
  "id": "action_bitrix",
  "type": "action",
  "data": {
    "action_type": "bitrix24",
    "integration_key": "bitrix_main",
    "operation": "update_deal",
    "update_targets": ["deal", "lead"],
    "user_fields": { "UF_CRM_PAID": "{{event.value_num}}" }
  }
}
```

{% endcode %}

**`yandex_metrika`** — send an offline conversion via the CalibratedConversion API.

| `data` field         | Type   | Description                                                                        |
| -------------------- | ------ | ---------------------------------------------------------------------------------- |
| `integration_key`    | string | Yandex.Metrika integration key (its settings hold the `counter_id`)                |
| `id_type`            | string | Visitor identifier type: `client_id`, `yclid`, or `user_id`                        |
| `id_path`            | string | Template/path to read the identifier value from, e.g. `{{user.ym_client_id}}`      |
| `target`             | string | Goal name template in Metrika                                                      |
| `date_time_template` | string | Optional — conversion time template (unix timestamp); defaults to the current time |
| `price_template`     | string | Optional — conversion amount                                                       |
| `currency`           | string | Optional — currency code, only used together with `price_template`                 |

{% code overflow="wrap" %}

```json
{
  "id": "action_ym",
  "type": "action",
  "data": {
    "action_type": "yandex_metrika",
    "integration_key": "ym_main",
    "id_type": "client_id",
    "id_path": "{{user.ym_client_id}}",
    "target": "purchase",
    "price_template": "{{event.value_num}}",
    "currency": "RUB"
  }
}
```

{% endcode %}

Example — send a message:

{% code overflow="wrap" %}

```json
{
  "id": "action_1",
  "type": "action",
  "data": {
    "action_type": "send_message",
    "type": "sendMessage",
    "message": { "text": "Hi, {{user.first_name}}! Thanks for your purchase." }
  }
}
```

{% endcode %}

Example — send a request to your own server:

{% code overflow="wrap" %}

```json
{
  "id": "action_2",
  "type": "action",
  "data": {
    "action_type": "webhook",
    "method": "POST",
    "url": "https://example.com/webhook",
    "body_template": "{\"user_id\": {{user.id}}}"
  }
}
```

{% endcode %}

### Condition node (`condition`)

Branches execution: if the condition is true, execution follows one path; if not, it follows another.

{% code overflow="wrap" %}

```json
{ "id": "cond_1", "type": "condition", "data": { "condition": ["and", ["=", "users.is_premium", 1]] } }
```

{% endcode %}

The `condition` format is the same as the broadcast audience filter (`["and"|"or", ...]`, with leaves shaped like `["operator", "field", value]`).

### Wait nodes (`wait` and `wait_event`)

`wait` — pause for a set number of seconds before continuing:

{% code overflow="wrap" %}

```json
{ "id": "wait_1", "type": "wait", "data": { "seconds": 3600 } }
```

{% endcode %}

`wait_event` — pause until the user completes a specific event (`event_key`), but no longer than `timeout` minutes:

{% code overflow="wrap" %}

```json
{ "id": "wait_event_1", "type": "wait_event", "data": { "event_key": 12, "timeout": 1440 } }
```

{% endcode %}

#### Buttons that continue the workflow (`data.buttons`)

Instead of a single `event_key`, a `wait_event` node can wait for a click on one of several inline buttons of a message sent by a `send_message` action — this is what the constructor UI calls a "button that continues the workflow", see [Automations → buttons section](/en/app/automations.md):

{% code overflow="wrap" %}

```json
{
  "id": "wait_event_1",
  "type": "wait_event",
  "data": {
    "timeout": 1440,
    "buttons": [
      { "button_id": "b1", "event_type_id": 501 },
      { "button_id": "b2", "event_type_id": 502 }
    ]
  }
}
```

{% endcode %}

In this mode `event_key` is not required. Each button in the message's `reply_markup.inline_keyboard` that's marked `"workflow_branch": true` with its own `"id"` is linked to a `data.buttons` entry by `button_id`; the server fills in `event_type_id` automatically when the graph is saved. The outgoing edge for each button is set as `sourceHandle: "btn:<button_id>"`. Unlike a regular `wait_event`, a button-bound node never completes on timeout — it keeps listening for clicks and can fire repeatedly (a timeout just extends the wait).

### Full example: event → request to server → message

{% code overflow="wrap" %}

```json
{
  "nodes": [
    { "id": "trigger_1", "type": "trigger", "data": { "trigger_type": "event", "event_ids": [7] } },
    {
      "id": "action_1",
      "type": "action",
      "data": { "action_type": "webhook", "method": "POST", "url": "https://example.com/webhook" }
    },
    {
      "id": "action_2",
      "type": "action",
      "data": {
        "action_type": "send_message",
        "type": "sendMessage",
        "message": { "text": "Hi, {{user.first_name}}!" }
      }
    }
  ],
  "edges": [
    { "source": "trigger_1", "target": "action_1" },
    { "source": "action_1", "target": "action_2" }
  ]
}
```

{% endcode %}

Pass this object as the `graph_definition` field in `create` or `update`.

***

## Methods

### POST /v1/automations/create

Creates a new automation (in "draft" status by default).

#### Parameters

| Parameter          | Type   | Required | Description                                                    |
| ------------------ | ------ | -------- | -------------------------------------------------------------- |
| `resource_key`     | string | yes      | See "Authentication" above                                     |
| `name`             | string | yes      | Automation name                                                |
| `trigger_type`     | string | no       | Defaults to `manual`. Can be set here or later via the diagram |
| `graph_definition` | object | no       | The diagram can be filled in right away or later via `update`  |

#### Example request

{% code overflow="wrap" %}

```json
POST /v1/automations/create
Api-Key: YOUR_API_KEY
Content-Type: application/json

{
  "resource_key": "bot_main",
  "name": "Thank-you after payment"
}
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{ "ok": true, "data": { "item": { "id": "a1b2c3", "status": 0, "name": "Thank-you after payment", "...": "..." } } }
```

{% endcode %}

***

### POST /v1/automations/update

Saves the automation's diagram (as a draft version). If the version being edited matches the currently published one, the system automatically creates a new draft version, without touching the running one.

#### Parameters

| Parameter          | Type   | Required              | Description                                                                             |
| ------------------ | ------ | --------------------- | --------------------------------------------------------------------------------------- |
| `resource_key`     | string | yes                   | See "Authentication" above                                                              |
| `id`               | string | yes                   | ID of the version to edit, or `"0"` to create a new version                             |
| `wid`              | string | yes, if `id` is `"0"` | Automation ID to create the new version for                                             |
| `graph_definition` | object | no                    | The new diagram                                                                         |
| `version_name`     | string | no                    | Version name — for your own reference                                                   |
| `name`             | string | no                    | New name for the automation itself                                                      |
| `activate`         | bool   | no                    | `true` — publish the saved version right away (with full validation, same as `publish`) |

{% hint style="info" %}
If you pass `activate: true` and the diagram fails full validation, the version is still saved as a draft, and the response returns `ok: false` with a list of errors and `ver_saved: true`.
{% endhint %}

#### Example request

{% code overflow="wrap" %}

```json
POST /v1/automations/update
Api-Key: YOUR_API_KEY
Content-Type: application/json

{
  "resource_key": "bot_main",
  "id": "0",
  "wid": "a1b2c3",
  "graph_definition": {
    "nodes": [
      { "id": "trigger_1", "type": "trigger", "data": { "trigger_type": "event", "event_ids": [7] } }
    ],
    "edges": []
  }
}
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{ "ok": true, "data": { "item": { "id": "v1", "workflow_id": "a1b2c3", "...": "..." }, "workflow": { "id": "a1b2c3", "status": 0, "...": "..." } } }
```

{% endcode %}

***

### POST /v1/automations/publish

Publishes the given diagram version — the automation starts running. Before publishing, the diagram is strictly validated: it must have exactly one trigger, at least one action, and all nodes must be connected to each other.

#### Parameters

| Parameter      | Type   | Required | Description                  |
| -------------- | ------ | -------- | ---------------------------- |
| `resource_key` | string | yes      | See "Authentication" above   |
| `id`           | string | yes      | Automation ID                |
| `version_id`   | string | yes      | ID of the version to publish |

#### Example request

{% code overflow="wrap" %}

```json
POST /v1/automations/publish
Api-Key: YOUR_API_KEY
Content-Type: application/json

{ "resource_key": "bot_main", "id": "a1b2c3", "version_id": "v1" }
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{ "ok": true, "data": { "item": { "id": "a1b2c3", "status": 1, "active_version_id": "v1", "...": "..." } } }
```

{% endcode %}

If the diagram fails validation, the response returns `ok: false` with a list of errors in `error.validation_errors` (format described in "The automation diagram" section above).

***

### POST /v1/automations/unpublish

Stops the automation (status changes to "stopped"). No validation required — it can be stopped at any time.

#### Parameters

| Parameter      | Type   | Required | Description                |
| -------------- | ------ | -------- | -------------------------- |
| `resource_key` | string | yes      | See "Authentication" above |
| `id`           | string | yes      | Automation ID              |

#### Example request

{% code overflow="wrap" %}

```json
POST /v1/automations/unpublish
Api-Key: YOUR_API_KEY
Content-Type: application/json

{ "resource_key": "bot_main", "id": "a1b2c3" }
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{ "ok": true, "data": { "item": { "id": "a1b2c3", "status": 2, "...": "..." } } }
```

{% endcode %}

***

### GET /v1/automations/get

Returns a single automation by ID — its current status and published diagram.

#### Parameters

| Parameter      | Type   | Required | Description                |
| -------------- | ------ | -------- | -------------------------- |
| `resource_key` | string | yes      | See "Authentication" above |
| `id`           | string | yes      | Automation ID              |

#### Example request

{% code overflow="wrap" %}

```
GET /v1/automations/get?resource_key=bot_main&id=a1b2c3
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{ "ok": true, "data": { "item": { "id": "a1b2c3", "status": 1, "...": "..." } } }
```

{% endcode %}

***

### GET /v1/automations/list

Returns the bot's list of automations.

#### Parameters

| Parameter      | Type   | Required | Description                                                 |
| -------------- | ------ | -------- | ----------------------------------------------------------- |
| `resource_key` | string | yes      | See "Authentication" above                                  |
| `project_code` | string | no       | Filter by project, if automations are grouped into projects |
| `page`         | int    | no       | Page number, defaults to `1`                                |
| `per_page`     | int    | no       | Items per page, defaults to `20`, maximum `100`             |

#### Example request

{% code overflow="wrap" %}

```
GET /v1/automations/list?resource_key=bot_main&page=1&per_page=20
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{
  "ok": true,
  "data": {
    "items": [ { "id": "a1b2c3", "status": 1, "name": "Thank-you after payment", "...": "..." } ],
    "count": 1,
    "page": 1,
    "page_count": 1
  }
}
```

{% endcode %}

***

### POST /v1/automations/delete

Permanently deletes the automation, along with all its versions. This action cannot be undone.

#### Parameters

| Parameter      | Type   | Required | Description                |
| -------------- | ------ | -------- | -------------------------- |
| `resource_key` | string | yes      | See "Authentication" above |
| `id`           | string | yes      | Automation ID              |

#### Example request

{% code overflow="wrap" %}

```json
POST /v1/automations/delete
Api-Key: YOUR_API_KEY
Content-Type: application/json

{ "resource_key": "bot_main", "id": "a1b2c3" }
```

{% endcode %}

#### Response

{% code overflow="wrap" %}

```json
{ "ok": true, "data": {} }
```

{% endcode %}

***

## Possible errors

| Code  | When it occurs                                                                                                                                           |
| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | A required parameter is missing, or the diagram (`graph_definition`) failed validation at publish time                                                   |
| `401` | The API key is missing or invalid                                                                                                                        |
| `403` | The method isn't available on your plan (Premium required), the key doesn't have permission to edit automations, or the bot isn't accessible to this key |
| `404` | The automation or version with the given `id` wasn't found                                                                                               |
| `500` | Internal server error — retry the request later                                                                                                          |

**Example error:**

{% code overflow="wrap" %}

```json
{ "ok": false, "error": "Premium tariff required", "error_code": 403 }
```

{% endcode %}
