# Automations

> Part of the Cowliss docs. Markdown for every page: append `index.md` to its URL. Site map: /llms.txt

<!-- GENERATED by apps/docs/scripts/generate-api-reference.ts. Do not edit. Regenerate with `pnpm -F cowliss-docs generate`. -->

An automation is a JSON definition, a trigger and a list of steps (schema: [/schemas/automation.json](/schemas/automation.json)), addressed by its key in its app and running its latest ready version: a key is unique per app, so every route that names one takes `appId` and a request without it is a **422** naming the parameter. Create and update write a definition the way a push of that one file would. Status decides whether the automation fires, and neither a push nor a write touches it: a key is off until it is turned on. The `spine` is a display outline derived from the steps. An automation belongs to one app and fires on that app's events only. A trigger's `event` is a pattern in which `*` matches any sequence of characters, one or a list of them, and a pattern reaches Cowliss's own `system.` events only when its literal prefix starts with `system.`: see [Event patterns](/automations/#event-patterns). Executions are on the [Executions](/reference/api/executions/) page.

## GET /v1/automations

List the organization's automations

**Auth:** Session token.

**Query parameters**

```json
{
  "cursor": "string",
  "limit": 50,
  "status": "on",
  "appId": "string",
  "q": "string",
  "tag": "string"
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `cursor` | string | no |  |
| `limit` | integer | no | default `50`; 1 ≤ x ≤ 100 |
| `status` | "on" \| "off" \| "paused" | no |  |
| `appId` | string | no | length 0–200 |
| `q` | string | no | length 0–200 |
| `tag` | string | no | length 0–50 |

**Response 200**

```json
{
  "data": [
    {
      "orgId": "string",
      "key": "string",
      "appId": "string",
      "tags": [
        "string"
      ],
      "trigger": {
        "event": "string"
      },
      "purpose": "string",
      "description": "string",
      "spine": [
        {
          "name": "sleep",
          "detail": "string",
          "timeout": "string",
          "steps": [],
          "otherwise": []
        }
      ],
      "status": "on",
      "source": "push",
      "createdAt": "2026-01-15T09:30:00.000Z",
      "updatedAt": "2026-01-15T09:30:00.000Z",
      "latestVersion": {
        "id": "string",
        "status": "compiling",
        "appId": "string",
        "error": "string",
        "protocol": 42,
        "source": "push",
        "pushedAt": "2026-01-15T09:30:00.000Z",
        "bundle": "string"
      }
    }
  ],
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object[] | yes |  |
| `data[]` | object | yes |  |
| `data[].orgId` | string | yes |  |
| `data[].key` | string | yes |  |
| `data[].appId` | string | yes |  |
| `data[].tags` | string[] | yes |  |
| `data[].trigger` | object \| object \| object | yes |  |
| `data[].purpose` | string | yes |  |
| `data[].description` | string \| null | yes |  |
| `data[].spine` | object[] | yes |  |
| `data[].spine[]` | object | yes |  |
| `data[].spine[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data[].spine[].detail` | string | no | length 0–200 |
| `data[].spine[].timeout` | string | no | length 0–50 |
| `data[].spine[].steps` | object[] | no |  |
| `data[].spine[].steps[]` | object | no |  |
| `data[].spine[].steps[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data[].spine[].steps[].detail` | string | no | length 0–200 |
| `data[].spine[].steps[].timeout` | string | no | length 0–50 |
| `data[].spine[].steps[].steps` | object[] | no |  |
| `data[].spine[].steps[].otherwise` | object[] | no |  |
| `data[].spine[].otherwise` | object[] | no |  |
| `data[].spine[].otherwise[]` | object | no |  |
| `data[].spine[].otherwise[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data[].spine[].otherwise[].detail` | string | no | length 0–200 |
| `data[].spine[].otherwise[].timeout` | string | no | length 0–50 |
| `data[].spine[].otherwise[].steps` | object[] | no |  |
| `data[].spine[].otherwise[].otherwise` | object[] | no |  |
| `data[].status` | "on" \| "off" \| "paused" | yes |  |
| `data[].source` | "push" \| "api" | yes |  |
| `data[].createdAt` | string (date-time) | yes |  |
| `data[].updatedAt` | string (date-time) | yes |  |
| `data[].latestVersion` | object \| null | yes |  |
| `data[].latestVersion.id` | string | yes |  |
| `data[].latestVersion.status` | "compiling" \| "ready" \| "failed" | yes |  |
| `data[].latestVersion.appId` | string | yes |  |
| `data[].latestVersion.error` | string \| null | yes |  |
| `data[].latestVersion.protocol` | integer | yes | -2147483648 ≤ x ≤ 2147483647 |
| `data[].latestVersion.source` | "push" \| "api" | yes |  |
| `data[].latestVersion.pushedAt` | string (date-time) | yes |  |
| `data[].latestVersion.bundle` | string | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## POST /v1/automations

Create an automation from its definition

Admin only. Files the definition exactly as `cow push` files an `automations/<key>.json` file, and the automation starts off. A key the app already holds is refused: change that automation with `automations.update` instead.

**Auth:** Session token.

**Request body**

```json
{
  "data": {
    "appId": "string",
    "key": "string",
    "definition": {
      "trigger": {
        "event": "string"
      },
      "purpose": "marketing",
      "from": "string",
      "tags": [],
      "description": "string",
      "enrollment": {
        "cooldown": "string"
      },
      "steps": [
        {
          "id": "string",
          "type": "sleep",
          "duration": "string"
        }
      ]
    }
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.appId` | string | yes |  |
| `data.key` | string | yes | length 0–64 |
| `data.definition` | object | yes |  |
| `data.definition.trigger` | object \| object \| object | yes |  |
| `data.definition.purpose` | "marketing" \| "transactional" | yes |  |
| `data.definition.from` | string | yes | length 0–200 |
| `data.definition.tags` | string[] | no | default `[]`; items 0–20 |
| `data.definition.description` | string | no | length 0–500 |
| `data.definition.enrollment` | object | no |  |
| `data.definition.enrollment.cooldown` | string \| integer | yes |  |
| `data.definition.steps` | one of[] | yes |  |
| `data.definition.steps[]` | one of | yes |  |
| `data.definition.steps[]〈sleep〉` | object | yes |  |
| `data.definition.steps[]〈sleep〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈sleep〉.type` | "sleep" | yes |  |
| `data.definition.steps[]〈sleep〉.duration` | string \| integer | yes |  |
| `data.definition.steps[]〈waitForEvent〉` | object | yes |  |
| `data.definition.steps[]〈waitForEvent〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈waitForEvent〉.type` | "waitForEvent" | yes |  |
| `data.definition.steps[]〈waitForEvent〉.event` | string \| string[] | yes |  |
| `data.definition.steps[]〈waitForEvent〉.timeout` | string \| integer | yes |  |
| `data.definition.steps[]〈sendEmail〉` | object | yes |  |
| `data.definition.steps[]〈sendEmail〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈sendEmail〉.type` | "sendEmail" | yes |  |
| `data.definition.steps[]〈sendEmail〉.template` | string | yes | length 0–64 |
| `data.definition.steps[]〈sendEmail〉.props` | record\<string, any> | no | default `{}` |
| `data.definition.steps[]〈sendEmail〉.from` | string | no | length 0–200 |
| `data.definition.steps[]〈sendWebhook〉` | object | yes |  |
| `data.definition.steps[]〈sendWebhook〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈sendWebhook〉.type` | "sendWebhook" | yes |  |
| `data.definition.steps[]〈sendWebhook〉.webhook` | string | no | length 1–∞ |
| `data.definition.steps[]〈sendWebhook〉.destination` | string | no | length 1–∞ |
| `data.definition.steps[]〈sendWebhook〉.payload` | record\<string, any> | no | default `{}` |
| `data.definition.steps[]〈branch〉` | object | yes |  |
| `data.definition.steps[]〈branch〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈branch〉.type` | "branch" | yes |  |
| `data.definition.steps[]〈branch〉.if` | string \| number \| boolean \| string \| number \| boolean \| AutomationRule[] \| record\<string, any> \| null[] \| record\<string, any> \| null | yes |  |
| `data.definition.steps[]〈branch〉.then` | one of[] | yes |  |
| `data.definition.steps[]〈branch〉.then[]` | one of | yes |  |
| `data.definition.steps[]〈branch〉.else` | one of[] | no |  |
| `data.definition.steps[]〈branch〉.else[]` | one of | no |  |
| `data.definition.steps[]〈exit〉` | object | yes |  |
| `data.definition.steps[]〈exit〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈exit〉.type` | "exit" | yes |  |
| `data.definition.steps[]〈restart〉` | object | yes |  |
| `data.definition.steps[]〈restart〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈restart〉.type` | "restart" | yes |  |

**Response 201**

```json
{
  "data": {
    "orgId": "string",
    "key": "string",
    "appId": "string",
    "tags": [
      "string"
    ],
    "trigger": {
      "event": "string"
    },
    "purpose": "string",
    "description": "string",
    "spine": [
      {
        "name": "sleep",
        "detail": "string",
        "timeout": "string",
        "steps": [],
        "otherwise": []
      }
    ],
    "status": "on",
    "source": "push",
    "createdAt": "2026-01-15T09:30:00.000Z",
    "updatedAt": "2026-01-15T09:30:00.000Z",
    "latestVersion": {
      "id": "string",
      "status": "compiling",
      "appId": "string",
      "error": "string",
      "protocol": 42,
      "source": "push",
      "pushedAt": "2026-01-15T09:30:00.000Z",
      "bundle": "string"
    }
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.orgId` | string | yes |  |
| `data.key` | string | yes |  |
| `data.appId` | string | yes |  |
| `data.tags` | string[] | yes |  |
| `data.trigger` | object \| object \| object | yes |  |
| `data.purpose` | string | yes |  |
| `data.description` | string \| null | yes |  |
| `data.spine` | object[] | yes |  |
| `data.spine[]` | object | yes |  |
| `data.spine[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data.spine[].detail` | string | no | length 0–200 |
| `data.spine[].timeout` | string | no | length 0–50 |
| `data.spine[].steps` | object[] | no |  |
| `data.spine[].steps[]` | object | no |  |
| `data.spine[].steps[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data.spine[].steps[].detail` | string | no | length 0–200 |
| `data.spine[].steps[].timeout` | string | no | length 0–50 |
| `data.spine[].steps[].steps` | object[] | no |  |
| `data.spine[].steps[].steps[]` | object | no |  |
| `data.spine[].steps[].otherwise` | object[] | no |  |
| `data.spine[].steps[].otherwise[]` | object | no |  |
| `data.spine[].otherwise` | object[] | no |  |
| `data.spine[].otherwise[]` | object | no |  |
| `data.spine[].otherwise[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data.spine[].otherwise[].detail` | string | no | length 0–200 |
| `data.spine[].otherwise[].timeout` | string | no | length 0–50 |
| `data.spine[].otherwise[].steps` | object[] | no |  |
| `data.spine[].otherwise[].steps[]` | object | no |  |
| `data.spine[].otherwise[].otherwise` | object[] | no |  |
| `data.spine[].otherwise[].otherwise[]` | object | no |  |
| `data.status` | "on" \| "off" \| "paused" | yes |  |
| `data.source` | "push" \| "api" | yes |  |
| `data.createdAt` | string (date-time) | yes |  |
| `data.updatedAt` | string (date-time) | yes |  |
| `data.latestVersion` | object \| null | yes |  |
| `data.latestVersion.id` | string | yes |  |
| `data.latestVersion.status` | "compiling" \| "ready" \| "failed" | yes |  |
| `data.latestVersion.appId` | string | yes |  |
| `data.latestVersion.error` | string \| null | yes |  |
| `data.latestVersion.protocol` | integer | yes | -2147483648 ≤ x ≤ 2147483647 |
| `data.latestVersion.source` | "push" \| "api" | yes |  |
| `data.latestVersion.pushedAt` | string (date-time) | yes |  |
| `data.latestVersion.bundle` | string | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## PATCH /v1/automations/{key}

Replace an automation's definition

Admin only. Files the definition as a new version, the way a push of the changed file does. Runs already in flight finish on the version they started on; new runs take this one.

**Auth:** Session token.

**Query parameters**

```json
{
  "appId": "string"
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `appId` | string | yes |  |

**Request body**

```json
{
  "data": {
    "definition": {
      "trigger": {
        "event": "string"
      },
      "purpose": "marketing",
      "from": "string",
      "tags": [],
      "description": "string",
      "enrollment": {
        "cooldown": "string"
      },
      "steps": [
        {
          "id": "string",
          "type": "sleep",
          "duration": "string"
        }
      ]
    }
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.definition` | object | yes |  |
| `data.definition.trigger` | object \| object \| object | yes |  |
| `data.definition.purpose` | "marketing" \| "transactional" | yes |  |
| `data.definition.from` | string | yes | length 0–200 |
| `data.definition.tags` | string[] | no | default `[]`; items 0–20 |
| `data.definition.description` | string | no | length 0–500 |
| `data.definition.enrollment` | object | no |  |
| `data.definition.enrollment.cooldown` | string \| integer | yes |  |
| `data.definition.steps` | one of[] | yes |  |
| `data.definition.steps[]` | one of | yes |  |
| `data.definition.steps[]〈sleep〉` | object | yes |  |
| `data.definition.steps[]〈sleep〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈sleep〉.type` | "sleep" | yes |  |
| `data.definition.steps[]〈sleep〉.duration` | string \| integer | yes |  |
| `data.definition.steps[]〈waitForEvent〉` | object | yes |  |
| `data.definition.steps[]〈waitForEvent〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈waitForEvent〉.type` | "waitForEvent" | yes |  |
| `data.definition.steps[]〈waitForEvent〉.event` | string \| string[] | yes |  |
| `data.definition.steps[]〈waitForEvent〉.timeout` | string \| integer | yes |  |
| `data.definition.steps[]〈sendEmail〉` | object | yes |  |
| `data.definition.steps[]〈sendEmail〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈sendEmail〉.type` | "sendEmail" | yes |  |
| `data.definition.steps[]〈sendEmail〉.template` | string | yes | length 0–64 |
| `data.definition.steps[]〈sendEmail〉.props` | record\<string, any> | no | default `{}` |
| `data.definition.steps[]〈sendEmail〉.from` | string | no | length 0–200 |
| `data.definition.steps[]〈sendWebhook〉` | object | yes |  |
| `data.definition.steps[]〈sendWebhook〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈sendWebhook〉.type` | "sendWebhook" | yes |  |
| `data.definition.steps[]〈sendWebhook〉.webhook` | string | no | length 1–∞ |
| `data.definition.steps[]〈sendWebhook〉.destination` | string | no | length 1–∞ |
| `data.definition.steps[]〈sendWebhook〉.payload` | record\<string, any> | no | default `{}` |
| `data.definition.steps[]〈branch〉` | object | yes |  |
| `data.definition.steps[]〈branch〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈branch〉.type` | "branch" | yes |  |
| `data.definition.steps[]〈branch〉.if` | string \| number \| boolean \| string \| number \| boolean \| AutomationRule[] \| record\<string, any> \| null[] \| record\<string, any> \| null | yes |  |
| `data.definition.steps[]〈branch〉.then` | one of[] | yes |  |
| `data.definition.steps[]〈branch〉.then[]` | one of | yes |  |
| `data.definition.steps[]〈branch〉.else` | one of[] | no |  |
| `data.definition.steps[]〈branch〉.else[]` | one of | no |  |
| `data.definition.steps[]〈exit〉` | object | yes |  |
| `data.definition.steps[]〈exit〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈exit〉.type` | "exit" | yes |  |
| `data.definition.steps[]〈restart〉` | object | yes |  |
| `data.definition.steps[]〈restart〉.id` | string | yes | length 0–64 |
| `data.definition.steps[]〈restart〉.type` | "restart" | yes |  |

**Response 200**

```json
{
  "data": {
    "orgId": "string",
    "key": "string",
    "appId": "string",
    "tags": [
      "string"
    ],
    "trigger": {
      "event": "string"
    },
    "purpose": "string",
    "description": "string",
    "spine": [
      {
        "name": "sleep",
        "detail": "string",
        "timeout": "string",
        "steps": [],
        "otherwise": []
      }
    ],
    "status": "on",
    "source": "push",
    "createdAt": "2026-01-15T09:30:00.000Z",
    "updatedAt": "2026-01-15T09:30:00.000Z",
    "latestVersion": {
      "id": "string",
      "status": "compiling",
      "appId": "string",
      "error": "string",
      "protocol": 42,
      "source": "push",
      "pushedAt": "2026-01-15T09:30:00.000Z",
      "bundle": "string"
    }
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.orgId` | string | yes |  |
| `data.key` | string | yes |  |
| `data.appId` | string | yes |  |
| `data.tags` | string[] | yes |  |
| `data.trigger` | object \| object \| object | yes |  |
| `data.purpose` | string | yes |  |
| `data.description` | string \| null | yes |  |
| `data.spine` | object[] | yes |  |
| `data.spine[]` | object | yes |  |
| `data.spine[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data.spine[].detail` | string | no | length 0–200 |
| `data.spine[].timeout` | string | no | length 0–50 |
| `data.spine[].steps` | object[] | no |  |
| `data.spine[].steps[]` | object | no |  |
| `data.spine[].steps[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data.spine[].steps[].detail` | string | no | length 0–200 |
| `data.spine[].steps[].timeout` | string | no | length 0–50 |
| `data.spine[].steps[].steps` | object[] | no |  |
| `data.spine[].steps[].steps[]` | object | no |  |
| `data.spine[].steps[].otherwise` | object[] | no |  |
| `data.spine[].steps[].otherwise[]` | object | no |  |
| `data.spine[].otherwise` | object[] | no |  |
| `data.spine[].otherwise[]` | object | no |  |
| `data.spine[].otherwise[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data.spine[].otherwise[].detail` | string | no | length 0–200 |
| `data.spine[].otherwise[].timeout` | string | no | length 0–50 |
| `data.spine[].otherwise[].steps` | object[] | no |  |
| `data.spine[].otherwise[].steps[]` | object | no |  |
| `data.spine[].otherwise[].otherwise` | object[] | no |  |
| `data.spine[].otherwise[].otherwise[]` | object | no |  |
| `data.status` | "on" \| "off" \| "paused" | yes |  |
| `data.source` | "push" \| "api" | yes |  |
| `data.createdAt` | string (date-time) | yes |  |
| `data.updatedAt` | string (date-time) | yes |  |
| `data.latestVersion` | object \| null | yes |  |
| `data.latestVersion.id` | string | yes |  |
| `data.latestVersion.status` | "compiling" \| "ready" \| "failed" | yes |  |
| `data.latestVersion.appId` | string | yes |  |
| `data.latestVersion.error` | string \| null | yes |  |
| `data.latestVersion.protocol` | integer | yes | -2147483648 ≤ x ≤ 2147483647 |
| `data.latestVersion.source` | "push" \| "api" | yes |  |
| `data.latestVersion.pushedAt` | string (date-time) | yes |  |
| `data.latestVersion.bundle` | string | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## GET /v1/automations/{key}

Inspect one automation

**Auth:** Session token.

**Query parameters**

```json
{
  "appId": "string"
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `appId` | string | yes |  |

**Response 200**

```json
{
  "data": {
    "orgId": "string",
    "key": "string",
    "appId": "string",
    "tags": [
      "string"
    ],
    "trigger": {
      "event": "string"
    },
    "purpose": "string",
    "description": "string",
    "spine": [
      {
        "name": "sleep",
        "detail": "string",
        "timeout": "string",
        "steps": [],
        "otherwise": []
      }
    ],
    "status": "on",
    "source": "push",
    "createdAt": "2026-01-15T09:30:00.000Z",
    "updatedAt": "2026-01-15T09:30:00.000Z",
    "latestVersion": {
      "id": "string",
      "status": "compiling",
      "appId": "string",
      "error": "string",
      "protocol": 42,
      "source": "push",
      "pushedAt": "2026-01-15T09:30:00.000Z",
      "bundle": "string"
    },
    "segment": {
      "id": "string",
      "orgId": "string",
      "appId": "string",
      "name": "string",
      "description": "string",
      "tags": [
        "string"
      ],
      "definition": {
        "match": "all",
        "predicates": [
          {
            "kind": "trait",
            "name": "string",
            "op": "eq",
            "value": {}
          }
        ]
      },
      "source": "push",
      "automationKey": "string",
      "createdAt": "2026-01-15T09:30:00.000Z",
      "updatedAt": "2026-01-15T09:30:00.000Z",
      "memberCount": 42
    }
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.orgId` | string | yes |  |
| `data.key` | string | yes |  |
| `data.appId` | string | yes |  |
| `data.tags` | string[] | yes |  |
| `data.trigger` | object \| object \| object | yes |  |
| `data.purpose` | string | yes |  |
| `data.description` | string \| null | yes |  |
| `data.spine` | object[] | yes |  |
| `data.spine[]` | object | yes |  |
| `data.spine[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data.spine[].detail` | string | no | length 0–200 |
| `data.spine[].timeout` | string | no | length 0–50 |
| `data.spine[].steps` | object[] | no |  |
| `data.spine[].steps[]` | object | no |  |
| `data.spine[].steps[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data.spine[].steps[].detail` | string | no | length 0–200 |
| `data.spine[].steps[].timeout` | string | no | length 0–50 |
| `data.spine[].steps[].steps` | object[] | no |  |
| `data.spine[].steps[].steps[]` | object | no |  |
| `data.spine[].steps[].otherwise` | object[] | no |  |
| `data.spine[].steps[].otherwise[]` | object | no |  |
| `data.spine[].otherwise` | object[] | no |  |
| `data.spine[].otherwise[]` | object | no |  |
| `data.spine[].otherwise[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data.spine[].otherwise[].detail` | string | no | length 0–200 |
| `data.spine[].otherwise[].timeout` | string | no | length 0–50 |
| `data.spine[].otherwise[].steps` | object[] | no |  |
| `data.spine[].otherwise[].steps[]` | object | no |  |
| `data.spine[].otherwise[].otherwise` | object[] | no |  |
| `data.spine[].otherwise[].otherwise[]` | object | no |  |
| `data.status` | "on" \| "off" \| "paused" | yes |  |
| `data.source` | "push" \| "api" | yes |  |
| `data.createdAt` | string (date-time) | yes |  |
| `data.updatedAt` | string (date-time) | yes |  |
| `data.latestVersion` | object \| null | yes |  |
| `data.latestVersion.id` | string | yes |  |
| `data.latestVersion.status` | "compiling" \| "ready" \| "failed" | yes |  |
| `data.latestVersion.appId` | string | yes |  |
| `data.latestVersion.error` | string \| null | yes |  |
| `data.latestVersion.protocol` | integer | yes | -2147483648 ≤ x ≤ 2147483647 |
| `data.latestVersion.source` | "push" \| "api" | yes |  |
| `data.latestVersion.pushedAt` | string (date-time) | yes |  |
| `data.latestVersion.bundle` | string | yes |  |
| `data.segment` | object \| null | yes |  |
| `data.segment.id` | string | yes |  |
| `data.segment.orgId` | string | yes |  |
| `data.segment.appId` | string | yes |  |
| `data.segment.name` | string | yes |  |
| `data.segment.description` | string \| null | yes |  |
| `data.segment.tags` | string[] | yes |  |
| `data.segment.definition` | object | yes |  |
| `data.segment.definition.match` | "all" \| "any" | yes | default `"all"` |
| `data.segment.definition.predicates` | array of variants | yes | items 1–∞ |
| `data.segment.definition.predicates[]` | one of | yes |  |
| `data.segment.definition.predicates[]〈trait〉` | object | yes |  |
| `data.segment.definition.predicates[]〈trait〉.kind` | "trait" | yes |  |
| `data.segment.definition.predicates[]〈trait〉.name` | string | yes | length 1–200 |
| `data.segment.definition.predicates[]〈trait〉.op` | "eq" \| "neq" \| "gt" \| "gte" \| "lt" \| "lte" \| "exists" \| "notExists" \| "contains" \| "before" \| "after" | yes |  |
| `data.segment.definition.predicates[]〈trait〉.value` | any | no |  |
| `data.segment.definition.predicates[]〈event〉` | object | yes |  |
| `data.segment.definition.predicates[]〈event〉.kind` | "event" | yes |  |
| `data.segment.definition.predicates[]〈event〉.name` | string \| string[] | yes |  |
| `data.segment.definition.predicates[]〈event〉.op` | "performed" \| "notPerformed" | yes |  |
| `data.segment.definition.predicates[]〈event〉.atLeast` | integer | yes | default `1`; 1 ≤ x ≤ ∞ |
| `data.segment.definition.predicates[]〈event〉.withinDays` | integer | no | 1 ≤ x ≤ ∞ |
| `data.segment.source` | "push" \| "api" | yes |  |
| `data.segment.automationKey` | string \| null | yes |  |
| `data.segment.createdAt` | string (date-time) | yes |  |
| `data.segment.updatedAt` | string (date-time) | yes |  |
| `data.segment.memberCount` | integer | yes | 0 ≤ x ≤ ∞ |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## GET /v1/automations/{key}/stats

Automation operational stats

**Auth:** Session token.

**Query parameters**

```json
{
  "appId": "string"
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `appId` | string | yes |  |

**Response 200**

```json
{
  "data": {
    "executions": {
      "entered": 42,
      "active": 42,
      "completed": 42,
      "failed": 42,
      "lastStartedAt": "2026-01-15T09:30:00.000Z"
    },
    "deliveries": {
      "byStatus": {
        "sent": 42
      },
      "sentLast30d": 42
    }
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.executions` | object | yes |  |
| `data.executions.entered` | integer | yes |  |
| `data.executions.active` | integer | yes |  |
| `data.executions.completed` | integer | yes |  |
| `data.executions.failed` | integer | yes |  |
| `data.executions.lastStartedAt` | string (date-time) \| null | yes |  |
| `data.deliveries` | object | yes |  |
| `data.deliveries.byStatus` | record\<sent \| delivered \| bounced \| complained \| failed \| skipped_disabled \| skipped_paused \| skipped_suspended \| skipped_suppressed \| skipped_quota \| skipped_unknown_purpose \| skipped_frequency_cap \| skipped_consent \| skipped_domain \| skipped_ssrf \| skipped_spam \| would_send \| would_skip_disabled \| would_skip_paused \| would_skip_suspended \| would_skip_suppressed \| would_skip_quota \| would_skip_unknown_purpose \| would_skip_frequency_cap \| would_skip_consent \| would_skip_domain \| would_skip_ssrf \| would_skip_spam, integer> | yes |  |
| `data.deliveries.sentLast30d` | integer | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## POST /v1/automations/status

Put automations on, off or on hold

Admin only. Turning an automation off lets executions in flight finish, while putting it on hold pauses them before their next step. Turning a held automation on resumes them.

**Auth:** Session token, or an org [pipeline key](/guides/keys/).

**Request body**

```json
{
  "data": {
    "keys": [
      "string"
    ],
    "appId": "string",
    "status": "on"
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.keys` | string[] | no | items 1–100 |
| `data.appId` | string | yes |  |
| `data.status` | "on" \| "off" \| "paused" | yes |  |

**Response 200**

```json
{
  "data": [
    {
      "orgId": "string",
      "key": "string",
      "appId": "string",
      "tags": [
        "string"
      ],
      "trigger": {
        "event": "string"
      },
      "purpose": "string",
      "description": "string",
      "spine": [
        {
          "name": "sleep",
          "detail": "string",
          "timeout": "string",
          "steps": [],
          "otherwise": []
        }
      ],
      "status": "on",
      "source": "push",
      "createdAt": "2026-01-15T09:30:00.000Z",
      "updatedAt": "2026-01-15T09:30:00.000Z",
      "latestVersion": {
        "id": "string",
        "status": "compiling",
        "appId": "string",
        "error": "string",
        "protocol": 42,
        "source": "push",
        "pushedAt": "2026-01-15T09:30:00.000Z",
        "bundle": "string"
      },
      "enrollment": {
        "counts": {
          "scanned": 42,
          "enrolled": 42,
          "skipped": {
            "completed": 42,
            "cooldown": 42,
            "running": 42
          }
        },
        "url": "string"
      }
    }
  ],
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object[] | yes |  |
| `data[]` | object | yes |  |
| `data[].orgId` | string | yes |  |
| `data[].key` | string | yes |  |
| `data[].appId` | string | yes |  |
| `data[].tags` | string[] | yes |  |
| `data[].trigger` | object \| object \| object | yes |  |
| `data[].purpose` | string | yes |  |
| `data[].description` | string \| null | yes |  |
| `data[].spine` | object[] | yes |  |
| `data[].spine[]` | object | yes |  |
| `data[].spine[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data[].spine[].detail` | string | no | length 0–200 |
| `data[].spine[].timeout` | string | no | length 0–50 |
| `data[].spine[].steps` | object[] | no |  |
| `data[].spine[].steps[]` | object | no |  |
| `data[].spine[].steps[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data[].spine[].steps[].detail` | string | no | length 0–200 |
| `data[].spine[].steps[].timeout` | string | no | length 0–50 |
| `data[].spine[].steps[].steps` | object[] | no |  |
| `data[].spine[].steps[].otherwise` | object[] | no |  |
| `data[].spine[].otherwise` | object[] | no |  |
| `data[].spine[].otherwise[]` | object | no |  |
| `data[].spine[].otherwise[].name` | "sleep" \| "waitForEvent" \| "send.email" \| "send.webhook" \| "profile.get" \| "profiles.get" \| "restart" \| "traits.set" \| "traits.unset" \| "events.track" \| "if" \| "loop" \| "end" | yes |  |
| `data[].spine[].otherwise[].detail` | string | no | length 0–200 |
| `data[].spine[].otherwise[].timeout` | string | no | length 0–50 |
| `data[].spine[].otherwise[].steps` | object[] | no |  |
| `data[].spine[].otherwise[].otherwise` | object[] | no |  |
| `data[].status` | "on" \| "off" \| "paused" | yes |  |
| `data[].source` | "push" \| "api" | yes |  |
| `data[].createdAt` | string (date-time) | yes |  |
| `data[].updatedAt` | string (date-time) | yes |  |
| `data[].latestVersion` | object \| null | yes |  |
| `data[].latestVersion.id` | string | yes |  |
| `data[].latestVersion.status` | "compiling" \| "ready" \| "failed" | yes |  |
| `data[].latestVersion.appId` | string | yes |  |
| `data[].latestVersion.error` | string \| null | yes |  |
| `data[].latestVersion.protocol` | integer | yes | -2147483648 ≤ x ≤ 2147483647 |
| `data[].latestVersion.source` | "push" \| "api" | yes |  |
| `data[].latestVersion.pushedAt` | string (date-time) | yes |  |
| `data[].latestVersion.bundle` | string | yes |  |
| `data[].enrollment` | object \| null | yes |  |
| `data[].enrollment.counts` | object \| null | yes |  |
| `data[].enrollment.counts.scanned` | integer | yes |  |
| `data[].enrollment.counts.enrolled` | integer | yes |  |
| `data[].enrollment.counts.skipped` | object | yes |  |
| `data[].enrollment.counts.skipped.completed` | integer | yes |  |
| `data[].enrollment.counts.skipped.cooldown` | integer | yes |  |
| `data[].enrollment.counts.skipped.running` | integer | yes |  |
| `data[].enrollment.url` | string | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## GET /v1/automations/{key}/waits

Count the executions a hold until a given day would end

**Auth:** Session token.

**Query parameters**

```json
{
  "before": "2026-01-15T09:30:00.000Z",
  "appId": "string"
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `before` | string (date-time) | yes |  |
| `appId` | string | yes |  |

**Response 200**

```json
{
  "data": {
    "due": 42,
    "inFlight": 42
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.due` | integer | yes |  |
| `data.inFlight` | integer | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## DELETE /v1/automations/{key}

Delete an automation

Admin only. Executions already in flight finish, but no new ones start. A push never deletes an automation, so this is the only way to remove one whose file was deleted from your code.

**Auth:** Session token.

**Query parameters**

```json
{
  "appId": "string"
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `appId` | string | yes |  |

**Response 200**

```json
{
  "data": {
    "deleted": true
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.deleted` | true | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## GET /v1/automations/{key}/enrollment/preview

Count who turning this automation on would reach

**Auth:** Session token.

**Query parameters**

```json
{
  "appId": "string"
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `appId` | string | yes |  |

**Response 200**

```json
{
  "data": {
    "scanned": 42,
    "enrolled": 42,
    "skipped": {
      "completed": 42,
      "cooldown": 42,
      "running": 42
    }
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.scanned` | integer | yes |  |
| `data.enrolled` | integer | yes |  |
| `data.skipped` | object | yes |  |
| `data.skipped.completed` | integer | yes |  |
| `data.skipped.cooldown` | integer | yes |  |
| `data.skipped.running` | integer | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## POST /v1/automations/{key}/enrollment

Enroll the automation's current members

Admin only. Starts an enrollment for recipients currently in the automation's segment. If an enrollment is already running, this returns the existing enrollment rather than starting another.

**Auth:** Session token.

**Query parameters**

```json
{
  "appId": "string"
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `appId` | string | yes |  |

**Response 202**

```json
{
  "data": {
    "scanned": 42,
    "enrolled": 42,
    "skipped": {
      "completed": 42,
      "cooldown": 42,
      "running": 42
    },
    "state": "none"
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.scanned` | integer | yes |  |
| `data.enrolled` | integer | yes |  |
| `data.skipped` | object | yes |  |
| `data.skipped.completed` | integer | yes |  |
| `data.skipped.cooldown` | integer | yes |  |
| `data.skipped.running` | integer | yes |  |
| `data.state` | "none" \| "running" \| "done" \| "cancelled" \| "stopped" \| "failed" | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## GET /v1/automations/{key}/enrollment

How far the automation's enrollment has got

The state is `none`, with zero counts, when no enrollment has run for the automation.

**Auth:** Session token.

**Query parameters**

```json
{
  "appId": "string"
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `appId` | string | yes |  |

**Response 200**

```json
{
  "data": {
    "scanned": 42,
    "enrolled": 42,
    "skipped": {
      "completed": 42,
      "cooldown": 42,
      "running": 42
    },
    "state": "none"
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.scanned` | integer | yes |  |
| `data.enrolled` | integer | yes |  |
| `data.skipped` | object | yes |  |
| `data.skipped.completed` | integer | yes |  |
| `data.skipped.cooldown` | integer | yes |  |
| `data.skipped.running` | integer | yes |  |
| `data.state` | "none" \| "running" \| "done" \| "cancelled" \| "stopped" \| "failed" | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## POST /v1/automations/{key}/enrollment/cancel

Stop a running enrollment

Admin only. Recipients already enrolled remain in the automation and their executions continue. Stopping the enrollment only prevents remaining recipients from enrolling.

**Auth:** Session token.

**Query parameters**

```json
{
  "appId": "string"
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `appId` | string | yes |  |

**Response 200**

```json
{
  "data": {
    "scanned": 42,
    "enrolled": 42,
    "skipped": {
      "completed": 42,
      "cooldown": 42,
      "running": 42
    },
    "state": "none"
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.scanned` | integer | yes |  |
| `data.enrolled` | integer | yes |  |
| `data.skipped` | object | yes |  |
| `data.skipped.completed` | integer | yes |  |
| `data.skipped.cooldown` | integer | yes |  |
| `data.skipped.running` | integer | yes |  |
| `data.state` | "none" \| "running" \| "done" \| "cancelled" \| "stopped" \| "failed" | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).

## POST /v1/automations/{key}/dry-run

Dry-run an automation against a real user

Admin only. Runs the automation's latest ready version against the specified profile with sends disabled, delivering no emails or webhooks. The execution proceeds even if the automation is turned off.

**Auth:** Session token.

**Query parameters**

```json
{
  "appId": "string"
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `appId` | string | yes |  |

**Request body**

```json
{
  "data": {
    "profileId": "string"
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.profileId` | string | yes | length 1–∞ |

**Response 201**

```json
{
  "data": {
    "id": "string",
    "orgId": "string",
    "appId": "string",
    "automationKey": "string",
    "profileId": "string",
    "workflowId": "string",
    "runId": "string",
    "versionId": "string",
    "triggerEventId": "string",
    "status": "running",
    "step": "string",
    "timerDeadline": "2026-01-15T09:30:00.000Z",
    "journalEntries": 42,
    "error": "string",
    "startedAt": "2026-01-15T09:30:00.000Z",
    "updatedAt": "2026-01-15T09:30:00.000Z",
    "endedAt": "2026-01-15T09:30:00.000Z"
  },
  "meta": {
    "requestId": "string",
    "nextCursor": "string",
    "hasMore": true,
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "suggestion": "string"
      }
    ]
  }
}
```

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | object | yes |  |
| `data.id` | string | yes |  |
| `data.orgId` | string | yes |  |
| `data.appId` | string | yes |  |
| `data.automationKey` | string | yes |  |
| `data.profileId` | string | yes |  |
| `data.workflowId` | string | yes |  |
| `data.runId` | string | yes |  |
| `data.versionId` | string | yes |  |
| `data.triggerEventId` | string \| null | yes |  |
| `data.status` | "running" \| "waiting" \| "completed" \| "failed" \| "cancelled" | yes |  |
| `data.step` | string \| null | yes |  |
| `data.timerDeadline` | string (date-time) \| null | yes |  |
| `data.journalEntries` | integer | yes | -2147483648 ≤ x ≤ 2147483647 |
| `data.error` | string \| null | yes |  |
| `data.startedAt` | string (date-time) | yes |  |
| `data.updatedAt` | string (date-time) | yes |  |
| `data.endedAt` | string (date-time) \| null | yes |  |
| `meta` | object | no |  |
| `meta.requestId` | string | no |  |
| `meta.nextCursor` | string \| null | no |  |
| `meta.hasMore` | boolean | no |  |
| `meta.warnings` | object[] | no |  |
| `meta.warnings[]` | object | no |  |
| `meta.warnings[].code` | string | yes |  |
| `meta.warnings[].message` | string | yes |  |
| `meta.warnings[].suggestion` | string | no |  |

Errors follow the [shared envelope and code table](/api-conventions/#errors).
