# Automations · Management API

> Management API reference for automations: GET /sites/{id}/automations, PATCH /sites/{id}/automations/{automationKey}/actions/{actionKey}, GET /sites/{id}/automations/catalog, GET /sites/{id}/automations/templates, ….

> Markdown version of https://kanorio.com/en/developers/management-api/automations. Append `.md` to any developer docs URL for this format. Index: https://kanorio.com/llms.txt · MCP: https://app.kanorio.com/api/mcp

Base URL: `https://app.kanorio.com/api/v1` · [Errors](https://kanorio.com/en/developers/basics/errors) · [Pagination](https://kanorio.com/en/developers/basics/pagination)

### List automations

`GET /sites/{id}/automations`

- Stability: beta
- Permission: `console:view`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_list`

List the site's built-in automations (what Kanorio sends automatically when store, form and customer-login events happen) with each action's status. Action status: on / off (owner can toggle), locked (always sent), managed_by_destination (webhooks follow endpoint subscriptions; manage with webhooks.*), upgrade_required, coming_soon. The whole list is returned in one page. Errors: FORBIDDEN.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | object[] | yes | — |
| `data[].key` | string | yes | Stable automation key, e.g. order_paid, form_inquiry, customer_login_code. |
| `data[].group` | "forms" \| "store" \| "customers" | yes | — |
| `data[].triggerEventKey` | string | yes | Webhook event key that triggers it, e.g. ec.order.paid. |
| `data[].timing` | "immediate" \| "delayed" | yes | delayed = runs later through the automation engine (e.g. payment due reminder). |
| `data[].actions` | object[] | yes | — |
| `data[].actions[].key` | "customer_email" \| "team_email" \| "team_in_app" \| "team_webhook" | yes | — |
| `data[].actions[].audience` | "customer" \| "team" | yes | — |
| `data[].actions[].channel` | string | yes | — |
| `data[].actions[].status` | "on" \| "off" \| "locked" \| "managed_by_destination" \| "upgrade_required" \| "coming_soon" | yes | — |
| `data[].actions[].minPlan` | string \| null | yes | Plan required when status is upgrade_required. |
| `hasMore` | boolean | yes | True when another page exists; pass nextCursor to fetch it. |
| `nextCursor` | string \| null | yes | Cursor for the next page, or null on the last page. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/automations" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "data": [
    {
      "key": "order_paid",
      "group": "store",
      "triggerEventKey": "ec.order.paid",
      "timing": "immediate",
      "actions": [
        {
          "key": "customer_email",
          "audience": "customer",
          "channel": "email",
          "status": "locked",
          "minPlan": null
        },
        {
          "key": "team_email",
          "audience": "team",
          "channel": "email",
          "status": "on",
          "minPlan": null
        },
        {
          "key": "team_in_app",
          "audience": "team",
          "channel": "in_app",
          "status": "on",
          "minPlan": null
        },
        {
          "key": "team_webhook",
          "audience": "team",
          "channel": "webhook",
          "status": "managed_by_destination",
          "minPlan": null
        }
      ]
    },
    {
      "key": "payment_due_soon",
      "group": "store",
      "triggerEventKey": "ec.checkout.payment_instructions_issued",
      "timing": "delayed",
      "actions": [
        {
          "key": "customer_email",
          "audience": "customer",
          "channel": "email",
          "status": "on",
          "minPlan": null
        }
      ]
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

### Set action enabled

`PATCH /sites/{id}/automations/{automationKey}/actions/{actionKey}`

- Stability: beta
- Permission: `console:view`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_set_action_enabled`

Turn one action of a built-in automation on or off (e.g. stop the shipment email to customers, or stop team emails for form submissions). Requires the manage permission of the automation's group: store → commerce:manage, forms → marketing:manage, customers → customers:manage. Errors: FORBIDDEN, AUTOMATION_NOT_FOUND, ACTION_NOT_FOUND, LOCKED (always-sent action), MANAGED_BY_DESTINATION (webhooks follow endpoint subscriptions), PLAN_REQUIRED, COMING_SOON.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `automationKey` | path | string | yes | Automation key from automations.list, e.g. shipment_shipped. |
| `actionKey` | path | string | yes | One of customer_email, team_email, team_in_app, team_webhook. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `enabled` | boolean | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `automationKey` | string | yes | — |
| `actionKey` | "customer_email" \| "team_email" \| "team_in_app" \| "team_webhook" | yes | — |
| `enabled` | boolean | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `AUTOMATION_NOT_FOUND` | 404 | — |
| `ACTION_NOT_FOUND` | 404 | — |
| `LOCKED` | 403 | always-sent action |
| `MANAGED_BY_DESTINATION` | 422 | webhooks follow endpoint subscriptions |
| `PLAN_REQUIRED` | 403 | — |
| `COMING_SOON` | 422 | — |

```bash
curl -X PATCH "https://app.kanorio.com/api/v1/sites/{id}/automations/{automationKey}/actions/{actionKey}" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}'
```

```json
{
  "automationKey": "shipment_shipped",
  "actionKey": "customer_email",
  "enabled": false
}
```

### Retrieve a catalog

`GET /sites/{id}/automations/catalog`

- Stability: beta
- Permission: `console:view`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_catalog_get`

Resolve the automation catalog for this site: triggers (event key, JSON schema of the payload a flow can read via var(), identity/futureDate/cancelRef fields) and actions (input/output JSON schemas, required permission, plan). Build graphs only from these keys; automations.flows.validate checks every var() path against the trigger schema and ancestor outputs. Custom triggers (custom:<id>) appear here too. Errors: FORBIDDEN.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `triggers` | object[] | yes | — |
| `triggers[].key` | string | yes | — |
| `triggers[].source` | "tenant_event" \| "platform_event" \| "custom" | yes | — |
| `triggers[].group` | string | yes | — |
| `triggers[].title` | string | yes | — |
| `triggers[].description` | string | yes | — |
| `triggers[].payloadSchema` | object | yes | — |
| `triggers[].identity` | object \| null | yes | — |
| `triggers[].identity.field` | string | yes | — |
| `triggers[].identity.type` | "contact" | yes | — |
| `triggers[].futureDateField` | string \| null | yes | — |
| `triggers[].cancelRefField` | string \| null | yes | — |
| `triggers[].filters` | object[] | yes | — |
| `triggers[].filters[].field` | string | yes | — |
| `triggers[].filters[].title` | string | yes | — |
| `triggers[].requiredPermission` | string \| null | yes | — |
| `triggers[].minPlan` | string | yes | — |
| `actions` | object[] | yes | — |
| `actions[].key` | string | yes | — |
| `actions[].group` | string | yes | — |
| `actions[].title` | string | yes | — |
| `actions[].description` | string | yes | — |
| `actions[].inputSchema` | object | yes | — |
| `actions[].outputSchema` | object | yes | — |
| `actions[].permission` | string \| null | yes | — |
| `actions[].minPlan` | string | yes | — |
| `actions[].idempotent` | boolean | yes | — |
| `actions[].compensable` | boolean | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/automations/catalog" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "triggers": [
    {
      "key": "ec.order.paid",
      "source": "tenant_event",
      "group": "store",
      "title": "Order paid",
      "description": "A buyer completed payment for an order.",
      "payloadSchema": {
        "type": "object",
        "properties": {
          "contactId": {
            "type": "string",
            "title": "Contact id",
            "description": "Contact id of the person the event is about."
          },
          "orderId": {
            "type": "string",
            "title": "Order id",
            "description": "Order id."
          }
        }
      },
      "identity": {
        "field": "contactId",
        "type": "contact"
      },
      "futureDateField": null,
      "cancelRefField": null,
      "filters": [],
      "requiredPermission": "commerce:manage",
      "minPlan": "FREE"
    }
  ],
  "actions": [
    {
      "key": "contacts.add_label",
      "group": "contacts",
      "title": "Add label to contact",
      "description": "Add a label (tag) to the contact.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "contactId": {
            "type": "string",
            "title": "Contact id",
            "description": "Contact id."
          },
          "label": {
            "type": "string",
            "title": "Label",
            "description": "Label text."
          }
        },
        "required": [
          "contactId",
          "label"
        ]
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "changed": {
            "type": "boolean",
            "title": "Changed",
            "description": "Whether the label was added."
          }
        }
      },
      "permission": "customers:manage",
      "minPlan": "FREE",
      "idempotent": true,
      "compensable": true
    }
  ]
}
```

### List templates

`GET /sites/{id}/automations/templates`

- Stability: beta
- Permission: `console:view`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_templates_list`

List ready-made automation templates (a pre-built graph plus what the site owner still has to fill in: an external URL, a course id, an amount threshold). Pass template.graph to automations.flows.create after filling the empty required inputs; validate first. Only templates whose trigger is in this site's catalog are returned, all in one page. Errors: FORBIDDEN.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | object[] | yes | — |
| `data[].key` | string | yes | — |
| `data[].category` | "store" \| "forms" \| "courses" \| "headless" | yes | — |
| `data[].title` | string | yes | — |
| `data[].description` | string | yes | — |
| `data[].needs` | "url" \| "deliverable" \| "threshold"[] | yes | — |
| `data[].graph` | object | yes | — |
| `data[].graph.trigger` | object | yes | — |
| `data[].graph.rootActionIds` | string[] | yes | — |
| `data[].graph.actions` | object | yes | — |
| `hasMore` | boolean | yes | True when another page exists; pass nextCursor to fetch it. |
| `nextCursor` | string \| null | yes | Cursor for the next page, or null on the last page. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/automations/templates" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "data": [
    {
      "key": "store_vip_label",
      "category": "store",
      "title": "Label big spenders as VIP",
      "description": "When an order above your threshold is paid, add the VIP label to the buyer.",
      "needs": [
        "threshold"
      ],
      "graph": {
        "trigger": {
          "triggerKey": "ec.order.paid",
          "filters": []
        },
        "rootActionIds": [
          "c1"
        ],
        "actions": {
          "c1": {
            "id": "c1",
            "type": "CONDITION",
            "namespace": "CONDITION-1",
            "conditionInfo": {
              "orExpressionGroups": [
                {
                  "operator": "AND",
                  "booleanExpressions": [
                    "{{numberGt(var(\"amount\");10000)}}"
                  ]
                }
              ],
              "truePostActionIds": [
                "a2"
              ],
              "falsePostActionIds": []
            }
          },
          "a2": {
            "id": "a2",
            "type": "APP_DEFINED",
            "namespace": "contacts_add_label-2",
            "appDefinedInfo": {
              "actionKey": "contacts.add_label",
              "inputMapping": {
                "contactId": "{{var(\"contactId\")}}",
                "label": "VIP"
              },
              "postActionIds": []
            }
          }
        }
      }
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

### List flows

`GET /sites/{id}/automations/flows`

- Stability: beta
- Permission: `console:view`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_flows_list`

List the site's owner-built automations (Automation objects with their published graph, status and revision). Preinstalled scenario automations are listed by automations.list. Errors: FORBIDDEN, INVALID_CURSOR.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `includeArchived` | query | boolean | no | Include archived automations. |
| `limit` | query | integer | no | Maximum number of items to return, 1–100. Defaults to 20. |
| `cursor` | query | string | no | Opaque cursor from the previous page's nextCursor. Omit for the first page. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | object[] | yes | — |
| `data[].id` | string | yes | — |
| `data[].key` | string | yes | db:<id> for owner-built, system:<name> for preinstalled. |
| `data[].name` | string | yes | — |
| `data[].description` | string \| null | yes | — |
| `data[].origin` | "USER" \| "PREINSTALLED" \| "APPLICATION" | yes | — |
| `data[].status` | "ACTIVE" \| "INACTIVE" | yes | — |
| `data[].revision` | integer | yes | Send it back on update (optimistic lock). |
| `data[].archived` | boolean | yes | — |
| `data[].settings` | object \| null | yes | — |
| `data[].settings.readonly` | boolean | no | — |
| `data[].settings.disableDelete` | boolean | no | — |
| `data[].settings.disableStatusChange` | boolean | no | — |
| `data[].settings.actionSettings` | object | no | — |
| `data[].graph` | object | yes | — |
| `data[].graph.trigger` | object | yes | — |
| `data[].graph.rootActionIds` | string[] | yes | — |
| `data[].graph.actions` | object | yes | — |
| `data[].draft` | object \| null | yes | — |
| `data[].draft.trigger` | object | yes | — |
| `data[].draft.rootActionIds` | string[] | yes | — |
| `data[].draft.actions` | object | yes | — |
| `data[].createdAt` | string | yes | — |
| `data[].updatedAt` | string | yes | — |
| `data[].lastTriggeredAt` | string \| null | yes | — |
| `data[].triggerCount` | integer | yes | — |
| `data[].autoPausedAt` | string \| null | yes | When Kanorio turned this automation off automatically (more than 20 runs in the past 7 days with 95% or more failing); null once it is turned back on. |
| `hasMore` | boolean | yes | True when another page exists; pass nextCursor to fetch it. |
| `nextCursor` | string \| null | yes | Cursor for the next page, or null on the last page. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `INVALID_CURSOR` | 400 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/automations/flows" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "data": [
    {
      "id": "auto_example01",
      "key": "db:auto_example01",
      "name": "Label big spenders",
      "description": null,
      "origin": "USER",
      "status": "INACTIVE",
      "revision": 1,
      "archived": false,
      "settings": null,
      "graph": {
        "trigger": {
          "triggerKey": "ec.order.paid",
          "filters": []
        },
        "rootActionIds": [
          "c1"
        ],
        "actions": {
          "c1": {
            "id": "c1",
            "type": "CONDITION",
            "namespace": "CONDITION-1",
            "displayName": "Big order?",
            "conditionInfo": {
              "orExpressionGroups": [
                {
                  "operator": "AND",
                  "booleanExpressions": [
                    "{{numberGt(var(\"amount\");100000)}}"
                  ]
                }
              ],
              "truePostActionIds": [
                "a2"
              ],
              "falsePostActionIds": []
            }
          },
          "a2": {
            "id": "a2",
            "type": "APP_DEFINED",
            "namespace": "contacts_add_label-2",
            "displayName": "Label as VIP",
            "appDefinedInfo": {
              "actionKey": "contacts.add_label",
              "inputMapping": {
                "contactId": "{{var(\"contactId\")}}",
                "label": "vip"
              },
              "postActionIds": []
            }
          }
        }
      },
      "draft": null,
      "createdAt": "2026-10-31T12:00:00.000Z",
      "updatedAt": "2026-10-31T12:00:00.000Z",
      "lastTriggeredAt": null,
      "triggerCount": 0,
      "autoPausedAt": null
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

### Create a flow

`POST /sites/{id}/automations/flows`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_flows_create`

Create an owner-built automation (created INACTIVE; activate with automations.flows.set_status). The graph must be VALID: a trigger from the catalog and a single-parent tree of actions (APP_DEFINED, CONDITION, DELAY, RATE_LIMIT, SET_VARIABLES, SPLIT). Expressions use {{…}} with var("path") and the catalog function list; string literals are URI-encoded. Errors: FORBIDDEN, VALIDATION, INVALID_AUTOMATION (details.validation lists the node errors).

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes | — |
| `description` | string \| null | no | — |
| `graph` | any | yes | — |

**Response** (201)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `key` | string | yes | db:<id> for owner-built, system:<name> for preinstalled. |
| `name` | string | yes | — |
| `description` | string \| null | yes | — |
| `origin` | "USER" \| "PREINSTALLED" \| "APPLICATION" | yes | — |
| `status` | "ACTIVE" \| "INACTIVE" | yes | — |
| `revision` | integer | yes | Send it back on update (optimistic lock). |
| `archived` | boolean | yes | — |
| `settings` | object \| null | yes | — |
| `settings.readonly` | boolean | no | — |
| `settings.disableDelete` | boolean | no | — |
| `settings.disableStatusChange` | boolean | no | — |
| `settings.actionSettings` | object | no | — |
| `settings.actionSettings.readonlyActionIds` | string[] | yes | — |
| `settings.actionSettings.permanentActionIds` | string[] | yes | — |
| `graph` | object | yes | — |
| `graph.trigger` | object | yes | — |
| `graph.trigger.triggerKey` | string | yes | — |
| `graph.trigger.filters` | object[] | yes | — |
| `graph.trigger.scheduledEventOffset` | object | no | — |
| `graph.rootActionIds` | string[] | yes | — |
| `graph.actions` | object | yes | — |
| `draft` | object \| null | yes | — |
| `draft.trigger` | object | yes | — |
| `draft.trigger.triggerKey` | string | yes | — |
| `draft.trigger.filters` | object[] | yes | — |
| `draft.trigger.scheduledEventOffset` | object | no | — |
| `draft.rootActionIds` | string[] | yes | — |
| `draft.actions` | object | yes | — |
| `createdAt` | string | yes | — |
| `updatedAt` | string | yes | — |
| `lastTriggeredAt` | string \| null | yes | — |
| `triggerCount` | integer | yes | — |
| `autoPausedAt` | string \| null | yes | When Kanorio turned this automation off automatically (more than 20 runs in the past 7 days with 95% or more failing); null once it is turned back on. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `VALIDATION` | 400 | — |
| `INVALID_AUTOMATION` | 400 | details.validation lists the node errors |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/automations/flows" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Label big spenders","graph":{"trigger":{"triggerKey":"ec.order.paid","filters":[]},"rootActionIds":["c1"],"actions":{"c1":{"id":"c1","type":"CONDITION","namespace":"CONDITION-1","displayName":"Big order?","conditionInfo":{"orExpressionGroups":[{"operator":"AND","booleanExpressions":["{{numberGt(var(\"amount\");100000)}}"]}],"truePostActionIds":["a2"],"falsePostActionIds":[]}},"a2":{"id":"a2","type":"APP_DEFINED","namespace":"contacts_add_label-2","displayName":"Label as VIP","appDefinedInfo":{"actionKey":"contacts.add_label","inputMapping":{"contactId":"{{var(\"contactId\")}}","label":"vip"},"postActionIds":[]}}}}}'
```

```json
{
  "id": "auto_example01",
  "key": "db:auto_example01",
  "name": "Label big spenders",
  "description": null,
  "origin": "USER",
  "status": "INACTIVE",
  "revision": 1,
  "archived": false,
  "settings": null,
  "graph": {
    "trigger": {
      "triggerKey": "ec.order.paid",
      "filters": []
    },
    "rootActionIds": [
      "c1"
    ],
    "actions": {
      "c1": {
        "id": "c1",
        "type": "CONDITION",
        "namespace": "CONDITION-1",
        "displayName": "Big order?",
        "conditionInfo": {
          "orExpressionGroups": [
            {
              "operator": "AND",
              "booleanExpressions": [
                "{{numberGt(var(\"amount\");100000)}}"
              ]
            }
          ],
          "truePostActionIds": [
            "a2"
          ],
          "falsePostActionIds": []
        }
      },
      "a2": {
        "id": "a2",
        "type": "APP_DEFINED",
        "namespace": "contacts_add_label-2",
        "displayName": "Label as VIP",
        "appDefinedInfo": {
          "actionKey": "contacts.add_label",
          "inputMapping": {
            "contactId": "{{var(\"contactId\")}}",
            "label": "vip"
          },
          "postActionIds": []
        }
      }
    }
  },
  "draft": null,
  "createdAt": "2026-10-31T12:00:00.000Z",
  "updatedAt": "2026-10-31T12:00:00.000Z",
  "lastTriggeredAt": null,
  "triggerCount": 0,
  "autoPausedAt": null
}
```

### Validate a flow

`POST /sites/{id}/automations/flows/validate`

- Stability: beta
- Permission: `console:view`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_flows_validate`

Validate an automation graph without saving it (Wix Validate Automation semantics): structural invariants, trigger/action keys against the catalog, inputMapping types, every var() path against the aggregated schema, condition vocabulary, delays, rate limits, permissions and plan. Returns status VALID / VALID_WITH_WARNINGS / INVALID with per-node errors (nodeId, fieldKey, errorType, severity). WARNING severity never blocks publishing: TRIGGER_SHARED_BY_MANY (more than 10 active automations would use this trigger — consider merging them into one with conditions) and ACTION_MAY_RETRIGGER (an action emits the automation's own trigger event — add a condition so it does not trigger itself). Pass automationId when validating an existing automation so it is not counted against itself. Call it before create, update and activation. Errors: FORBIDDEN.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `graph` | any | yes | Automation graph: { trigger, rootActionIds, actions }. |
| `automationId` | string | no | The automation being edited, if any; excluded when counting other active automations on the same trigger. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | "VALID" \| "VALID_WITH_WARNINGS" \| "INVALID" | yes | — |
| `triggerErrors` | object[] | yes | — |
| `triggerErrors[].nodeId` | string \| null | yes | — |
| `triggerErrors[].fieldKey` | string \| null | yes | — |
| `triggerErrors[].errorType` | string | yes | — |
| `triggerErrors[].severity` | "CRITICAL" \| "WARNING" | yes | — |
| `triggerErrors[].message` | string | yes | — |
| `actionErrors` | object[] | yes | — |
| `actionErrors[].nodeId` | string \| null | yes | — |
| `actionErrors[].fieldKey` | string \| null | yes | — |
| `actionErrors[].errorType` | string | yes | — |
| `actionErrors[].severity` | "CRITICAL" \| "WARNING" | yes | — |
| `actionErrors[].message` | string | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/automations/flows/validate" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"graph":{"trigger":{"triggerKey":"ec.order.paid","filters":[]},"rootActionIds":["c1"],"actions":{"c1":{"id":"c1","type":"CONDITION","namespace":"CONDITION-1","displayName":"Big order?","conditionInfo":{"orExpressionGroups":[{"operator":"AND","booleanExpressions":["{{numberGt(var(\"amount\");100000)}}"]}],"truePostActionIds":["a2"],"falsePostActionIds":[]}},"a2":{"id":"a2","type":"APP_DEFINED","namespace":"contacts_add_label-2","displayName":"Label as VIP","appDefinedInfo":{"actionKey":"contacts.add_label","inputMapping":{"contactId":"{{var(\"contactId\")}}","label":"vip"},"postActionIds":[]}}}}}'
```

```json
{
  "status": "VALID",
  "triggerErrors": [],
  "actionErrors": []
}
```

### Test a flow

`POST /sites/{id}/automations/flows/test`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: heavy (20/minute per token, plus 20/minute per site)
- MCP tool: `automations_flows_test`

Test-run an automation with a sample trigger payload (Wix Test Automation): actions REALLY execute (emails are sent, labels added, HTTP requests fired); delays and rate limits are skipped; the automation need not be ACTIVE. Pass confirm: true to acknowledge the side effects. Poll the run with automations.runs.get or operations.get (op_automation_run_<id>). Limited to 20 test runs per site per hour. Errors: FORBIDDEN, CONFIRM_REQUIRED, AUTOMATION_NOT_FOUND, PAYLOAD_INVALID, RATE_LIMITED.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `automationKey` | string | yes | db:<id> or system:<name>. |
| `payload` | object | yes | Trigger payload matching the trigger's schema. |
| `confirm` | boolean | yes | Must be true: the test executes real actions. |

**Response** (202)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `runId` | string | yes | — |
| `operationId` | string | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `CONFIRM_REQUIRED` | 400 | — |
| `AUTOMATION_NOT_FOUND` | 404 | — |
| `PAYLOAD_INVALID` | 422 | — |
| `RATE_LIMITED` | 429 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/automations/flows/test" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"automationKey":"db:auto_example01","payload":{"contactId":"cnt_01","orderId":"ord_01","amount":250000,"currency":"USD","itemCount":1},"confirm":true}'
```

```json
{
  "runId": "run_example02",
  "operationId": "op_automation_run_run_example02"
}
```

### Retrieve a flow

`GET /sites/{id}/automations/flows/{automationId}`

- Stability: beta
- Permission: `console:view`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_flows_get`

Get one owner-built automation with its graph, draft and revision. Errors: FORBIDDEN, AUTOMATION_NOT_FOUND.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `automationId` | path | string | yes | Automation id (from automations.flows.list). |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `key` | string | yes | db:<id> for owner-built, system:<name> for preinstalled. |
| `name` | string | yes | — |
| `description` | string \| null | yes | — |
| `origin` | "USER" \| "PREINSTALLED" \| "APPLICATION" | yes | — |
| `status` | "ACTIVE" \| "INACTIVE" | yes | — |
| `revision` | integer | yes | Send it back on update (optimistic lock). |
| `archived` | boolean | yes | — |
| `settings` | object \| null | yes | — |
| `settings.readonly` | boolean | no | — |
| `settings.disableDelete` | boolean | no | — |
| `settings.disableStatusChange` | boolean | no | — |
| `settings.actionSettings` | object | no | — |
| `settings.actionSettings.readonlyActionIds` | string[] | yes | — |
| `settings.actionSettings.permanentActionIds` | string[] | yes | — |
| `graph` | object | yes | — |
| `graph.trigger` | object | yes | — |
| `graph.trigger.triggerKey` | string | yes | — |
| `graph.trigger.filters` | object[] | yes | — |
| `graph.trigger.scheduledEventOffset` | object | no | — |
| `graph.rootActionIds` | string[] | yes | — |
| `graph.actions` | object | yes | — |
| `draft` | object \| null | yes | — |
| `draft.trigger` | object | yes | — |
| `draft.trigger.triggerKey` | string | yes | — |
| `draft.trigger.filters` | object[] | yes | — |
| `draft.trigger.scheduledEventOffset` | object | no | — |
| `draft.rootActionIds` | string[] | yes | — |
| `draft.actions` | object | yes | — |
| `createdAt` | string | yes | — |
| `updatedAt` | string | yes | — |
| `lastTriggeredAt` | string \| null | yes | — |
| `triggerCount` | integer | yes | — |
| `autoPausedAt` | string \| null | yes | When Kanorio turned this automation off automatically (more than 20 runs in the past 7 days with 95% or more failing); null once it is turned back on. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `AUTOMATION_NOT_FOUND` | 404 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/automations/flows/{automationId}" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "id": "auto_example01",
  "key": "db:auto_example01",
  "name": "Label big spenders",
  "description": null,
  "origin": "USER",
  "status": "INACTIVE",
  "revision": 1,
  "archived": false,
  "settings": null,
  "graph": {
    "trigger": {
      "triggerKey": "ec.order.paid",
      "filters": []
    },
    "rootActionIds": [
      "c1"
    ],
    "actions": {
      "c1": {
        "id": "c1",
        "type": "CONDITION",
        "namespace": "CONDITION-1",
        "displayName": "Big order?",
        "conditionInfo": {
          "orExpressionGroups": [
            {
              "operator": "AND",
              "booleanExpressions": [
                "{{numberGt(var(\"amount\");100000)}}"
              ]
            }
          ],
          "truePostActionIds": [
            "a2"
          ],
          "falsePostActionIds": []
        }
      },
      "a2": {
        "id": "a2",
        "type": "APP_DEFINED",
        "namespace": "contacts_add_label-2",
        "displayName": "Label as VIP",
        "appDefinedInfo": {
          "actionKey": "contacts.add_label",
          "inputMapping": {
            "contactId": "{{var(\"contactId\")}}",
            "label": "vip"
          },
          "postActionIds": []
        }
      }
    }
  },
  "draft": null,
  "createdAt": "2026-10-31T12:00:00.000Z",
  "updatedAt": "2026-10-31T12:00:00.000Z",
  "lastTriggeredAt": null,
  "triggerCount": 0,
  "autoPausedAt": null
}
```

### Update a flow

`PATCH /sites/{id}/automations/flows/{automationId}`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_flows_update`

Update name, description or graph of an owner-built automation. Send the current revision (optimistic lock; REVISION_CONFLICT otherwise). A new graph must be VALID; updating an ACTIVE automation takes effect immediately. Errors: FORBIDDEN, AUTOMATION_NOT_FOUND, REVISION_CONFLICT, INVALID_AUTOMATION, LOCKED.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `automationId` | path | string | yes | — |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `revision` | integer | yes | — |
| `name` | string | no | — |
| `description` | string \| null | no | — |
| `graph` | any | no | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `key` | string | yes | db:<id> for owner-built, system:<name> for preinstalled. |
| `name` | string | yes | — |
| `description` | string \| null | yes | — |
| `origin` | "USER" \| "PREINSTALLED" \| "APPLICATION" | yes | — |
| `status` | "ACTIVE" \| "INACTIVE" | yes | — |
| `revision` | integer | yes | Send it back on update (optimistic lock). |
| `archived` | boolean | yes | — |
| `settings` | object \| null | yes | — |
| `settings.readonly` | boolean | no | — |
| `settings.disableDelete` | boolean | no | — |
| `settings.disableStatusChange` | boolean | no | — |
| `settings.actionSettings` | object | no | — |
| `settings.actionSettings.readonlyActionIds` | string[] | yes | — |
| `settings.actionSettings.permanentActionIds` | string[] | yes | — |
| `graph` | object | yes | — |
| `graph.trigger` | object | yes | — |
| `graph.trigger.triggerKey` | string | yes | — |
| `graph.trigger.filters` | object[] | yes | — |
| `graph.trigger.scheduledEventOffset` | object | no | — |
| `graph.rootActionIds` | string[] | yes | — |
| `graph.actions` | object | yes | — |
| `draft` | object \| null | yes | — |
| `draft.trigger` | object | yes | — |
| `draft.trigger.triggerKey` | string | yes | — |
| `draft.trigger.filters` | object[] | yes | — |
| `draft.trigger.scheduledEventOffset` | object | no | — |
| `draft.rootActionIds` | string[] | yes | — |
| `draft.actions` | object | yes | — |
| `createdAt` | string | yes | — |
| `updatedAt` | string | yes | — |
| `lastTriggeredAt` | string \| null | yes | — |
| `triggerCount` | integer | yes | — |
| `autoPausedAt` | string \| null | yes | When Kanorio turned this automation off automatically (more than 20 runs in the past 7 days with 95% or more failing); null once it is turned back on. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `AUTOMATION_NOT_FOUND` | 404 | — |
| `REVISION_CONFLICT` | 422 | — |
| `INVALID_AUTOMATION` | 400 | — |
| `LOCKED` | 403 | — |

```bash
curl -X PATCH "https://app.kanorio.com/api/v1/sites/{id}/automations/flows/{automationId}" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"revision":1,"name":"Label VIP buyers"}'
```

```json
{
  "id": "auto_example01",
  "key": "db:auto_example01",
  "name": "Label VIP buyers",
  "description": null,
  "origin": "USER",
  "status": "INACTIVE",
  "revision": 2,
  "archived": false,
  "settings": null,
  "graph": {
    "trigger": {
      "triggerKey": "ec.order.paid",
      "filters": []
    },
    "rootActionIds": [
      "c1"
    ],
    "actions": {
      "c1": {
        "id": "c1",
        "type": "CONDITION",
        "namespace": "CONDITION-1",
        "displayName": "Big order?",
        "conditionInfo": {
          "orExpressionGroups": [
            {
              "operator": "AND",
              "booleanExpressions": [
                "{{numberGt(var(\"amount\");100000)}}"
              ]
            }
          ],
          "truePostActionIds": [
            "a2"
          ],
          "falsePostActionIds": []
        }
      },
      "a2": {
        "id": "a2",
        "type": "APP_DEFINED",
        "namespace": "contacts_add_label-2",
        "displayName": "Label as VIP",
        "appDefinedInfo": {
          "actionKey": "contacts.add_label",
          "inputMapping": {
            "contactId": "{{var(\"contactId\")}}",
            "label": "vip"
          },
          "postActionIds": []
        }
      }
    }
  },
  "draft": null,
  "createdAt": "2026-10-31T12:00:00.000Z",
  "updatedAt": "2026-10-31T12:00:00.000Z",
  "lastTriggeredAt": null,
  "triggerCount": 0,
  "autoPausedAt": null
}
```

### Delete a flow

`DELETE /sites/{id}/automations/flows/{automationId}`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_flows_delete`

Delete an owner-built automation (preinstalled ones cannot be deleted; turn them off instead). Run history is kept. Errors: FORBIDDEN, AUTOMATION_NOT_FOUND, LOCKED.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `automationId` | path | string | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `deleted` | true | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `AUTOMATION_NOT_FOUND` | 404 | — |
| `LOCKED` | 403 | — |

```bash
curl -X DELETE "https://app.kanorio.com/api/v1/sites/{id}/automations/flows/{automationId}" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "deleted": true
}
```

### Set status

`POST /sites/{id}/automations/flows/{automationId}/status`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_flows_set_status`

Turn an owner-built automation on or off. Activation requires a VALID graph and counts against the site's active-automation limit (same on every plan); deactivation never fails on validation. Errors: FORBIDDEN, AUTOMATION_NOT_FOUND, INVALID_AUTOMATION, PLAN_LIMIT_REACHED, LOCKED.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `automationId` | path | string | yes | — |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | "ACTIVE" \| "INACTIVE" | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `key` | string | yes | db:<id> for owner-built, system:<name> for preinstalled. |
| `name` | string | yes | — |
| `description` | string \| null | yes | — |
| `origin` | "USER" \| "PREINSTALLED" \| "APPLICATION" | yes | — |
| `status` | "ACTIVE" \| "INACTIVE" | yes | — |
| `revision` | integer | yes | Send it back on update (optimistic lock). |
| `archived` | boolean | yes | — |
| `settings` | object \| null | yes | — |
| `settings.readonly` | boolean | no | — |
| `settings.disableDelete` | boolean | no | — |
| `settings.disableStatusChange` | boolean | no | — |
| `settings.actionSettings` | object | no | — |
| `settings.actionSettings.readonlyActionIds` | string[] | yes | — |
| `settings.actionSettings.permanentActionIds` | string[] | yes | — |
| `graph` | object | yes | — |
| `graph.trigger` | object | yes | — |
| `graph.trigger.triggerKey` | string | yes | — |
| `graph.trigger.filters` | object[] | yes | — |
| `graph.trigger.scheduledEventOffset` | object | no | — |
| `graph.rootActionIds` | string[] | yes | — |
| `graph.actions` | object | yes | — |
| `draft` | object \| null | yes | — |
| `draft.trigger` | object | yes | — |
| `draft.trigger.triggerKey` | string | yes | — |
| `draft.trigger.filters` | object[] | yes | — |
| `draft.trigger.scheduledEventOffset` | object | no | — |
| `draft.rootActionIds` | string[] | yes | — |
| `draft.actions` | object | yes | — |
| `createdAt` | string | yes | — |
| `updatedAt` | string | yes | — |
| `lastTriggeredAt` | string \| null | yes | — |
| `triggerCount` | integer | yes | — |
| `autoPausedAt` | string \| null | yes | When Kanorio turned this automation off automatically (more than 20 runs in the past 7 days with 95% or more failing); null once it is turned back on. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `AUTOMATION_NOT_FOUND` | 404 | — |
| `INVALID_AUTOMATION` | 400 | — |
| `PLAN_LIMIT_REACHED` | 403 | — |
| `LOCKED` | 403 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/automations/flows/{automationId}/status" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status":"ACTIVE"}'
```

```json
{
  "id": "auto_example01",
  "key": "db:auto_example01",
  "name": "Label big spenders",
  "description": null,
  "origin": "USER",
  "status": "ACTIVE",
  "revision": 2,
  "archived": false,
  "settings": null,
  "graph": {
    "trigger": {
      "triggerKey": "ec.order.paid",
      "filters": []
    },
    "rootActionIds": [
      "c1"
    ],
    "actions": {
      "c1": {
        "id": "c1",
        "type": "CONDITION",
        "namespace": "CONDITION-1",
        "displayName": "Big order?",
        "conditionInfo": {
          "orExpressionGroups": [
            {
              "operator": "AND",
              "booleanExpressions": [
                "{{numberGt(var(\"amount\");100000)}}"
              ]
            }
          ],
          "truePostActionIds": [
            "a2"
          ],
          "falsePostActionIds": []
        }
      },
      "a2": {
        "id": "a2",
        "type": "APP_DEFINED",
        "namespace": "contacts_add_label-2",
        "displayName": "Label as VIP",
        "appDefinedInfo": {
          "actionKey": "contacts.add_label",
          "inputMapping": {
            "contactId": "{{var(\"contactId\")}}",
            "label": "vip"
          },
          "postActionIds": []
        }
      }
    }
  },
  "draft": null,
  "createdAt": "2026-10-31T12:00:00.000Z",
  "updatedAt": "2026-10-31T12:00:00.000Z",
  "lastTriggeredAt": null,
  "triggerCount": 0,
  "autoPausedAt": null
}
```

### Archive a flow

`POST /sites/{id}/automations/flows/{automationId}/archive`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_flows_archive`

Archive (deactivates and hides from the list; runs are kept) or unarchive an owner-built automation. Errors: FORBIDDEN, AUTOMATION_NOT_FOUND, LOCKED.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `automationId` | path | string | yes | — |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `archived` | boolean | no | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `key` | string | yes | db:<id> for owner-built, system:<name> for preinstalled. |
| `name` | string | yes | — |
| `description` | string \| null | yes | — |
| `origin` | "USER" \| "PREINSTALLED" \| "APPLICATION" | yes | — |
| `status` | "ACTIVE" \| "INACTIVE" | yes | — |
| `revision` | integer | yes | Send it back on update (optimistic lock). |
| `archived` | boolean | yes | — |
| `settings` | object \| null | yes | — |
| `settings.readonly` | boolean | no | — |
| `settings.disableDelete` | boolean | no | — |
| `settings.disableStatusChange` | boolean | no | — |
| `settings.actionSettings` | object | no | — |
| `settings.actionSettings.readonlyActionIds` | string[] | yes | — |
| `settings.actionSettings.permanentActionIds` | string[] | yes | — |
| `graph` | object | yes | — |
| `graph.trigger` | object | yes | — |
| `graph.trigger.triggerKey` | string | yes | — |
| `graph.trigger.filters` | object[] | yes | — |
| `graph.trigger.scheduledEventOffset` | object | no | — |
| `graph.rootActionIds` | string[] | yes | — |
| `graph.actions` | object | yes | — |
| `draft` | object \| null | yes | — |
| `draft.trigger` | object | yes | — |
| `draft.trigger.triggerKey` | string | yes | — |
| `draft.trigger.filters` | object[] | yes | — |
| `draft.trigger.scheduledEventOffset` | object | no | — |
| `draft.rootActionIds` | string[] | yes | — |
| `draft.actions` | object | yes | — |
| `createdAt` | string | yes | — |
| `updatedAt` | string | yes | — |
| `lastTriggeredAt` | string \| null | yes | — |
| `triggerCount` | integer | yes | — |
| `autoPausedAt` | string \| null | yes | When Kanorio turned this automation off automatically (more than 20 runs in the past 7 days with 95% or more failing); null once it is turned back on. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `AUTOMATION_NOT_FOUND` | 404 | — |
| `LOCKED` | 403 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/automations/flows/{automationId}/archive" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"archived":true}'
```

```json
{
  "id": "auto_example01",
  "key": "db:auto_example01",
  "name": "Label big spenders",
  "description": null,
  "origin": "USER",
  "status": "INACTIVE",
  "revision": 2,
  "archived": true,
  "settings": null,
  "graph": {
    "trigger": {
      "triggerKey": "ec.order.paid",
      "filters": []
    },
    "rootActionIds": [
      "c1"
    ],
    "actions": {
      "c1": {
        "id": "c1",
        "type": "CONDITION",
        "namespace": "CONDITION-1",
        "displayName": "Big order?",
        "conditionInfo": {
          "orExpressionGroups": [
            {
              "operator": "AND",
              "booleanExpressions": [
                "{{numberGt(var(\"amount\");100000)}}"
              ]
            }
          ],
          "truePostActionIds": [
            "a2"
          ],
          "falsePostActionIds": []
        }
      },
      "a2": {
        "id": "a2",
        "type": "APP_DEFINED",
        "namespace": "contacts_add_label-2",
        "displayName": "Label as VIP",
        "appDefinedInfo": {
          "actionKey": "contacts.add_label",
          "inputMapping": {
            "contactId": "{{var(\"contactId\")}}",
            "label": "vip"
          },
          "postActionIds": []
        }
      }
    }
  },
  "draft": null,
  "createdAt": "2026-10-31T12:00:00.000Z",
  "updatedAt": "2026-10-31T12:00:00.000Z",
  "lastTriggeredAt": null,
  "triggerCount": 0,
  "autoPausedAt": null
}
```

### List runs

`GET /sites/{id}/automations/runs`

- Stability: beta
- Permission: `console:view`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_runs_list`

List automation runs (newest first) with status PENDING / WAITING (delay) / RUNNING / DONE / CANCELED / FAILED / SKIPPED (not run: the trigger was blocked by loop protection — causation depth over 16 — or by the trigger rate limit; lastError says which, and one record covers each 30-second window per automation and reason); filter by automationKey, status or contactId. Errors: FORBIDDEN.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `automationKey` | query | string | no | — |
| `status` | query | "PENDING" \| "WAITING" \| "RUNNING" \| "DONE" \| "CANCELED" \| "FAILED" \| "SKIPPED" | no | — |
| `contactId` | query | string | no | — |
| `limit` | query | integer | no | Maximum number of items to return, 1–100. Defaults to 20. |
| `cursor` | query | string | no | Opaque cursor from the previous page's nextCursor. Omit for the first page. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | object[] | yes | — |
| `data[].id` | string | yes | — |
| `data[].automationKey` | string | yes | — |
| `data[].automationId` | string \| null | yes | — |
| `data[].revision` | integer \| null | yes | — |
| `data[].status` | "PENDING" \| "WAITING" \| "RUNNING" \| "DONE" \| "CANCELED" \| "FAILED" \| "SKIPPED" | yes | — |
| `data[].initiation` | "TRIGGER" \| "MANUAL" \| "RERUN" \| "TEST" | yes | — |
| `data[].triggerEventKey` | string | yes | — |
| `data[].triggerEventId` | string | yes | — |
| `data[].contactId` | string \| null | yes | — |
| `data[].subjectType` | string \| null | yes | — |
| `data[].subjectId` | string \| null | yes | — |
| `data[].currentNodeId` | string \| null | yes | — |
| `data[].runAt` | string | yes | — |
| `data[].waitUntil` | string \| null | yes | — |
| `data[].attempts` | integer | yes | — |
| `data[].lastError` | string \| null | yes | — |
| `data[].causationDepth` | integer | yes | — |
| `data[].parentRunId` | string \| null | yes | — |
| `data[].messageLogId` | string \| null | yes | — |
| `data[].createdAt` | string | yes | — |
| `data[].updatedAt` | string | yes | — |
| `data[].steps` | object[] | no | — |
| `data[].steps[].id` | string | yes | — |
| `data[].steps[].nodeId` | string | yes | — |
| `data[].steps[].type` | string | yes | — |
| `data[].steps[].namespace` | string | yes | — |
| `data[].steps[].status` | "PENDING" \| "RUNNING" \| "SUCCEEDED" \| "FAILED" \| "SKIPPED" \| "CANCELED" | yes | — |
| `data[].steps[].attempts` | integer | yes | — |
| `data[].steps[].startedAt` | string \| null | yes | — |
| `data[].steps[].finishedAt` | string \| null | yes | — |
| `data[].steps[].input` | any | yes | — |
| `data[].steps[].output` | any | yes | — |
| `data[].steps[].error` | string \| null | yes | — |
| `data[].steps[].compensatedAt` | string \| null | yes | — |
| `data[].trigger` | object \| null | no | — |
| `data[].trigger.key` | string | yes | — |
| `data[].trigger.occurredAt` | string | yes | — |
| `data[].trigger.payload` | object | yes | — |
| `data[].nodes` | object | no | Step nodeId → node of the automation's current graph (type, owner-given step name, action key). Steps from an older revision may be missing. |
| `data[].automationName` | string \| null | no | Current name of the automation (owner-built); null for platform defaults or deleted automations. |
| `hasMore` | boolean | yes | True when another page exists; pass nextCursor to fetch it. |
| `nextCursor` | string \| null | yes | Cursor for the next page, or null on the last page. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/automations/runs?status=DONE&limit=20" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "data": [
    {
      "id": "run_example01",
      "automationKey": "db:auto_example01",
      "automationId": "auto_example01",
      "revision": 1,
      "status": "DONE",
      "initiation": "TRIGGER",
      "triggerEventKey": "ec.order.paid",
      "triggerEventId": "ord_01",
      "contactId": "cnt_01",
      "subjectType": "order",
      "subjectId": "ord_01",
      "currentNodeId": null,
      "runAt": "2026-10-31T12:00:00.000Z",
      "waitUntil": null,
      "attempts": 0,
      "lastError": null,
      "causationDepth": 0,
      "parentRunId": null,
      "messageLogId": null,
      "createdAt": "2026-10-31T12:00:00.000Z",
      "updatedAt": "2026-10-31T12:00:02.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

### Retrieve a run

`GET /sites/{id}/automations/runs/{runId}`

- Stability: beta
- Permission: `console:view`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_runs_get`

Get one run with its per-step records (status, attempts, input as sent, output readable by later steps, error, compensation) and the trigger payload. Secrets never appear (inputs hold placeholders). Errors: FORBIDDEN, RUN_NOT_FOUND.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `runId` | path | string | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `automationKey` | string | yes | — |
| `automationId` | string \| null | yes | — |
| `revision` | integer \| null | yes | — |
| `status` | "PENDING" \| "WAITING" \| "RUNNING" \| "DONE" \| "CANCELED" \| "FAILED" \| "SKIPPED" | yes | — |
| `initiation` | "TRIGGER" \| "MANUAL" \| "RERUN" \| "TEST" | yes | — |
| `triggerEventKey` | string | yes | — |
| `triggerEventId` | string | yes | — |
| `contactId` | string \| null | yes | — |
| `subjectType` | string \| null | yes | — |
| `subjectId` | string \| null | yes | — |
| `currentNodeId` | string \| null | yes | — |
| `runAt` | string | yes | — |
| `waitUntil` | string \| null | yes | — |
| `attempts` | integer | yes | — |
| `lastError` | string \| null | yes | — |
| `causationDepth` | integer | yes | — |
| `parentRunId` | string \| null | yes | — |
| `messageLogId` | string \| null | yes | — |
| `createdAt` | string | yes | — |
| `updatedAt` | string | yes | — |
| `steps` | object[] | no | — |
| `steps[].id` | string | yes | — |
| `steps[].nodeId` | string | yes | — |
| `steps[].type` | string | yes | — |
| `steps[].namespace` | string | yes | — |
| `steps[].status` | "PENDING" \| "RUNNING" \| "SUCCEEDED" \| "FAILED" \| "SKIPPED" \| "CANCELED" | yes | — |
| `steps[].attempts` | integer | yes | — |
| `steps[].startedAt` | string \| null | yes | — |
| `steps[].finishedAt` | string \| null | yes | — |
| `steps[].input` | any | yes | — |
| `steps[].output` | any | yes | — |
| `steps[].error` | string \| null | yes | — |
| `steps[].compensatedAt` | string \| null | yes | — |
| `trigger` | object \| null | no | — |
| `trigger.key` | string | yes | — |
| `trigger.occurredAt` | string | yes | — |
| `trigger.payload` | object | yes | — |
| `nodes` | object | no | Step nodeId → node of the automation's current graph (type, owner-given step name, action key). Steps from an older revision may be missing. |
| `automationName` | string \| null | no | Current name of the automation (owner-built); null for platform defaults or deleted automations. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `RUN_NOT_FOUND` | 404 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/automations/runs/{runId}" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "id": "run_example01",
  "automationKey": "db:auto_example01",
  "automationId": "auto_example01",
  "revision": 1,
  "status": "DONE",
  "initiation": "TRIGGER",
  "triggerEventKey": "ec.order.paid",
  "triggerEventId": "ord_01",
  "contactId": "cnt_01",
  "subjectType": "order",
  "subjectId": "ord_01",
  "currentNodeId": null,
  "runAt": "2026-10-31T12:00:00.000Z",
  "waitUntil": null,
  "attempts": 0,
  "lastError": null,
  "causationDepth": 0,
  "parentRunId": null,
  "messageLogId": null,
  "createdAt": "2026-10-31T12:00:00.000Z",
  "updatedAt": "2026-10-31T12:00:02.000Z",
  "steps": [
    {
      "id": "step_01",
      "nodeId": "c1",
      "type": "CONDITION",
      "namespace": "CONDITION-1",
      "status": "SUCCEEDED",
      "attempts": 0,
      "startedAt": null,
      "finishedAt": "2026-10-31T12:00:01.000Z",
      "input": null,
      "output": {
        "result": true
      },
      "error": null,
      "compensatedAt": null
    },
    {
      "id": "step_02",
      "nodeId": "a2",
      "type": "APP_DEFINED",
      "namespace": "contacts_add_label-2",
      "status": "SUCCEEDED",
      "attempts": 1,
      "startedAt": "2026-10-31T12:00:01.000Z",
      "finishedAt": "2026-10-31T12:00:02.000Z",
      "input": {
        "contactId": "cnt_01",
        "label": "vip"
      },
      "output": {
        "contactId": "cnt_01",
        "label": "vip",
        "changed": true
      },
      "error": null,
      "compensatedAt": null
    }
  ],
  "trigger": {
    "key": "ec.order.paid",
    "occurredAt": "2026-10-31T12:00:00.000Z",
    "payload": {
      "contactId": "cnt_01",
      "orderId": "ord_01",
      "amount": 250000
    }
  }
}
```

### Run rerun

`POST /sites/{id}/automations/runs/{runId}/rerun`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_runs_rerun`

Rerun a finished run (Wix Rerun Activation): from "failed" continues at the failed step reusing successful steps' outputs; from "start" replays the whole flow with the same trigger payload. Returns the new run id. Errors: FORBIDDEN, RUN_NOT_FOUND, RUN_NOT_TERMINAL.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `runId` | path | string | yes | — |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `from` | "failed" \| "start" | no | — |

**Response** (202)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `runId` | string | yes | — |
| `operationId` | string | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `RUN_NOT_FOUND` | 404 | — |
| `RUN_NOT_TERMINAL` | 422 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/automations/runs/{runId}/rerun" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"from":"failed"}'
```

```json
{
  "runId": "run_example03",
  "operationId": "op_automation_run_run_example03"
}
```

### Report events

`POST /sites/{id}/automations/events`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: default (120/minute per token)
- Idempotent: send `Idempotency-Key`
- MCP tool: `automations_events_report`

Report an event to start automations (Wix Report Event): triggerKey is a catalog trigger or a custom trigger (custom:<id>); payload must match the trigger's schema (missing required fields → PAYLOAD_INVALID, nothing starts). idempotencyKey (or the Idempotency-Key header) deduplicates; externalEntityId lets automations.events.cancel withdraw waiting runs; scheduledAt starts the flow at that time. Errors: FORBIDDEN, TRIGGER_NOT_FOUND, TRIGGER_DISABLED, PAYLOAD_INVALID.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `Idempotency-Key` | header | string | no | Client-chosen key (1-200 chars). Retrying with the same key and the same body returns the original result; the same key with a different body is rejected with IDEMPOTENCY_CONFLICT (409). Keys expire after 24 hours. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `triggerKey` | string | yes | — |
| `payload` | object | yes | — |
| `externalEntityId` | string | no | — |
| `idempotencyKey` | string | no | — |
| `scheduledAt` | string | no | ISO 8601; start the automations at this time instead of now. |

**Response** (202)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `eventId` | string | yes | Outbox id; the runs appear in automations.runs.list once the worker delivers it. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `TRIGGER_NOT_FOUND` | 404 | — |
| `TRIGGER_DISABLED` | 422 | — |
| `PAYLOAD_INVALID` | 422 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/automations/events" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"triggerKey":"custom:trg_example01","payload":{"orderRef":"ERP-1001","total":129.5},"externalEntityId":"ERP-1001"}'
```

```json
{
  "eventId": "obx_example01"
}
```

### Cancel an event

`POST /sites/{id}/automations/events/cancel`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_events_cancel`

Cancel waiting runs started by a trigger for the given externalEntityId (Wix Cancel Event), e.g. the order was paid so the reminder must not go out. Errors: FORBIDDEN, TRIGGER_NOT_FOUND.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `triggerKey` | string | yes | — |
| `externalEntityId` | string | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `canceled` | integer | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `TRIGGER_NOT_FOUND` | 404 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/automations/events/cancel" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"triggerKey":"custom:trg_example01","externalEntityId":"ERP-1001"}'
```

```json
{
  "canceled": 1
}
```

### List triggers

`GET /sites/{id}/automations/triggers`

- Stability: beta
- Permission: `console:view`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_triggers_list`

List the site's custom triggers (webhook endpoints external systems post to). Tokens and signing secrets are never returned; only the token prefix. The whole list is returned in one page. Errors: FORBIDDEN.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | object[] | yes | — |
| `data[].id` | string | yes | — |
| `data[].key` | string | yes | — |
| `data[].name` | string | yes | — |
| `data[].description` | string \| null | yes | — |
| `data[].payloadSchema` | object | yes | — |
| `data[].identityField` | string \| null | yes | — |
| `data[].futureDateField` | string \| null | yes | — |
| `data[].cancelRefField` | string \| null | yes | — |
| `data[].endpointTokenPrefix` | string | yes | — |
| `data[].requireSignature` | boolean | yes | — |
| `data[].status` | "ACTIVE" \| "DISABLED" | yes | — |
| `data[].lastReceivedAt` | string \| null | yes | — |
| `data[].receivedCount` | integer | yes | — |
| `data[].previousSecretExpiresAt` | string \| null | yes | — |
| `data[].createdAt` | string | yes | — |
| `hasMore` | boolean | yes | True when another page exists; pass nextCursor to fetch it. |
| `nextCursor` | string \| null | yes | Cursor for the next page, or null on the last page. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/automations/triggers" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "data": [
    {
      "id": "trg_example01",
      "key": "custom:trg_example01",
      "name": "ERP order synced",
      "description": null,
      "payloadSchema": {
        "type": "object",
        "properties": {
          "orderRef": {
            "type": "string",
            "title": "Order ref",
            "description": "ERP order reference"
          },
          "total": {
            "type": "number",
            "title": "Total",
            "description": "Order total"
          }
        },
        "required": [
          "orderRef",
          "total"
        ]
      },
      "identityField": null,
      "futureDateField": null,
      "cancelRefField": "orderRef",
      "endpointTokenPrefix": "aut_Ab12Cd34",
      "requireSignature": true,
      "status": "ACTIVE",
      "lastReceivedAt": null,
      "receivedCount": 0,
      "previousSecretExpiresAt": null,
      "createdAt": "2026-10-31T12:00:00.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

### Create a trigger

`POST /sites/{id}/automations/triggers`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_triggers_create`

Create a custom trigger: give a name and a payload schema (or a sample payload). Returns the endpoint path (POST /api/webhooks/automations/<token>) and the Standard Webhooks signing secret (whsec_…) ONCE; store them now. Senders sign webhook-id.webhook-timestamp.body with HMAC-SHA256 and send webhook-id / webhook-timestamp / webhook-signature headers (±5 minutes). Errors: FORBIDDEN, VALIDATION, INVALID_SCHEMA, PLAN_LIMIT_REACHED.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes | — |
| `description` | string \| null | no | — |
| `payloadSchema` | object | no | Catalog JSON schema subset (object with described properties). |
| `samplePayload` | object | no | Alternative to payloadSchema: a sample JSON the schema is inferred from. |
| `identityField` | string \| null | no | — |
| `futureDateField` | string \| null | no | — |
| `cancelRefField` | string \| null | no | — |
| `requireSignature` | boolean | no | — |

**Response** (201)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `trigger` | object | yes | — |
| `trigger.id` | string | yes | — |
| `trigger.key` | string | yes | — |
| `trigger.name` | string | yes | — |
| `trigger.description` | string \| null | yes | — |
| `trigger.payloadSchema` | object | yes | — |
| `trigger.identityField` | string \| null | yes | — |
| `trigger.futureDateField` | string \| null | yes | — |
| `trigger.cancelRefField` | string \| null | yes | — |
| `trigger.endpointTokenPrefix` | string | yes | — |
| `trigger.requireSignature` | boolean | yes | — |
| `trigger.status` | "ACTIVE" \| "DISABLED" | yes | — |
| `trigger.lastReceivedAt` | string \| null | yes | — |
| `trigger.receivedCount` | integer | yes | — |
| `trigger.previousSecretExpiresAt` | string \| null | yes | — |
| `trigger.createdAt` | string | yes | — |
| `endpointToken` | string | yes | — |
| `endpointPath` | string | yes | — |
| `signingSecret` | string \| null | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `VALIDATION` | 400 | — |
| `INVALID_SCHEMA` | 400 | — |
| `PLAN_LIMIT_REACHED` | 403 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/automations/triggers" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"ERP order synced","samplePayload":{"orderRef":"ERP-1001","total":129.5},"cancelRefField":"orderRef"}'
```

```json
{
  "trigger": {
    "id": "trg_example01",
    "key": "custom:trg_example01",
    "name": "ERP order synced",
    "description": null,
    "payloadSchema": {
      "type": "object",
      "properties": {
        "orderRef": {
          "type": "string",
          "title": "Order ref",
          "description": "ERP order reference"
        },
        "total": {
          "type": "number",
          "title": "Total",
          "description": "Order total"
        }
      },
      "required": [
        "orderRef",
        "total"
      ]
    },
    "identityField": null,
    "futureDateField": null,
    "cancelRefField": "orderRef",
    "endpointTokenPrefix": "aut_Ab12Cd34",
    "requireSignature": true,
    "status": "ACTIVE",
    "lastReceivedAt": null,
    "receivedCount": 0,
    "previousSecretExpiresAt": null,
    "createdAt": "2026-10-31T12:00:00.000Z"
  },
  "endpointToken": "aut_Ab12Cd34…",
  "endpointPath": "/api/webhooks/automations/aut_Ab12Cd34…",
  "signingSecret": "whsec_…"
}
```

### Update a trigger

`PATCH /sites/{id}/automations/triggers/{triggerId}`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_triggers_update`

Update a custom trigger (name, schema, identity/futureDate/cancelRef fields, requireSignature, status ACTIVE/DISABLED; a DISABLED endpoint answers 410). Errors: FORBIDDEN, TRIGGER_NOT_FOUND, VALIDATION, INVALID_SCHEMA.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `triggerId` | path | string | yes | — |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no | — |
| `description` | string \| null | no | — |
| `payloadSchema` | object | no | Catalog JSON schema subset (object with described properties). |
| `samplePayload` | object | no | Alternative to payloadSchema: a sample JSON the schema is inferred from. |
| `identityField` | string \| null | no | — |
| `futureDateField` | string \| null | no | — |
| `cancelRefField` | string \| null | no | — |
| `requireSignature` | boolean | no | — |
| `status` | "ACTIVE" \| "DISABLED" | no | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `key` | string | yes | — |
| `name` | string | yes | — |
| `description` | string \| null | yes | — |
| `payloadSchema` | object | yes | — |
| `identityField` | string \| null | yes | — |
| `futureDateField` | string \| null | yes | — |
| `cancelRefField` | string \| null | yes | — |
| `endpointTokenPrefix` | string | yes | — |
| `requireSignature` | boolean | yes | — |
| `status` | "ACTIVE" \| "DISABLED" | yes | — |
| `lastReceivedAt` | string \| null | yes | — |
| `receivedCount` | integer | yes | — |
| `previousSecretExpiresAt` | string \| null | yes | — |
| `createdAt` | string | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `TRIGGER_NOT_FOUND` | 404 | — |
| `VALIDATION` | 400 | — |
| `INVALID_SCHEMA` | 400 | — |

```bash
curl -X PATCH "https://app.kanorio.com/api/v1/sites/{id}/automations/triggers/{triggerId}" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status":"DISABLED"}'
```

```json
{
  "id": "trg_example01",
  "key": "custom:trg_example01",
  "name": "ERP order synced",
  "description": null,
  "payloadSchema": {
    "type": "object",
    "properties": {
      "orderRef": {
        "type": "string",
        "title": "Order ref",
        "description": "ERP order reference"
      },
      "total": {
        "type": "number",
        "title": "Total",
        "description": "Order total"
      }
    },
    "required": [
      "orderRef",
      "total"
    ]
  },
  "identityField": null,
  "futureDateField": null,
  "cancelRefField": "orderRef",
  "endpointTokenPrefix": "aut_Ab12Cd34",
  "requireSignature": true,
  "status": "DISABLED",
  "lastReceivedAt": null,
  "receivedCount": 0,
  "previousSecretExpiresAt": null,
  "createdAt": "2026-10-31T12:00:00.000Z"
}
```

### Delete a trigger

`DELETE /sites/{id}/automations/triggers/{triggerId}`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_triggers_delete`

Delete a custom trigger; its endpoint stops working and automations using it stop starting (they keep their graph). Errors: FORBIDDEN, TRIGGER_NOT_FOUND.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `triggerId` | path | string | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `deleted` | true | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `TRIGGER_NOT_FOUND` | 404 | — |

```bash
curl -X DELETE "https://app.kanorio.com/api/v1/sites/{id}/automations/triggers/{triggerId}" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "deleted": true
}
```

### Rotate token

`POST /sites/{id}/automations/triggers/{triggerId}/rotate-token`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_triggers_rotate_token`

Regenerate a custom trigger's endpoint token: the old URL stops working immediately; the new path is returned once. Errors: FORBIDDEN, TRIGGER_NOT_FOUND.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `triggerId` | path | string | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `endpointToken` | string | yes | — |
| `endpointPath` | string | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `TRIGGER_NOT_FOUND` | 404 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/automations/triggers/{triggerId}/rotate-token" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```json
{
  "endpointToken": "aut_Ef56Gh78…",
  "endpointPath": "/api/webhooks/automations/aut_Ef56Gh78…"
}
```

### Rotate secret

`POST /sites/{id}/automations/triggers/{triggerId}/rotate-secret`

- Stability: beta
- Permission: `automations:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_triggers_rotate_secret`

Rotate a custom trigger's signing secret: the new whsec_ secret is returned once; the previous one keeps verifying for 24 hours so senders can switch without downtime. Errors: FORBIDDEN, TRIGGER_NOT_FOUND.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `triggerId` | path | string | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `signingSecret` | string | yes | — |
| `previousSecretExpiresAt` | string \| null | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `TRIGGER_NOT_FOUND` | 404 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/automations/triggers/{triggerId}/rotate-secret" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```json
{
  "signingSecret": "whsec_…",
  "previousSecretExpiresAt": "2026-11-01T12:00:00.000Z"
}
```

### List secrets

`GET /sites/{id}/automations/secrets`

- Stability: beta
- Permission: `site:integrations`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_secrets_list`

List the site's automation secrets (handles and masked hints only; values are write-only). Reference them in the Send HTTP request action as {{secret("handle")}}. The whole list is returned in one page. Errors: FORBIDDEN.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | object[] | yes | — |
| `data[].handle` | string | yes | — |
| `data[].hint` | string \| null | yes | — |
| `data[].createdAt` | string | yes | — |
| `data[].rotatedAt` | string | yes | — |
| `hasMore` | boolean | yes | True when another page exists; pass nextCursor to fetch it. |
| `nextCursor` | string \| null | yes | Cursor for the next page, or null on the last page. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/automations/secrets" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "data": [
    {
      "handle": "erp_api_key",
      "hint": "••••f3a9",
      "createdAt": "2026-10-31T12:00:00.000Z",
      "rotatedAt": "2026-10-31T12:00:00.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

### Set a secret

`PUT /sites/{id}/automations/secrets`

- Stability: beta
- Permission: `site:integrations`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_secrets_put`

Create or rotate an automation secret (handle ^[a-z0-9][a-z0-9_-]{0,63}$, value ≤ 4096 chars). Values are encrypted, never returned and never written to run logs. Errors: FORBIDDEN, VALIDATION, PLAN_LIMIT_REACHED.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `handle` | string | yes | — |
| `value` | string | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `secrets` | object[] | yes | — |
| `secrets[].handle` | string | yes | — |
| `secrets[].hint` | string \| null | yes | — |
| `secrets[].createdAt` | string | yes | — |
| `secrets[].rotatedAt` | string | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `VALIDATION` | 400 | — |
| `PLAN_LIMIT_REACHED` | 403 | — |

```bash
curl -X PUT "https://app.kanorio.com/api/v1/sites/{id}/automations/secrets" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"handle":"erp_api_key","value":"sk-live-…"}'
```

```json
{
  "secrets": [
    {
      "handle": "erp_api_key",
      "hint": "••••f3a9",
      "createdAt": "2026-10-31T12:00:00.000Z",
      "rotatedAt": "2026-10-31T12:00:00.000Z"
    }
  ]
}
```

### Delete a secret

`DELETE /sites/{id}/automations/secrets/{handle}`

- Stability: beta
- Permission: `site:integrations`
- Rate limit: default (120/minute per token)
- MCP tool: `automations_secrets_delete`

Delete an automation secret; HTTP actions referencing it fail at send time. Errors: FORBIDDEN, SECRET_NOT_FOUND.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `handle` | path | string | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `deleted` | true | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `SECRET_NOT_FOUND` | 404 | — |

```bash
curl -X DELETE "https://app.kanorio.com/api/v1/sites/{id}/automations/secrets/{handle}" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "deleted": true
}
```
