# Automations API

> Build workflows from triggers and steps, run them, and inspect their runs.

Base URL: `https://api.emailit.com/v2`. Authenticate with `Authorization: Bearer <API key>`.

## Create an automation — POST /automations

> Create a draft automation from a graph of trigger and action steps, such as a welcome email sent when a contact joins an audience.

# Create an automation

Creates an automation in `draft` status from a graph of steps and connections. Requires an API key with the `full` scope. Automations are in beta.

The automation does nothing until you [start it](/docs/api-reference/automations/start/). Each run costs 3 credits when it starts, and every email sent by `send_email` or `forward_email` costs 1 more credit. In an unverified workspace, those actions can only send to workspace members' account emails.

`POST /automations`

## Body parameters

- `context` (string, required): What each run is about: `contact`, `email` or `event`. The context decides which triggers and actions you can use and can't be changed later. See [Contexts](#contexts).

- `name` (string, required): Name of the automation, up to 191 characters.

- `description` (string | null): Optional description.

- `settings` (object): Run rules. See [Settings](#settings).

- `steps` (object[], required): The trigger and action steps, at least one trigger. See [Steps](#steps).

- `connections` (object[], required): The edges between steps. Pass `[]` for a graph with only a trigger. See [Connections](#connections).

### Settings

- `on_step_failure` (string): `stop` marks the run `failed` when a step fails. `skip` records the failed step and lets the rest of the run finish.

- `allow_reentry` (boolean): `false` skips a trigger when the same contact (or email) already has a running run in this automation.

- `max_concurrent_runs` (integer): Maximum number of runs in `running` status at once. `0` means no limit.

- `cooldown_seconds` (integer): Skips a trigger when the same contact (or email) started a run in this automation within this many seconds.

### Steps

- `key` (string, required): Your identifier for the step, unique within the automation, for example `welcome_email`. Connections, [step statistics](/docs/api-reference/automations/step-stats/) and updates refer to steps by key.

- `type` (string, required): `trigger` or `action`.

- `trigger` (string): Trigger name, required when `type` is `trigger`. See [Triggers](#triggers).

- `action` (string): Action name, required when `type` is `action`. See [Actions](#actions).

- `config` (object): Settings for the trigger or action.

### Connections

- `from` (string, required): Key of the step the edge starts at.

- `to` (string, required): Key of the next step.

- `branch` (string): Which outcome of the `from` step follows this edge. `condition` steps use `yes` and `no`; `experiment` steps use variant keys. Every other step uses `default`.

## Contexts

| Context | A run is about | Triggers | Rules |
| --- | --- | --- | --- |
| `contact` | One contact. `send_email` goes to that contact. | `contact.*`, `system.*` | One or more triggers. They must all connect to the same first action. |
| `email` | One email (sent or received). | `email.*`, `system.*` | One or more triggers. They must all connect to the same first action. |
| `event` | The trigger payload only. | `event.*`, `system.*` | Exactly one trigger. |

Every action must be reachable from a trigger. A run starts at the action connected to the trigger that fired and follows connections:

- `condition` follows only the edge whose `branch` is `yes` or `no`, depending on the result.
- `experiment` follows the edges of the chosen variant. When that path ends, the run continues on the experiment step's `default` edges.
- `wait` delays the next step.
- Every other action follows its `default` edges. A step with several outgoing edges runs all of them.

A run is `completed` when no steps are left, `failed` when a step fails (with `on_step_failure: "stop"`), and `canceled` when you [stop the automation](/docs/api-reference/automations/stop/).

## Triggers

| Trigger | Context | Fires when |
| --- | --- | --- |
| `contact.added_to_audience` | contact | A contact subscribes to an audience, including resubscribes. |
| `contact.removed_from_audience` | contact | A subscriber is deleted from an audience. |
| `contact.updated` | contact | A contact is updated. |
| `contact.loaded_email` | contact | A recipient who is a contact in the workspace opens an email. |
| `contact.clicked_in_email` | contact | A recipient who is a contact in the workspace clicks a tracked link. |
| `contact.date_anniversary` | contact | Daily at 00:00 UTC, for contacts whose date custom field (`YYYY-MM-DD`) has today's month and day. |
| `contact.on_date` | contact | Daily at 00:00 UTC, for contacts whose date custom field equals today's date. |
| `contact.visits_url`, `contact.on_purchase`, `contact.on_event` | contact | Accepted, but Emailit doesn't fire these yet. |
| `email.received` | email | An inbound email arrives. |
| `email.delivered`, `email.bounced`, `email.complained`, `email.loaded`, `email.clicked`, `email.failed`, `email.suppressed`, `email.canceled` | email | The email event of the same name occurs. |
| `event.<name>` | event | Any name that starts with `event.`. Emailit doesn't emit `event.*` events yet; start event automations with `system.manual`. |
| `system.manual` | all | You call [Trigger a run](/docs/api-reference/automations/trigger/). |
| `system.schedule` | all | Accepted, but Emailit doesn't fire scheduled triggers yet. |

Trigger `config` fields:

- `audience_id` (string): For `contact.added_to_audience` and `contact.removed_from_audience`: only fire for this audience (`aud_…`).

- `date_field` (string): For `contact.date_anniversary` and `contact.on_date`: the [custom field](/docs/contacts/custom-fields/) key that holds the date, for example `birthday`.

- `filter` (object): Only fire when the event matches: `{ "match": "all", "rules": [{ "field": "subject", "operator": "contains", "value": "Invoice" }] }`. `match` is `all` (default) or `any`. `field` is a dotted path into the event's `object`, for example `to` or `email.subject`; a leading `payload.` is ignored. Operators: `equals`, `not_equals`, `contains`, `not_contains`, `starts_with`, `ends_with`, `greater_than`, `less_than`, `in`, `not_in`, `is_set`, `is_not_set`. Every operator except `is_set` and `is_not_set` needs a `value`; `in` and `not_in` take an array.

## Actions

| Action | Context | Config |
| --- | --- | --- |
| `send_email` | all | `type` (required, `template`), `template_id` (required: a `tem_` ID or the alias of a published template), `from`, `subject`, `reply_to`, `to` |
| `forward_email` | email, event | `to` (required), `from`, `subject`, `email_id` |
| `wait` | all | `seconds` (required, 0 to 2,592,000, which is 30 days) |
| `condition` | all | `filter` (required, see below) |
| `experiment` | all | `variants` (required), `control` |
| `call_webhook` | all | `url` (required), `method`, `headers`, `body` |
| `run_automation` | all | `automation_id` (required) |
| `end` | all | None |
| `add_to_audience` | contact | `audience_id` (required) |
| `remove_from_audience` | contact | `audience_id` (required) |
| `edit_contact` | contact | `fields` (required) |
| `add_to_suppressions` | email, event | `type`, `reason`, `email` |
| `remove_from_suppressions` | email, event | `email` |
| `create_contact` | email, event | `email`, `first_name`, `audience_id` |

- **`send_email`** sends the template. `from` defaults to the template's sender and must be on a verified sending domain. `subject` overrides the template subject. `reply_to` is an address or an array of addresses. In the `contact` context the email goes to the run's contact; in the `email` and `event` contexts set `to`.
- **`forward_email`** forwards the run's email (or the email in `email_id`) to `to`. `from` defaults to the original sender and `subject` to `Fwd: <original subject>`.
- **`condition`** takes `{ "match": "all" | "any", "rules": [...] }` with the same operators as trigger filters. Bare fields resolve against the run's contact (`first_name`, `custom_fields.plan`) or email (`rcpt_to`, `subject`); prefix a field with `contact.`, `email.`, `payload.` or `meta.` to be explicit. The step continues on `yes` or `no`.
- **`experiment`** picks one of `variants` (and `control`), each `{ "key": "a", "weight": 50 }`, at random by weight, and continues on the branch named after the chosen key.
- **`call_webhook`** sends an HTTP request (default method `POST`, JSON `Content-Type`) and records the status class (`2xx`, `4xx`, `5xx`) or `timeout` or `network_error`. A non-2xx response doesn't fail the step.
- **`run_automation`** starts a run of another running automation with this run's payload. The current run continues.
- **`edit_contact`** takes `fields: [{ "key": "first_name", "value": "Ada" }]`. The keys `email`, `first_name`, `last_name` and `unsubscribed` update the contact; any other key sets a custom field.
- **`add_to_suppressions`** suppresses the run's address (default `type` `recipient`, default `reason` `automation`). **`remove_from_suppressions`** removes it. In the `event` context, pass `email`.
- **`create_contact`** creates the contact (or finds the existing one) and optionally subscribes it to `audience_id`. In the `email` context, `email` defaults to the email's recipient.

String values in any action config can use placeholders that Emailit fills in when the step runs: `{{contact.email}}`, `{{email.mail_from}}`, `{{payload.object.subject}}` or `{{meta.source_event_id}}`, for example `"to": "{{email.mail_from}}"`. Templates sent by `send_email` also render the contact's fields directly, such as `{{ first_name }}`.

## Returns

Returns `201 Created` with the automation in `data`, including each step's ID (`aus_…`) and the connections. `status` is `draft`.

Create checks the graph structure: trigger and action names for the context, unique keys, valid connections, reachability, and that any `send_email` `from` address uses a verified sending domain. It doesn't check that every action config is complete; [Update an automation](/docs/api-reference/automations/update/) does. Errors return `400` with `errors` keyed by field path.

**Request** `POST /automations`

**cURL**

```bash
curl -X POST https://api.emailit.com/v2/automations \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Welcome series",
    "context": "contact",
    "description": "Welcome new subscribers, then nudge free users a day later.",
    "settings": { "allow_reentry": false },
    "steps": [
      {
        "key": "joined",
        "type": "trigger",
        "trigger": "contact.added_to_audience",
        "config": { "audience_id": "aud_3Cz7WJ9GpvD7P0PQez50xOmkZSV" }
      },
      {
        "key": "welcome_email",
        "type": "action",
        "action": "send_email",
        "config": { "type": "template", "template_id": "welcome", "from": "Acme <hello@acme.com>" }
      },
      {
        "key": "wait_1_day",
        "type": "action",
        "action": "wait",
        "config": { "seconds": 86400 }
      },
      {
        "key": "is_free_user",
        "type": "action",
        "action": "condition",
        "config": {
          "filter": {
            "match": "all",
            "rules": [{ "field": "custom_fields.plan", "operator": "is_not_set" }]
          }
        }
      },
      {
        "key": "upgrade_tips",
        "type": "action",
        "action": "send_email",
        "config": { "type": "template", "template_id": "getting-started-tips" }
      },
      { "key": "done", "type": "action", "action": "end" }
    ],
    "connections": [
      { "from": "joined", "to": "welcome_email" },
      { "from": "welcome_email", "to": "wait_1_day" },
      { "from": "wait_1_day", "to": "is_free_user" },
      { "from": "is_free_user", "to": "upgrade_tips", "branch": "yes" },
      { "from": "is_free_user", "to": "done", "branch": "no" }
    ]
  }'
```

**Node.js**

```javascript
const res = await fetch('https://api.emailit.com/v2/automations', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EMAILIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Welcome series',
    context: 'contact',
    description: 'Welcome new subscribers, then nudge free users a day later.',
    settings: { allow_reentry: false },
    steps: [
      {
        key: 'joined',
        type: 'trigger',
        trigger: 'contact.added_to_audience',
        config: { audience_id: 'aud_3Cz7WJ9GpvD7P0PQez50xOmkZSV' },
      },
      {
        key: 'welcome_email',
        type: 'action',
        action: 'send_email',
        config: { type: 'template', template_id: 'welcome', from: 'Acme <hello@acme.com>' },
      },
      { key: 'wait_1_day', type: 'action', action: 'wait', config: { seconds: 86400 } },
      {
        key: 'is_free_user',
        type: 'action',
        action: 'condition',
        config: {
          filter: { match: 'all', rules: [{ field: 'custom_fields.plan', operator: 'is_not_set' }] },
        },
      },
      {
        key: 'upgrade_tips',
        type: 'action',
        action: 'send_email',
        config: { type: 'template', template_id: 'getting-started-tips' },
      },
      { key: 'done', type: 'action', action: 'end' },
    ],
    connections: [
      { from: 'joined', to: 'welcome_email' },
      { from: 'welcome_email', to: 'wait_1_day' },
      { from: 'wait_1_day', to: 'is_free_user' },
      { from: 'is_free_user', to: 'upgrade_tips', branch: 'yes' },
      { from: 'is_free_user', to: 'done', branch: 'no' },
    ],
  }),
});
const { data: automation } = await res.json();
```

**Python**

```python
import os, requests

r = requests.post(
    "https://api.emailit.com/v2/automations",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
    json={
        "name": "Welcome series",
        "context": "contact",
        "description": "Welcome new subscribers, then nudge free users a day later.",
        "settings": {"allow_reentry": False},
        "steps": [
            {
                "key": "joined",
                "type": "trigger",
                "trigger": "contact.added_to_audience",
                "config": {"audience_id": "aud_3Cz7WJ9GpvD7P0PQez50xOmkZSV"},
            },
            {
                "key": "welcome_email",
                "type": "action",
                "action": "send_email",
                "config": {"type": "template", "template_id": "welcome", "from": "Acme <hello@acme.com>"},
            },
            {"key": "wait_1_day", "type": "action", "action": "wait", "config": {"seconds": 86400}},
            {
                "key": "is_free_user",
                "type": "action",
                "action": "condition",
                "config": {
                    "filter": {
                        "match": "all",
                        "rules": [{"field": "custom_fields.plan", "operator": "is_not_set"}],
                    }
                },
            },
            {
                "key": "upgrade_tips",
                "type": "action",
                "action": "send_email",
                "config": {"type": "template", "template_id": "getting-started-tips"},
            },
            {"key": "done", "type": "action", "action": "end"},
        ],
        "connections": [
            {"from": "joined", "to": "welcome_email"},
            {"from": "welcome_email", "to": "wait_1_day"},
            {"from": "wait_1_day", "to": "is_free_user"},
            {"from": "is_free_user", "to": "upgrade_tips", "branch": "yes"},
            {"from": "is_free_user", "to": "done", "branch": "no"},
        ],
    },
)
automation = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->post('automations', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
    'json' => [
        'name' => 'Welcome series',
        'context' => 'contact',
        'description' => 'Welcome new subscribers, then nudge free users a day later.',
        'settings' => ['allow_reentry' => false],
        'steps' => [
            [
                'key' => 'joined',
                'type' => 'trigger',
                'trigger' => 'contact.added_to_audience',
                'config' => ['audience_id' => 'aud_3Cz7WJ9GpvD7P0PQez50xOmkZSV'],
            ],
            [
                'key' => 'welcome_email',
                'type' => 'action',
                'action' => 'send_email',
                'config' => ['type' => 'template', 'template_id' => 'welcome', 'from' => 'Acme <hello@acme.com>'],
            ],
            ['key' => 'wait_1_day', 'type' => 'action', 'action' => 'wait', 'config' => ['seconds' => 86400]],
            [
                'key' => 'is_free_user',
                'type' => 'action',
                'action' => 'condition',
                'config' => [
                    'filter' => [
                        'match' => 'all',
                        'rules' => [['field' => 'custom_fields.plan', 'operator' => 'is_not_set']],
                    ],
                ],
            ],
            [
                'key' => 'upgrade_tips',
                'type' => 'action',
                'action' => 'send_email',
                'config' => ['type' => 'template', 'template_id' => 'getting-started-tips'],
            ],
            ['key' => 'done', 'type' => 'action', 'action' => 'end'],
        ],
        'connections' => [
            ['from' => 'joined', 'to' => 'welcome_email'],
            ['from' => 'welcome_email', 'to' => 'wait_1_day'],
            ['from' => 'wait_1_day', 'to' => 'is_free_user'],
            ['from' => 'is_free_user', 'to' => 'upgrade_tips', 'branch' => 'yes'],
            ['from' => 'is_free_user', 'to' => 'done', 'branch' => 'no'],
        ],
    ],
]);
$automation = json_decode($response->getBody(), true)['data'];
```

**201**

```json
{
  "data": {
    "id": "aut_3xqC9YD79FZZA36uTekWTBO1ghe",
    "context": "contact",
    "name": "Welcome series",
    "description": "Welcome new subscribers, then nudge free users a day later.",
    "status": "draft",
    "settings": { "allow_reentry": false },
    "last_triggered_at": null,
    "published_at": null,
    "created_at": "2026-10-01T09:41:05.318274+00:00",
    "updated_at": "2026-10-01T09:41:05.318274+00:00",
    "steps": [
      {
        "id": "aus_3ZqpuxjmL1szZ8og6uGL1mT6QEJ",
        "key": "joined",
        "type": "trigger",
        "trigger": "contact.added_to_audience",
        "action": null,
        "config": { "audience_id": "aud_3Cz7WJ9GpvD7P0PQez50xOmkZSV" }
      },
      {
        "id": "aus_3V5TZIXXak1SSmGcOmzPOeFaguQ",
        "key": "welcome_email",
        "type": "action",
        "trigger": null,
        "action": "send_email",
        "config": { "type": "template", "template_id": "welcome", "from": "Acme <hello@acme.com>" }
      },
      {
        "id": "aus_3vM0ORwEXCQcyS7rylfQBQEXjVk",
        "key": "wait_1_day",
        "type": "action",
        "trigger": null,
        "action": "wait",
        "config": { "seconds": 86400 }
      },
      {
        "id": "aus_3HOgOzxaXBgVRpLFtpvJNo4vd5c",
        "key": "is_free_user",
        "type": "action",
        "trigger": null,
        "action": "condition",
        "config": {
          "filter": {
            "match": "all",
            "rules": [{ "field": "custom_fields.plan", "operator": "is_not_set" }]
          }
        }
      },
      {
        "id": "aus_3gCI5SWMPFVhOSawR6nz8sF55wp",
        "key": "upgrade_tips",
        "type": "action",
        "trigger": null,
        "action": "send_email",
        "config": { "type": "template", "template_id": "getting-started-tips" }
      },
      {
        "id": "aus_3Pq7Wd2LxN8cVt5RmK0sHy4BfJe",
        "key": "done",
        "type": "action",
        "trigger": null,
        "action": "end",
        "config": {}
      }
    ],
    "connections": [
      { "from": "joined", "to": "welcome_email", "branch": "default" },
      { "from": "welcome_email", "to": "wait_1_day", "branch": "default" },
      { "from": "wait_1_day", "to": "is_free_user", "branch": "default" },
      { "from": "is_free_user", "to": "upgrade_tips", "branch": "yes" },
      { "from": "is_free_user", "to": "done", "branch": "no" }
    ]
  },
  "message": "Automation was successfully created.",
  "notify": true
}
```

**400**

```json
{
  "message": "Validation failed.",
  "errors": {
    "steps.1.action": ["Action \"forward_email\" is not allowed for context \"contact\"."],
    "steps": ["Action step \"upgrade_tips\" is not reachable from any trigger."]
  }
}
```

---
Source: https://emailit.com/docs/api-reference/automations/create/

## Retrieve an automation — GET /automations/{id}

> Retrieve an automation with its status, settings, trigger and action steps, and the connections between them.

# Retrieve an automation

Retrieves an automation with its full graph. Requires an API key with the `full` scope.

`GET /automations/{id}`

## Path parameters

- `id` (string, required): The automation ID (`aut_…`).

## Returns

Returns the automation in `data`.

- `id` (string): Automation ID, prefixed `aut_`.

- `context` (string): `contact`, `email` or `event`.

- `name` (string): Name of the automation.

- `description` (string | null): Optional description.

- `status` (string): `draft`, `running`, `paused`, `stopped` or `archived`.

- `settings` (object): Run rules: `on_step_failure`, `allow_reentry`, `max_concurrent_runs`, `cooldown_seconds`. Empty when you haven't set any.

- `last_triggered_at` (string | null): When the automation last started a run.

- `published_at` (string | null): When the automation was first started.

- `steps` (object[]): Each step's `id` (`aus_…`), `key`, `type`, `trigger`, `action` and `config`. Run details refer to steps by `id`; statistics refer to them by `key`.

- `connections` (object[]): Each edge's `from` and `to` step keys and its `branch`.

See [Create an automation](/docs/api-reference/automations/create/) for the meaning of every trigger, action and setting. Returns `404` if the automation doesn't exist or was deleted.

**Request** `GET /automations/{id}`

**cURL**

```bash
curl https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
```

**Node.js**

```javascript
const res = await fetch('https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe', {
  headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` },
});
const { data: automation } = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
)
automation = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
]);
$automation = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": {
    "id": "aut_3xqC9YD79FZZA36uTekWTBO1ghe",
    "context": "contact",
    "name": "Welcome series",
    "description": "Welcome new subscribers, then nudge free users a day later.",
    "status": "running",
    "settings": { "allow_reentry": false },
    "last_triggered_at": "2026-10-03T14:12:40.551870+00:00",
    "published_at": "2026-10-01T10:00:02.204118+00:00",
    "created_at": "2026-10-01T09:41:05.318274+00:00",
    "updated_at": "2026-10-03T14:12:40.551870+00:00",
    "steps": [
      {
        "id": "aus_3ZqpuxjmL1szZ8og6uGL1mT6QEJ",
        "key": "joined",
        "type": "trigger",
        "trigger": "contact.added_to_audience",
        "action": null,
        "config": { "audience_id": "aud_3Cz7WJ9GpvD7P0PQez50xOmkZSV" }
      },
      {
        "id": "aus_3V5TZIXXak1SSmGcOmzPOeFaguQ",
        "key": "welcome_email",
        "type": "action",
        "trigger": null,
        "action": "send_email",
        "config": { "type": "template", "template_id": "welcome", "from": "Acme <hello@acme.com>" }
      },
      {
        "id": "aus_3vM0ORwEXCQcyS7rylfQBQEXjVk",
        "key": "wait_1_day",
        "type": "action",
        "trigger": null,
        "action": "wait",
        "config": { "seconds": 86400 }
      }
    ],
    "connections": [
      { "from": "joined", "to": "welcome_email", "branch": "default" },
      { "from": "welcome_email", "to": "wait_1_day", "branch": "default" }
    ]
  }
}
```

**404**

```json
{
  "message": "Automation not found."
}
```

---
Source: https://emailit.com/docs/api-reference/automations/get/

## Update an automation — POST /automations/{id}

> Rename an automation, change its settings, or replace its graph of steps and connections with full validation.

# Update an automation

Updates an automation's name, description, settings or graph. Requires an API key with the `full` scope. The context can't be changed.

To change the graph, send `steps` and `connections` together; they replace the current graph. Steps whose `key` already exists keep their ID and run history, steps you leave out are deleted, and new keys are added. Unlike create, update also validates every action's config (for example, `send_email` needs `type` and `template_id`, `wait` needs `seconds`).

[Pause the automation](/docs/api-reference/automations/pause/) before you change the graph of a running automation, then [start it](/docs/api-reference/automations/start/) again so the new triggers take effect.

`POST /automations/{id}`

## Path parameters

- `id` (string, required): The automation ID (`aut_…`).

## Body parameters

- `name` (string): Name, up to 191 characters.

- `description` (string): Description.

- `settings` (object): Replaces all settings: `on_step_failure`, `allow_reentry`, `max_concurrent_runs`, `cooldown_seconds`. See [Settings](/docs/api-reference/automations/create/#settings).

- `steps` (object[]): The complete list of steps. Required when you send `connections`. See [Steps](/docs/api-reference/automations/create/#steps).

- `connections` (object[]): The complete list of connections. Required when you send `steps`. See [Connections](/docs/api-reference/automations/create/#connections).

## Returns

Returns the updated automation in `data`, with `message` and `notify`. Returns `400` with `errors` keyed by field path when validation fails, and `404` if the automation doesn't exist.

**Request** `POST /automations/{id}`

**cURL**

```bash
curl -X POST https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Welcome series (v2)",
    "settings": { "allow_reentry": false, "on_step_failure": "skip" }
  }'
```

**Node.js**

```javascript
const res = await fetch('https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EMAILIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Welcome series (v2)',
    settings: { allow_reentry: false, on_step_failure: 'skip' },
  }),
});
const { data: automation } = await res.json();
```

**Python**

```python
import os, requests

r = requests.post(
    "https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
    json={
        "name": "Welcome series (v2)",
        "settings": {"allow_reentry": False, "on_step_failure": "skip"},
    },
)
automation = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->post('automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
    'json' => [
        'name' => 'Welcome series (v2)',
        'settings' => ['allow_reentry' => false, 'on_step_failure' => 'skip'],
    ],
]);
$automation = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": {
    "id": "aut_3xqC9YD79FZZA36uTekWTBO1ghe",
    "context": "contact",
    "name": "Welcome series (v2)",
    "description": "Welcome new subscribers, then nudge free users a day later.",
    "status": "paused",
    "settings": { "allow_reentry": false, "on_step_failure": "skip" },
    "last_triggered_at": "2026-10-03T14:12:40.551870+00:00",
    "published_at": "2026-10-01T10:00:02.204118+00:00",
    "created_at": "2026-10-01T09:41:05.318274+00:00",
    "updated_at": "2026-10-04T08:15:22.730115+00:00",
    "steps": [
      {
        "id": "aus_3ZqpuxjmL1szZ8og6uGL1mT6QEJ",
        "key": "joined",
        "type": "trigger",
        "trigger": "contact.added_to_audience",
        "action": null,
        "config": { "audience_id": "aud_3Cz7WJ9GpvD7P0PQez50xOmkZSV" }
      },
      {
        "id": "aus_3V5TZIXXak1SSmGcOmzPOeFaguQ",
        "key": "welcome_email",
        "type": "action",
        "trigger": null,
        "action": "send_email",
        "config": { "type": "template", "template_id": "welcome", "from": "Acme <hello@acme.com>" }
      }
    ],
    "connections": [
      { "from": "joined", "to": "welcome_email", "branch": "default" }
    ]
  },
  "message": "Automation was successfully updated.",
  "notify": true
}
```

**400**

```json
{
  "message": "Validation failed.",
  "errors": {
    "steps.2.config.seconds": ["Wait seconds cannot exceed 2592000 (30 days)."],
    "steps.1.config.template_id": ["Template ID is required when type is \"template\"."]
  }
}
```

**404**

```json
{
  "message": "Automation not found."
}
```

---
Source: https://emailit.com/docs/api-reference/automations/update/

## List automations — GET /automations

> List a workspace's automations with page and per_page pagination, filtered by context, status or name.

# List automations

Returns the workspace's automations, newest first, without their steps and connections. Requires an API key with the `full` scope. Deleted automations aren't listed.

`GET /automations`

## Query parameters

- `page` (integer): Page number, starting at 1.

- `per_page` (integer): Automations per page, from 1 to 100.

- `filter[context]` (string): Only automations with this context: `contact`, `email` or `event`.

- `filter[status]` (string): Only automations with this status: `draft`, `running`, `paused`, `stopped` or `archived`.

- `filter[name]` (string): Case-insensitive match on part of the name.

- `sort` (string): Sort field: `name`, `created_at`, `updated_at` or `last_triggered_at`.

- `order` (string): Sort direction: `asc` or `desc`.

You can also use the generic `key.condition=value` filters on `name`, `status`, `context` and `created_at`, with `match`. See [Filtering](/docs/api-reference/filtering/).

## Returns

- `data` (object[]): Automations on this page: `id`, `context`, `name`, `description`, `status`, `settings`, `last_triggered_at`, `published_at`, `created_at`, `updated_at`. `settings` is always an empty object in this list; [retrieve the automation](/docs/api-reference/automations/get/) to read it.

- `total_records` (integer): Number of automations that match.

- `per_page` (integer): Page size used.

- `current_page` (integer): This page's number.

- `total_pages` (integer): Number of pages.

**Request** `GET /automations`

**cURL**

```bash
curl -G https://api.emailit.com/v2/automations \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  --data-urlencode "filter[status]=running" \
  -d sort=last_triggered_at \
  -d per_page=50
```

**Node.js**

```javascript
const params = new URLSearchParams({ 'filter[status]': 'running', sort: 'last_triggered_at', per_page: '50' });
const res = await fetch(`https://api.emailit.com/v2/automations?${params}`, {
  headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` },
});
const { data, total_pages } = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/automations",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
    params={"filter[status]": "running", "sort": "last_triggered_at", "per_page": 50},
)
automations = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('automations', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
    'query' => ['filter[status]' => 'running', 'sort' => 'last_triggered_at', 'per_page' => 50],
]);
$automations = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": [
    {
      "id": "aut_3xqC9YD79FZZA36uTekWTBO1ghe",
      "context": "contact",
      "name": "Welcome series",
      "description": "Welcome new subscribers, then nudge free users a day later.",
      "status": "running",
      "settings": {},
      "last_triggered_at": "2026-10-03T14:12:40.551870+00:00",
      "published_at": "2026-10-01T10:00:02.204118+00:00",
      "created_at": "2026-10-01T09:41:05.318274+00:00",
      "updated_at": "2026-10-03T14:12:40.551870+00:00"
    }
  ],
  "total_records": 1,
  "per_page": 50,
  "current_page": 1,
  "total_pages": 1
}
```

---
Source: https://emailit.com/docs/api-reference/automations/list/

## Delete an automation — DELETE /automations/{id}

> Delete an automation so it stops reacting to triggers. Stop it first if you also want to cancel runs in progress.

# Delete an automation

Deletes an automation. It disappears from lists and stops reacting to triggers. Requires an API key with the `full` scope.

Runs that are already in progress aren't canceled. To cancel them, [stop the automation](/docs/api-reference/automations/stop/) before you delete it.

`DELETE /automations/{id}`

## Path parameters

- `id` (string, required): The automation ID (`aut_…`).

## Returns

Returns a `message` confirming the deletion. Returns `404` if the automation doesn't exist or was already deleted.

**Request** `DELETE /automations/{id}`

**cURL**

```bash
curl -X DELETE https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
```

**Node.js**

```javascript
const res = await fetch('https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` },
});
const result = await res.json();
```

**Python**

```python
import os, requests

r = requests.delete(
    "https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
)
result = r.json()
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->delete('automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
]);
$result = json_decode($response->getBody(), true);
```

**200**

```json
{
  "message": "Automation was deleted successfully.",
  "notify": true
}
```

**404**

```json
{
  "message": "Automation not found."
}
```

---
Source: https://emailit.com/docs/api-reference/automations/delete/

## Start an automation — POST /automations/{id}/start

> Set an automation to running so its triggers start new runs. Works for draft, paused and stopped automations.

# Start an automation

Sets the automation's status to `running`. From then on, matching triggers start runs; events that happened before you started it don't. Requires an API key with the `full` scope.

You can start a `draft`, `paused` or `stopped` automation. The first start sets `published_at`.

Starting doesn't validate the graph again. If you built the automation with [Create an automation](/docs/api-reference/automations/create/), which only checks structure, make sure every action config is complete, or send the graph once through [Update an automation](/docs/api-reference/automations/update/), which validates it fully. A step with an incomplete config fails when a run reaches it.

`POST /automations/{id}/start`

## Path parameters

- `id` (string, required): The automation ID (`aut_…`).

## Returns

Returns the automation in `data` with `status` set to `running`. Returns `404` if the automation doesn't exist.

**Request** `POST /automations/{id}/start`

**cURL**

```bash
curl -X POST https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/start \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
```

**Node.js**

```javascript
const res = await fetch('https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/start', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` },
});
const { data: automation } = await res.json();
```

**Python**

```python
import os, requests

r = requests.post(
    "https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/start",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
)
automation = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->post('automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/start', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
]);
$automation = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": {
    "id": "aut_3xqC9YD79FZZA36uTekWTBO1ghe",
    "context": "contact",
    "name": "Welcome series",
    "description": "Welcome new subscribers, then nudge free users a day later.",
    "status": "running",
    "settings": { "allow_reentry": false },
    "last_triggered_at": null,
    "published_at": "2026-10-01T10:00:02.204118+00:00",
    "created_at": "2026-10-01T09:41:05.318274+00:00",
    "updated_at": "2026-10-01T10:00:02.204118+00:00",
    "steps": [
      {
        "id": "aus_3ZqpuxjmL1szZ8og6uGL1mT6QEJ",
        "key": "joined",
        "type": "trigger",
        "trigger": "contact.added_to_audience",
        "action": null,
        "config": { "audience_id": "aud_3Cz7WJ9GpvD7P0PQez50xOmkZSV" }
      },
      {
        "id": "aus_3V5TZIXXak1SSmGcOmzPOeFaguQ",
        "key": "welcome_email",
        "type": "action",
        "trigger": null,
        "action": "send_email",
        "config": { "type": "template", "template_id": "welcome", "from": "Acme <hello@acme.com>" }
      }
    ],
    "connections": [
      { "from": "joined", "to": "welcome_email", "branch": "default" }
    ]
  },
  "message": "Automation was successfully started.",
  "notify": true
}
```

**404**

```json
{
  "message": "Automation not found."
}
```

---
Source: https://emailit.com/docs/api-reference/automations/start/

## Pause an automation — POST /automations/{id}/pause

> Pause an automation so its triggers stop starting new runs, while runs already in progress continue to the end.

# Pause an automation

Sets the automation's status to `paused`. Its triggers stop starting new runs, but runs already in progress continue, including those waiting on a `wait` step. Requires an API key with the `full` scope.

To also cancel runs in progress, [stop the automation](/docs/api-reference/automations/stop/) instead. [Start it](/docs/api-reference/automations/start/) again to resume triggering.

`POST /automations/{id}/pause`

## Path parameters

- `id` (string, required): The automation ID (`aut_…`).

## Returns

Returns the automation in `data` with `status` set to `paused`. Returns `404` if the automation doesn't exist.

**Request** `POST /automations/{id}/pause`

**cURL**

```bash
curl -X POST https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/pause \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
```

**Node.js**

```javascript
const res = await fetch('https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/pause', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` },
});
const { data: automation } = await res.json();
```

**Python**

```python
import os, requests

r = requests.post(
    "https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/pause",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
)
automation = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->post('automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/pause', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
]);
$automation = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": {
    "id": "aut_3xqC9YD79FZZA36uTekWTBO1ghe",
    "context": "contact",
    "name": "Welcome series",
    "description": "Welcome new subscribers, then nudge free users a day later.",
    "status": "paused",
    "settings": { "allow_reentry": false },
    "last_triggered_at": "2026-10-03T14:12:40.551870+00:00",
    "published_at": "2026-10-01T10:00:02.204118+00:00",
    "created_at": "2026-10-01T09:41:05.318274+00:00",
    "updated_at": "2026-10-04T08:10:51.004732+00:00",
    "steps": [
      {
        "id": "aus_3ZqpuxjmL1szZ8og6uGL1mT6QEJ",
        "key": "joined",
        "type": "trigger",
        "trigger": "contact.added_to_audience",
        "action": null,
        "config": { "audience_id": "aud_3Cz7WJ9GpvD7P0PQez50xOmkZSV" }
      },
      {
        "id": "aus_3V5TZIXXak1SSmGcOmzPOeFaguQ",
        "key": "welcome_email",
        "type": "action",
        "trigger": null,
        "action": "send_email",
        "config": { "type": "template", "template_id": "welcome", "from": "Acme <hello@acme.com>" }
      }
    ],
    "connections": [
      { "from": "joined", "to": "welcome_email", "branch": "default" }
    ]
  },
  "message": "Automation was successfully paused.",
  "notify": true
}
```

**404**

```json
{
  "message": "Automation not found."
}
```

---
Source: https://emailit.com/docs/api-reference/automations/pause/

## Stop an automation — POST /automations/{id}/stop

> Stop an automation and cancel all of its runs in progress. Only available through the API, not in the dashboard.

# Stop an automation

Sets the automation's status to `stopped` and cancels every run that is still `running`; those runs get the status `canceled` and their remaining steps don't execute. Requires an API key with the `full` scope.

Stopping is only available through the API. To keep runs in progress going, [pause the automation](/docs/api-reference/automations/pause/) instead. You can [start](/docs/api-reference/automations/start/) a stopped automation again; new runs start from scratch.

`POST /automations/{id}/stop`

## Path parameters

- `id` (string, required): The automation ID (`aut_…`).

## Returns

Returns the automation in `data` with `status` set to `stopped`. Returns `404` if the automation doesn't exist.

**Request** `POST /automations/{id}/stop`

**cURL**

```bash
curl -X POST https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/stop \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
```

**Node.js**

```javascript
const res = await fetch('https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/stop', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` },
});
const { data: automation } = await res.json();
```

**Python**

```python
import os, requests

r = requests.post(
    "https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/stop",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
)
automation = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->post('automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/stop', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
]);
$automation = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": {
    "id": "aut_3xqC9YD79FZZA36uTekWTBO1ghe",
    "context": "contact",
    "name": "Welcome series",
    "description": "Welcome new subscribers, then nudge free users a day later.",
    "status": "stopped",
    "settings": { "allow_reentry": false },
    "last_triggered_at": "2026-10-03T14:12:40.551870+00:00",
    "published_at": "2026-10-01T10:00:02.204118+00:00",
    "created_at": "2026-10-01T09:41:05.318274+00:00",
    "updated_at": "2026-10-05T16:45:09.882301+00:00",
    "steps": [
      {
        "id": "aus_3ZqpuxjmL1szZ8og6uGL1mT6QEJ",
        "key": "joined",
        "type": "trigger",
        "trigger": "contact.added_to_audience",
        "action": null,
        "config": { "audience_id": "aud_3Cz7WJ9GpvD7P0PQez50xOmkZSV" }
      },
      {
        "id": "aus_3V5TZIXXak1SSmGcOmzPOeFaguQ",
        "key": "welcome_email",
        "type": "action",
        "trigger": null,
        "action": "send_email",
        "config": { "type": "template", "template_id": "welcome", "from": "Acme <hello@acme.com>" }
      }
    ],
    "connections": [
      { "from": "joined", "to": "welcome_email", "branch": "default" }
    ]
  },
  "message": "Automation was successfully stopped.",
  "notify": true
}
```

**404**

```json
{
  "message": "Automation not found."
}
```

---
Source: https://emailit.com/docs/api-reference/automations/stop/

## Trigger a run — POST /automations/{id}/trigger

> Fire the system.manual trigger of a running automation with your own payload to start a run from your code.

# Trigger a run

Fires the `system.manual` trigger with a payload you choose. The automation must be `running` and have a `system.manual` trigger step; otherwise no run starts. Requires an API key with the `full` scope. Manual triggers are only available through the API.

The run starts asynchronously and costs 3 credits like any other run. Find it with [List runs](/docs/api-reference/automations/runs/).

> **Every manual trigger in the workspace fires:** Emailit currently delivers the manual trigger to every running automation in the workspace that has a `system.manual` trigger step, not only to the one in the path. To limit an automation to its own calls, add a filter to its trigger step: `{ "match": "all", "rules": [{ "field": "automation_id", "operator": "equals", "value": "aut_…" }] }`.

`POST /automations/{id}/trigger`

## Path parameters

- `id` (string, required): The automation ID (`aut_…`).

## Body parameters

- `payload` (object): Data for the run. Emailit adds `automation_id` and stores the result as the run's `payload`. Steps can read it with placeholders such as `{{payload.order_id}}` and conditions such as `payload.plan`. What the run is about depends on the automation's context: - `contact`: pass `contact_id` (`con_…`). Contact actions and `send_email` use that contact. - `email`: pass `email_id` (`em_…`). Email actions use that email. - `event`: any data. Set `to` or `email` in the action configs, for example `"to": "{{payload.customer_email}}"`.

## Returns

Returns `200` with a `message` once the trigger is queued. Returns `422` if the automation isn't `running` and `404` if it doesn't exist.

**Request** `POST /automations/{id}/trigger`

**cURL**

```bash
curl -X POST https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/trigger \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "payload": {
      "contact_id": "con_3munwNLaXKUARc6ff9wPtxKVq4A",
      "plan": "pro",
      "order_id": "ord_1042"
    }
  }'
```

**Node.js**

```javascript
const res = await fetch('https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/trigger', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EMAILIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    payload: { contact_id: 'con_3munwNLaXKUARc6ff9wPtxKVq4A', plan: 'pro', order_id: 'ord_1042' },
  }),
});
const result = await res.json();
```

**Python**

```python
import os, requests

r = requests.post(
    "https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/trigger",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
    json={"payload": {"contact_id": "con_3munwNLaXKUARc6ff9wPtxKVq4A", "plan": "pro", "order_id": "ord_1042"}},
)
result = r.json()
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->post('automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/trigger', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
    'json' => [
        'payload' => ['contact_id' => 'con_3munwNLaXKUARc6ff9wPtxKVq4A', 'plan' => 'pro', 'order_id' => 'ord_1042'],
    ],
]);
$result = json_decode($response->getBody(), true);
```

**200**

```json
{
  "message": "Automation trigger dispatched."
}
```

**422**

```json
{
  "message": "Automation must be running to trigger."
}
```

**404**

```json
{
  "message": "Automation not found."
}
```

---
Source: https://emailit.com/docs/api-reference/automations/trigger/

## List runs — GET /automations/{id}/runs

> List the runs of an automation, newest first, with the triggering event, payload, status and timing of each run.

# List runs

Returns an automation's runs, newest first. Each run is one pass through the graph, started by a trigger. Requires an API key with the `full` scope.

`GET /automations/{id}/runs`

## Path parameters

- `id` (string, required): The automation ID (`aut_…`).

## Query parameters

- `page` (integer): Page number, starting at 1.

- `per_page` (integer): Runs per page, from 1 to 100.

- `filter[status]` (string): Only runs with this status: `running`, `completed`, `failed` or `canceled`.

You can also filter with `key.condition=value` on `status`, `event` and `created_at` (for example `created_at.after=2026-10-01`), and sort with `order` and `direction` on the same keys. See [Filtering](/docs/api-reference/filtering/).

## Returns

- `data` (object[]): Runs on this page. See the fields below.

- `total_records, per_page, current_page, total_pages` (integer): Pagination details.

Each run has:

- `id` (string): Run ID, prefixed `aur_`.

- `automation_id` (string): The automation (`aut_…`).

- `contact_id` (string | null): The run's contact (`con_…`) in the `contact` context.

- `email_id` (string | null): The run's email (`em_…`) in the `email` context.

- `event_id` (string | null): Source event ID, when the trigger payload has one.

- `event` (string): The trigger that started the run, for example `contact.added_to_audience` or `system.manual`.

- `payload` (object): Always an empty object in this list. [Retrieve the run](/docs/api-reference/automations/run/) to read its trigger payload.

- `meta` (object): Always an empty object in this list. Retrieve the run to read its metadata, such as `failure_reason`.

- `status` (string): `running`, `completed`, `failed` or `canceled` (the automation was stopped).

- `started_at, completed_at, created_at, updated_at` (string | null): Timestamps in UTC.

**Request** `GET /automations/{id}/runs`

**cURL**

```bash
curl -G https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/runs \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  --data-urlencode "filter[status]=failed"
```

**Node.js**

```javascript
const params = new URLSearchParams({ 'filter[status]': 'failed' });
const res = await fetch(
  `https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/runs?${params}`,
  { headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` } },
);
const { data: runs } = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/runs",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
    params={"filter[status]": "failed"},
)
runs = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/runs', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
    'query' => ['filter[status]' => 'failed'],
]);
$runs = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": [
    {
      "id": "aur_3q6GdFk2eRSG093grI0v9e6REu8",
      "automation_id": "aut_3xqC9YD79FZZA36uTekWTBO1ghe",
      "contact_id": "con_3munwNLaXKUARc6ff9wPtxKVq4A",
      "email_id": null,
      "event_id": null,
      "event": "contact.added_to_audience",
      "payload": {},
      "meta": {},
      "status": "failed",
      "started_at": "2026-10-03T14:12:40.551870+00:00",
      "completed_at": "2026-10-03T14:12:41.093355+00:00",
      "created_at": "2026-10-03T14:12:40.551870+00:00",
      "updated_at": "2026-10-03T14:12:41.093355+00:00"
    }
  ],
  "total_records": 1,
  "per_page": 25,
  "current_page": 1,
  "total_pages": 1
}
```

**404**

```json
{
  "message": "Automation not found."
}
```

---
Source: https://emailit.com/docs/api-reference/automations/runs/

## Retrieve a run — GET /automations/{id}/runs/{run_id}

> Retrieve one automation run with its trigger payload, metadata and the status and result of every step it executed.

# Retrieve a run

Retrieves one run of an automation, including the steps it executed. Requires an API key with the `full` scope.

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

## Path parameters

- `id` (string, required): The automation ID (`aut_…`).

- `run_id` (string, required): The run ID (`aur_…`).

## Returns

Returns the run in `data` with the fields described in [List runs](/docs/api-reference/automations/runs/), plus the full `payload` and `meta` and a `run_steps` array.

- `payload` (object | null): The trigger payload: the event data (`{ "object": { … } }`) or the payload you passed to [Trigger a run](/docs/api-reference/automations/trigger/), with `automation_id` added.

- `meta` (object | null): `source_event_id` links the run to the event that started it. Failed runs can have `failure_reason`: `insufficient_credits` (the 3 run credits couldn't be charged) or `run_timeout` (the run was still `running` after 72 hours without a step waiting).

- `run_steps` (object[]): One entry per step the run reached: - `step_id`: the step's ID (`aus_…`). Match it to `steps[].id` from [Retrieve an automation](/docs/api-reference/automations/get/). - `status`: `running`, `waiting` (a `wait` step that hasn't elapsed), `completed` or `failed`. - `data`: the step's result. For example `send_email` returns `{ "result": "email_queued", "email_oid": "em_…", "to": "…" }`, `condition` returns `{ "result": true, "branch": "yes" }`, and failed steps return `{ "error": "…" }`. - `started_at`, `completed_at`, `created_at`.

Returns `404` if the automation or the run doesn't exist.

**Request** `GET /automations/{id}/runs/{run_id}`

**cURL**

```bash
curl https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/runs/aur_3q6GdFk2eRSG093grI0v9e6REu8 \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
```

**Node.js**

```javascript
const res = await fetch(
  'https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/runs/aur_3q6GdFk2eRSG093grI0v9e6REu8',
  { headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` } },
);
const { data: run } = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/runs/aur_3q6GdFk2eRSG093grI0v9e6REu8",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
)
run = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/runs/aur_3q6GdFk2eRSG093grI0v9e6REu8', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
]);
$run = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": {
    "id": "aur_3q6GdFk2eRSG093grI0v9e6REu8",
    "automation_id": "aut_3xqC9YD79FZZA36uTekWTBO1ghe",
    "contact_id": "con_3munwNLaXKUARc6ff9wPtxKVq4A",
    "email_id": null,
    "event_id": null,
    "event": "contact.added_to_audience",
    "payload": {
      "object": {
        "id": "sub_3Fh2pQx9LmZr4Wt7Nc0bVd8KsYe",
        "object": "subscriber",
        "subscribed": true,
        "audience": { "id": "aud_3Cz7WJ9GpvD7P0PQez50xOmkZSV", "name": "Newsletter" },
        "contact": { "id": "con_3munwNLaXKUARc6ff9wPtxKVq4A", "email": "ada@example.com" }
      }
    },
    "meta": { "source_event_id": "evt_3Kd8sWq1NzXc5Vb7Mt2LpRy0HgA" },
    "status": "running",
    "started_at": "2026-10-03T14:12:40.551870+00:00",
    "completed_at": null,
    "created_at": "2026-10-03T14:12:40.551870+00:00",
    "updated_at": "2026-10-03T14:12:40.551870+00:00",
    "run_steps": [
      {
        "step_id": "aus_3V5TZIXXak1SSmGcOmzPOeFaguQ",
        "status": "completed",
        "data": {
          "result": "email_queued",
          "outcome": "accepted",
          "email_oid": "em_3Cp8cMgPskzB8tIlgUyNJkpDp9O",
          "to": "ada@example.com"
        },
        "started_at": "2026-10-03T14:12:40.702113+00:00",
        "completed_at": "2026-10-03T14:12:40.918540+00:00",
        "created_at": "2026-10-03T14:12:40.702113+00:00"
      },
      {
        "step_id": "aus_3vM0ORwEXCQcyS7rylfQBQEXjVk",
        "status": "waiting",
        "data": { "status": "waiting", "delayMs": 86400000, "seconds": 86400 },
        "started_at": "2026-10-03T14:12:41.004221+00:00",
        "completed_at": null,
        "created_at": "2026-10-03T14:12:41.004221+00:00"
      }
    ]
  }
}
```

**404**

```json
{
  "message": "Run not found."
}
```

---
Source: https://emailit.com/docs/api-reference/automations/run/

## Retrieve statistics — GET /automations/{id}/stats

> Get per-step counts for an automation: how many runs reached each step, step statuses, outcomes and an email funnel.

# Retrieve statistics

Returns counts for every step of an automation, keyed by step key. Requires an API key with the `full` scope.

`GET /automations/{id}/stats`

## Path parameters

- `id` (string, required): The automation ID (`aut_…`).

## Query parameters

- `since` (string): Only count step executions created at or after this time. RFC 3339 date-time, for example `2026-10-01T00:00:00Z`.

- `until` (string): Only count step executions created at or before this time. RFC 3339 date-time.

- `run_ids[]` (string): Only count these runs (`aur_…`). Repeat the parameter for several runs.

## Returns

Returns `data`, an object with one entry per step key. Steps no run has reached have a `total` of `0`.

- `total` (integer): Number of times runs reached the step.

- `by_status` (object): Counts per step status: `running`, `waiting`, `completed`, `failed`.

- `by_outcome` (object): Counts per outcome. `send_email` and `forward_email`: `accepted`, then the email's latest state (`delivered`, `loaded`, `clicked`, `bounced`, `failed`, `complained`, `unsubscribed`, `canceled`). `condition`: `matched`, `not_matched`. `experiment`: the chosen variant key. `call_webhook`: `2xx`, `4xx`, `5xx`, `timeout`, `network_error`. Failed steps: `error`.

- `funnel` (object): Only for `send_email` steps. Cumulative counts: `accepted` includes every email that got further, `delivered` includes loaded and clicked emails, and `loaded` includes clicked ones. `bounced`, `failed`, `complained` and `unsubscribed` are plain counts.

**Request** `GET /automations/{id}/stats`

**cURL**

```bash
curl -G https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/stats \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -d since=2026-10-01T00:00:00Z
```

**Node.js**

```javascript
const params = new URLSearchParams({ since: '2026-10-01T00:00:00Z' });
const res = await fetch(
  `https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/stats?${params}`,
  { headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` } },
);
const { data: stats } = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/stats",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
    params={"since": "2026-10-01T00:00:00Z"},
)
stats = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/stats', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
    'query' => ['since' => '2026-10-01T00:00:00Z'],
]);
$stats = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": {
    "joined": { "total": 0, "by_status": {}, "by_outcome": {} },
    "welcome_email": {
      "total": 412,
      "by_status": { "completed": 409, "failed": 3 },
      "by_outcome": { "delivered": 251, "loaded": 98, "clicked": 41, "bounced": 19, "error": 3 },
      "funnel": {
        "accepted": 390,
        "delivered": 390,
        "loaded": 139,
        "clicked": 41,
        "bounced": 19,
        "failed": 0,
        "complained": 0,
        "unsubscribed": 0
      }
    },
    "wait_1_day": {
      "total": 409,
      "by_status": { "completed": 352, "waiting": 57 },
      "by_outcome": {}
    },
    "is_free_user": {
      "total": 352,
      "by_status": { "completed": 352 },
      "by_outcome": { "matched": 270, "not_matched": 82 }
    },
    "upgrade_tips": {
      "total": 270,
      "by_status": { "completed": 270 },
      "by_outcome": { "accepted": 12, "delivered": 180, "loaded": 61, "clicked": 17 },
      "funnel": {
        "accepted": 270,
        "delivered": 258,
        "loaded": 78,
        "clicked": 17,
        "bounced": 0,
        "failed": 0,
        "complained": 0,
        "unsubscribed": 0
      }
    },
    "done": {
      "total": 82,
      "by_status": { "completed": 82 },
      "by_outcome": {}
    }
  }
}
```

**404**

```json
{
  "message": "Automation not found."
}
```

---
Source: https://emailit.com/docs/api-reference/automations/stats/

## Retrieve step statistics — GET /automations/{id}/steps/{step_key}/stats

> Get the counts for a single automation step by its key: executions, statuses, outcomes and the email funnel.

# Retrieve step statistics

Returns the counts for one step of an automation. Requires an API key with the `full` scope. The fields are the same as in [Retrieve statistics](/docs/api-reference/automations/stats/).

`GET /automations/{id}/steps/{step_key}/stats`

## Path parameters

- `id` (string, required): The automation ID (`aut_…`).

- `step_key` (string, required): The step's `key`, for example `welcome_email`.

## Query parameters

- `since` (string): Only count step executions created at or after this time. RFC 3339 date-time.

- `until` (string): Only count step executions created at or before this time. RFC 3339 date-time.

## Returns

Returns `data` with `total`, `by_status`, `by_outcome` and, for `send_email` steps, `funnel`. Returns `404` if the automation or the step key doesn't exist.

**Request** `GET /automations/{id}/steps/{step_key}/stats`

**cURL**

```bash
curl https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/steps/welcome_email/stats \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
```

**Node.js**

```javascript
const res = await fetch(
  'https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/steps/welcome_email/stats',
  { headers: { Authorization: `Bearer ${process.env.EMAILIT_API_KEY}` } },
);
const { data: stats } = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    "https://api.emailit.com/v2/automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/steps/welcome_email/stats",
    headers={"Authorization": f"Bearer {os.environ['EMAILIT_API_KEY']}"},
)
stats = r.json()["data"]
```

**PHP**

```php
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.emailit.com/v2/']);

$response = $client->get('automations/aut_3xqC9YD79FZZA36uTekWTBO1ghe/steps/welcome_email/stats', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('EMAILIT_API_KEY')],
]);
$stats = json_decode($response->getBody(), true)['data'];
```

**200**

```json
{
  "data": {
    "total": 412,
    "by_status": { "completed": 409, "failed": 3 },
    "by_outcome": { "delivered": 251, "loaded": 98, "clicked": 41, "bounced": 19, "error": 3 },
    "funnel": {
      "accepted": 390,
      "delivered": 390,
      "loaded": 139,
      "clicked": 41,
      "bounced": 19,
      "failed": 0,
      "complained": 0,
      "unsubscribed": 0
    }
  }
}
```

**404**

```json
{
  "message": "Step not found."
}
```

---
Source: https://emailit.com/docs/api-reference/automations/step-stats/
