# Automations

Comment-to-DM rules that reply to people and capture leads.

When a comment (or DM) on an account matches your keywords, an automation
replies publicly and sends the person **one** private message — optionally
asking for their email address or phone number and saving what they send as a
lead.

```json
{
  "id": "d2a6f8b1-5c94-4e07-9b3a-1e7c0f4d6a25",
  "account_id": "a3d9f1c4-7b2e-4c60-9e85-1f4b6d2a0c73",
  "network": "instagram",
  "name": "Catalogue",
  "trigger": "comment_keyword",
  "keywords": ["catalogue", "price"],
  "match_mode": "contains",
  "post_external_ids": null,
  "public_replies": ["Sent you a DM!"],
  "dm_message": "Here is the catalogue.",
  "link_url": "https://example.com/catalogue",
  "link_label": "Open catalogue",
  "collect": "email",
  "collect_prompt": "Want the restock alert? Reply with your email.",
  "use_ai": false,
  "enabled": true,
  "triggered_count": 42,
  "dms_sent": 40,
  "leads_captured": 17,
  "created_at": "2026-09-30T11:00:00+00:00",
  "updated_at": "2026-09-30T11:00:00+00:00"
}
```

| | |
|---|---|
| `account_id` | Required on create; cannot be changed afterwards. |
| `name` | Required on create. 1–80 characters. |
| `dm_message` | Required on create. 1–1000 characters. |
| `trigger` | `comment_keyword` (default) or `dm_keyword`. |
| `keywords` | Up to 20. None matches everything. |
| `match_mode` | `contains` (default) or `exact`. |
| `post_external_ids` | Up to 50 of the network's post ids; `null` means every post. |
| `public_replies` | Up to 10; one is picked each time. |
| `link_url`, `link_label` | An `http(s)` link sent with the DM, and its button text. |
| `collect` | `none` (default), `email` or `phone`. |
| `collect_prompt` | The question asked when collecting. |
| `use_ai` | Personalise the DM with AI. Uses one of the plan's monthly AI drafts, else today's AI credits (`ai_credits_per_day` in `GET /usage`); with neither left, a written reply is sent. |
| `enabled` | Defaults to `true` — **an automation runs as soon as it is created.** |

Each person gets **one** private reply per comment, only within the network's
window (seven days for Instagram and Facebook), and a business never starts a
conversation. These rules are the networks', they are enforced here, and no
setting turns them off.

## Operations

### `GET /v1/automations`

**List automations**

Scope `social:read`.

Every automation, with its counters. Never paged (`next_cursor` is always null).

**Answers**

| Status | Body |  |
|---|---|---|
| 200 | `AutomationList` | The automations. |
| 401 | `ErrorEnvelope` | `UNAUTHORIZED`: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | `ErrorEnvelope` | `INSUFFICIENT_SCOPE`: the key lacks this operation's scope. Or `ORGANISATION_SUSPENDED`: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 429 | `ErrorEnvelope` | `RATE_LIMITED`: too many requests. Wait for `Retry-After` seconds. |
| 500 | `ErrorEnvelope` | `INTERNAL_ERROR`: ours. Safe to retry; send the same `Idempotency-Key` on a POST. |

**The comment-to-DM rules** — answers `200`

One response, never paged. Each rule's counters say how often it has fired.

```bash
curl https://api.boltclaw.ai/v1/automations \
  -H "Authorization: Bearer bc_live_EXAMPLExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```js
const res = await fetch("https://api.boltclaw.ai/v1/automations", {
  headers: {
    Authorization: `Bearer ${process.env.BOLTCLAW_KEY}`,
  },
});

// 200
const data = await res.json();
```

### `POST /v1/automations`

**Create an automation**

Scope `social:write`. Accepts `Idempotency-Key`. **Can make something publicly visible.** **Can record a platform charge.**

A rule that answers a keyword in a comment or a message with a DM, and optionally a public
reply and a request for an email or phone number. What a network supports is checked
here (`400`). The plan sets how many automations an organisation may hold and how many
DMs a month they may send (`402 PLAN_LIMIT`). DMs on X are billed per call on top of the plan.

**Request body** (required)

| Field | Type |  |
|---|---|---|
| `name` | `string` | (**required**; min 1; max 80) |
| `dm_message` | `string` | (**required**; min 1; max 1000) |
| `trigger` | `string` | (one of `comment_keyword`, `dm_keyword`) |
| `keywords` | `string[]` | (<= 20 items) |
| `match_mode` | `string` | (one of `contains`, `exact`) |
| `post_external_ids` | `array \| null` | (<= 50 items) |
| `public_replies` | `string[]` | (<= 10 items) |
| `link_url` | `string \| null` | (max 2048) |
| `link_label` | `string \| null` | (max 80) |
| `collect` | `string` | (one of `none`, `email`, `phone`) |
| `collect_prompt` | `string \| null` | (max 1000) |
| `use_ai` | `boolean` |  |
| `enabled` | `boolean` |  |
| `account_id` | `uuid` | (**required**) |

```json
{
  "name": "<name>",
  "dm_message": "<dm_message>",
  "account_id": "3f8c1e42-9b7a-4d61-a0f5-2c9e1b7d4a83"
}
```

**Answers**

| Status | Body |  |
|---|---|---|
| 201 | `Automation` | Created. |
| 400 | `ErrorEnvelope` | `VALIDATION_ERROR`: a body that is not JSON, a field that breaks its rule (named in `fields`), a bad query value or cursor, or a malformed `Idempotency-Key`. |
| 401 | `ErrorEnvelope` | `UNAUTHORIZED`: no key, or one that is malformed, unknown or revoked. It never says which. |
| 402 | `ErrorEnvelope` | `PLAN_LIMIT`: the organisation's plan does not allow more of this. The message names the allowance. |
| 403 | `ErrorEnvelope` | `INSUFFICIENT_SCOPE`: the key lacks this operation's scope. Or `ORGANISATION_SUSPENDED`: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | `ErrorEnvelope` | `NOT_FOUND`: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 413 | `ErrorEnvelope` | `PAYLOAD_TOO_LARGE`: the body is over 64 KB. |
| 422 | `ErrorEnvelope` | `VALIDATION_ERROR`: the request is well formed but cannot be done as asked, such as a network that does not support it. Or `UPSTREAM_ERROR`: the network refused it. |
| 428 | `ErrorEnvelope` | `APPROVAL_REQUIRED` on the first try, `APPROVAL_PENDING` on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries `approval` with a `url`: show it to the person, and once they approve, send the identical request again with the approval id in `BoltClaw-Approval`. Only an AI assistant's request is ever answered this way. |
| 429 | `ErrorEnvelope` | `RATE_LIMITED`: too many requests. Wait for `Retry-After` seconds. |
| 500 | `ErrorEnvelope` | `INTERNAL_ERROR`: ours. Safe to retry; send the same `Idempotency-Key` on a POST. |

### `PATCH /v1/automations/{id}`

**Update an automation**

Scope `social:write`. Another workspace's answers `404`. **Can make something publicly visible.** **Can record a platform charge.**

Changes the fields you send. The account cannot change; delete it and create another.

**Parameters**

| Name | In | Type |  |
|---|---|---|---|
| `id` | path | `uuid` | An id this API gave you. Anything that is not one of yours is `404`. (**required**) |

**Request body** (required)

| Field | Type |  |
|---|---|---|
| `name` | `string` | (min 1; max 80) |
| `dm_message` | `string` | (min 1; max 1000) |
| `trigger` | `string` | (one of `comment_keyword`, `dm_keyword`) |
| `keywords` | `string[]` | (<= 20 items) |
| `match_mode` | `string` | (one of `contains`, `exact`) |
| `post_external_ids` | `array \| null` | (<= 50 items) |
| `public_replies` | `string[]` | (<= 10 items) |
| `link_url` | `string \| null` | (max 2048) |
| `link_label` | `string \| null` | (max 80) |
| `collect` | `string` | (one of `none`, `email`, `phone`) |
| `collect_prompt` | `string \| null` | (max 1000) |
| `use_ai` | `boolean` |  |
| `enabled` | `boolean` |  |

**Answers**

| Status | Body |  |
|---|---|---|
| 200 | `Automation` | The automation. |
| 400 | `ErrorEnvelope` | `VALIDATION_ERROR`: a body that is not JSON, a field that breaks its rule (named in `fields`), a bad query value or cursor, or a malformed `Idempotency-Key`. |
| 401 | `ErrorEnvelope` | `UNAUTHORIZED`: no key, or one that is malformed, unknown or revoked. It never says which. |
| 402 | `ErrorEnvelope` | `PLAN_LIMIT`: the organisation's plan does not allow more of this. The message names the allowance. |
| 403 | `ErrorEnvelope` | `INSUFFICIENT_SCOPE`: the key lacks this operation's scope. Or `ORGANISATION_SUSPENDED`: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | `ErrorEnvelope` | `NOT_FOUND`: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 413 | `ErrorEnvelope` | `PAYLOAD_TOO_LARGE`: the body is over 64 KB. |
| 422 | `ErrorEnvelope` | `VALIDATION_ERROR`: the request is well formed but cannot be done as asked, such as a network that does not support it. Or `UPSTREAM_ERROR`: the network refused it. |
| 428 | `ErrorEnvelope` | `APPROVAL_REQUIRED` on the first try, `APPROVAL_PENDING` on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries `approval` with a `url`: show it to the person, and once they approve, send the identical request again with the approval id in `BoltClaw-Approval`. Only an AI assistant's request is ever answered this way. |
| 429 | `ErrorEnvelope` | `RATE_LIMITED`: too many requests. Wait for `Retry-After` seconds. |
| 500 | `ErrorEnvelope` | `INTERNAL_ERROR`: ours. Safe to retry; send the same `Idempotency-Key` on a POST. |

### `DELETE /v1/automations/{id}`

**Delete an automation**

Scope `social:write`. Another workspace's answers `404`.

Deletes the automation. Leads it captured are kept.

**Parameters**

| Name | In | Type |  |
|---|---|---|---|
| `id` | path | `uuid` | An id this API gave you. Anything that is not one of yours is `404`. (**required**) |

**Answers**

| Status | Body |  |
|---|---|---|
| 200 | `Deleted` | Deleted. |
| 401 | `ErrorEnvelope` | `UNAUTHORIZED`: no key, or one that is malformed, unknown or revoked. It never says which. |
| 403 | `ErrorEnvelope` | `INSUFFICIENT_SCOPE`: the key lacks this operation's scope. Or `ORGANISATION_SUSPENDED`: the key's organisation is suspended. Never about another workspace's object, which is 404. |
| 404 | `ErrorEnvelope` | `NOT_FOUND`: no such object, or it belongs to another workspace. The two are deliberately the same answer and never 403. If the id is right, check you are using the right key. |
| 428 | `ErrorEnvelope` | `APPROVAL_REQUIRED` on the first try, `APPROVAL_PENDING` on a retry before a person has decided. NOTHING HAS HAPPENED either way. The body carries `approval` with a `url`: show it to the person, and once they approve, send the identical request again with the approval id in `BoltClaw-Approval`. Only an AI assistant's request is ever answered this way. |
| 429 | `ErrorEnvelope` | `RATE_LIMITED`: too many requests. Wait for `Retry-After` seconds. |
| 500 | `ErrorEnvelope` | `INTERNAL_ERROR`: ours. Safe to retry; send the same `Idempotency-Key` on a POST. |

## Shapes

The objects these operations send and answer.

### Automation

| Field | Type |  |
|---|---|---|
| `id` | `uuid` | (**required**) |
| `account_id` | `uuid` | (**required**) |
| `network` | `Network` | (**required**; one of `instagram`, `facebook`, `threads`, `bluesky`, `x`, `tiktok`, `linkedin`, `youtube`, `pinterest`) |
| `name` | `string` | (**required**) |
| `trigger` | `string` | (**required**; one of `comment_keyword`, `dm_keyword`) |
| `keywords` | `string[]` | (**required**) |
| `match_mode` | `string` | (**required**; one of `contains`, `exact`) |
| `post_external_ids` | `array \| null` | (**required**) |
| `public_replies` | `string[]` | (**required**) |
| `dm_message` | `string` | (**required**) |
| `link_url` | `string \| null` | (**required**) |
| `link_label` | `string \| null` | (**required**) |
| `collect` | `string` | (**required**; one of `none`, `email`, `phone`) |
| `collect_prompt` | `string \| null` | (**required**) |
| `use_ai` | `boolean` | (**required**) |
| `enabled` | `boolean` | (**required**) |
| `triggered_count` | `integer` | (**required**) |
| `dms_sent` | `integer` | (**required**) |
| `leads_captured` | `integer` | (**required**) |
| `created_at` | `date-time` | (**required**) |
| `updated_at` | `date-time` | (**required**) |

### AutomationCreate

| Field | Type |  |
|---|---|---|
| `name` | `string` | (**required**; min 1; max 80) |
| `dm_message` | `string` | (**required**; min 1; max 1000) |
| `trigger` | `string` | (one of `comment_keyword`, `dm_keyword`) |
| `keywords` | `string[]` | (<= 20 items) |
| `match_mode` | `string` | (one of `contains`, `exact`) |
| `post_external_ids` | `array \| null` | (<= 50 items) |
| `public_replies` | `string[]` | (<= 10 items) |
| `link_url` | `string \| null` | (max 2048) |
| `link_label` | `string \| null` | (max 80) |
| `collect` | `string` | (one of `none`, `email`, `phone`) |
| `collect_prompt` | `string \| null` | (max 1000) |
| `use_ai` | `boolean` |  |
| `enabled` | `boolean` |  |
| `account_id` | `uuid` | (**required**) |

### AutomationList

| Field | Type |  |
|---|---|---|
| `data` | `Automation[]` | (**required**) |
| `next_cursor` | `NullCursor` | Always null; this list is never paged. (**required**) |

### AutomationUpdate

At least one field. The account cannot change.

| Field | Type |  |
|---|---|---|
| `name` | `string` | (min 1; max 80) |
| `dm_message` | `string` | (min 1; max 1000) |
| `trigger` | `string` | (one of `comment_keyword`, `dm_keyword`) |
| `keywords` | `string[]` | (<= 20 items) |
| `match_mode` | `string` | (one of `contains`, `exact`) |
| `post_external_ids` | `array \| null` | (<= 50 items) |
| `public_replies` | `string[]` | (<= 10 items) |
| `link_url` | `string \| null` | (max 2048) |
| `link_label` | `string \| null` | (max 80) |
| `collect` | `string` | (one of `none`, `email`, `phone`) |
| `collect_prompt` | `string \| null` | (max 1000) |
| `use_ai` | `boolean` |  |
| `enabled` | `boolean` |  |

### Deleted

| Field | Type |  |
|---|---|---|
| `deleted` | `true` | (**required**) |
| `id` | `uuid` | (**required**) |

### Network

One of: `instagram`, `facebook`, `threads`, `bluesky`, `x`, `tiktok`, `linkedin`, `youtube`, `pinterest`.

### NullCursor

Always null; this list is never paged.

`any | null`.

<!-- https://boltclaw.ai/docs/api/automations as markdown. Generated from openapi.yaml; API version 1.22.0. -->
<!-- Every /docs page answers as markdown: add .md to its path, or send Accept: text/markdown. -->
