> ## Documentation Index
> Fetch the complete documentation index at: https://www.ayrshare.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Automations API Overview

> Engagement-triggered Instagram automations — fire a DM, webhook, or email when an end user comments, replies to a story, reacts to a DM, or sends a DM

export const PlansAvailable = ({plans = [], maxPackRequired}) => {
  let displayPlans = plans;
  if (plans && plans.length === 1) {
    const lowerCasePlan = plans[0].toLowerCase();
    if (lowerCasePlan === "business") {
      displayPlans = ["Launch", "Business", "Enterprise"];
    } else if (lowerCasePlan === "premium") {
      displayPlans = ["Premium", "Launch", "Business", "Enterprise"];
    }
  }
  return <Note>
Available on {displayPlans.length === 1 ? "the " : ""}
{displayPlans.join(", ").replace(/\b\w/g, l => l.toUpperCase())}{" "}
{displayPlans.length > 1 ? "plans" : "plan"}.

{maxPackRequired && <span onClick={() => window.open('https://www.ayrshare.com/docs/additional/maxpack', '_self')} className="flex items-center mt-2 cursor-pointer">
 <span className="px-1.5 py-0.5 rounded text-sm" style={{
    backgroundColor: '#C264B6',
    color: 'white',
    fontSize: '12px'
  }}>
   Max Pack required
 </span>
</span>}
</Note>;
};

<PlansAvailable plans={["business", "enterprise"]} maxPackRequired={false} />

<Note>
  **Beta.** The Automations API is in beta and we are actively collecting feedback. Endpoints, payloads, and limits may change as we iterate. Please send feedback and bug reports to support so we can prioritize the right improvements.
</Note>

The Automations endpoints let you define rules that automatically react to incoming Instagram engagement. Each automation pairs one or more **triggers** (the event that fires the rule) with one or more **actions** (what happens when it fires). A single rule can listen for multiple triggers and dispatch multiple actions — fire a webhook into your analytics pipeline AND send a DM from the same engagement.

The engine runs entirely inside Meta's policy envelope (no follow-triggered DMs, no first-message-to-strangers, no bulk outbound) and inherits Ayrshare's per-account rate limits, per-recipient deduplication, and idempotent webhook ingestion.

## How it works

<Steps>
  <Step title="Create an automation">
    `POST /automations` with the triggers and actions you want. The automation activates immediately.
  </Step>

  <Step title="An end user engages">
    Someone comments on your post, replies to your story, sends a DM, or reacts to a DM. Meta delivers the webhook to Ayrshare.
  </Step>

  <Step title="Ayrshare matches and dispatches">
    The engine looks up every rule matching the event, checks per-action deduplication and your daily DM cap, then runs each action. A 20–60 second jitter is applied to DM sends to stay inside Instagram's anti-spam heuristics.
  </Step>

  <Step title="Inspect what fired">
    `GET /automations/:id/activity` returns the audit log — every dispatch attempt, the per-action results, and any errors.
  </Step>
</Steps>

## Triggers

You can attach up to **50 triggers** to one automation. Each trigger is a discriminated union on the `type` field; type-specific fields live at the same level. All triggers are Instagram-only in v1.

| Type              | Fires when                                            | Config                                               |
| ----------------- | ----------------------------------------------------- | ---------------------------------------------------- |
| `comment_keyword` | A comment matching a keyword lands on a specific post | `postId` (required); `keywords` (required, ≥1 entry) |
| `story_reply`     | A user replies to a story via DM                      | `storyId` (optional)                                 |
| `dm_reaction`     | A user reacts to one of your DMs with an emoji        | `emoji` (optional — omit to fire on any emoji)       |
| `dm_keyword`      | A user sends a DM whose text matches a keyword        | `keywords` (required, ≥1 entry)                      |

Keyword matching is **case-insensitive** and whole-word. An event satisfies a keyword-filtered trigger if it contains any one of the configured keywords. Omit `storyId` on a story trigger to fire on every story for the connected account.

## Actions

You can attach up to **50 actions** to one automation. They run sequentially; each result is recorded on the activity row.

| Type           | Effect                                                                                                                          | Config                                                                                   |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `send_dm`      | Sends an Instagram DM to the user who triggered the rule, using a [templated message](#template-variables).                     | `message` (required, templated)                                                          |
| `fire_webhook` | POSTs the automation context to your account-level webhook URL (configure via [`POST /hook/webhook`](/apis/webhooks/register)). | *(none — payload shape is fixed; see [below](#fire_webhook-payload))*                    |
| `send_email`   | Queues an email via the platform's mail pipeline.                                                                               | `to` (required, email); `subject` (optional, templated); `message` (required, templated) |

### Per-action dedup window

Every action — regardless of type — additionally accepts an optional top-level `dedupWindowMinutes` field that overrides the **default 7-day** per-recipient dedup window for that action only.

* Set to `0` to **disable** dedup entirely for that action (typical for `fire_webhook` / `send_email` where the receiver expects every event).
* Capped at `525600` (one year).

```json Action with a 24h dedup override theme={"system"}
{
  "type": "send_dm",
  "message": "Thanks {{recipient_username}}!",
  "dedupWindowMinutes": 1440
}
```

### `fire_webhook` payload

When `fire_webhook` runs it POSTs a JSON body to your account-level webhook URL:

```json theme={"system"}
{
  "automationId":      "auto_9xKp2Lm4nQ",
  "triggerId":         "trg_a1b2c3",
  "trigger":           "comment_keyword",
  "platform":          "instagram",
  "recipientId":       "17841401234567890",
  "recipientUsername": "jane_doe",
  "keyword":           "LINK",
  "timestamp":         "2026-05-12T09:14:22.000Z"
}
```

`recipientUsername` and `keyword` are `null` when the trigger doesn't populate them (e.g. `dm_keyword` doesn't carry a username on Meta's payload; `story_reply` doesn't have a keyword).

## Template variables

`send_dm.message`, `send_email.subject`, and `send_email.message` support `{{placeholder}}` substitution. **Unknown placeholders are rejected at create/update time** (as a `473` validation error) so a typo never silently leaks the literal `{{foo}}` into a customer-facing message.

| Placeholder              | Resolves to                                                               |
| ------------------------ | ------------------------------------------------------------------------- |
| `{{recipient_username}}` | The engaging user's Instagram handle (when the webhook carries it)        |
| `{{recipient_id}}`       | The engaging user's Instagram participant ID (IGSID)                      |
| `{{recipient_name}}`     | Reserved; resolves to empty until a future enrichment source populates it |
| `{{sender_username}}`    | Your linked Instagram username                                            |
| `{{sender_name}}`        | Your linked Instagram display name                                        |
| `{{comment_text}}`       | The text of the comment / DM / story reply that fired the trigger         |
| `{{comment_id}}`         | The platform id of the comment / message that fired                       |
| `{{comment_sent_at}}`    | ISO 8601 timestamp of the event (when available)                          |
| `{{matched_keyword}}`    | The keyword that matched (or the emoji string for `dm_reaction`)          |
| `{{platform}}`           | Platform identifier (e.g. `instagram`)                                    |
| `{{trigger_type}}`       | Trigger type (e.g. `comment_keyword`)                                     |

<Note>
  **No `sender_email` / `recipient_email`.** These are deliberately not exposed — your billing email has no legitimate place in a DM to a stranger, and Meta does not provide the recipient's email on any IG webhook. Avoiding the placeholders prevents accidental disclosure.
</Note>

Example template:

```
Hey {{recipient_username}}, thanks for the comment "{{comment_text}}" — here is the link you wanted: https://example.com
```

## Rate limits and caps

| Plan       | Active automations | Daily DM cap |
| ---------- | ------------------ | ------------ |
| Business   | 10                 | 1,000        |
| Enterprise | 50                 | 5,000        |

Caps apply per parent Ayrshare account. When a DM cap is hit the activity row records status `rate_limited` and no DM is sent. The active-automation cap is enforced on both `POST` (create) and on `PUT` when re-activating (`active: false → true`) — both surface error code `470`.

Structural caps on a single automation: **1–50 triggers**, **1–50 actions**.

Instagram itself caps DMs at roughly 200/hour per account. The engine paces dispatch with a 20–60 second jitter to stay safely under this.

## Activity statuses

A row in `GET /automations/:id/activity` carries a top-level `status` plus a per-action `status` inside `actionResults[]`:

| Status         | Meaning                                                                    |
| -------------- | -------------------------------------------------------------------------- |
| `pending`      | Just written; the worker hasn't picked it up yet                           |
| `in_flight`    | Worker is currently dispatching                                            |
| `sent`         | Every action succeeded                                                     |
| `failed`       | At least one action failed (and none hit an auth error)                    |
| `auth_error`   | The Instagram access token was invalid; the DM was not retried             |
| `rate_limited` | The daily DM cap (tier or per-profile) was hit; no DM was sent             |
| `deduplicated` | This action already fired to this recipient inside its dedup window        |
| `skipped`      | The automation became inactive or was deleted between fan-out and dispatch |

`pending` and `in_flight` are transient; everything else is terminal.

## Error codes

The API returns two shapes of error:

* **Business-rule errors** carry a numbered automation `code` (e.g. `{ "action": "automation", "code": 469, ... }`).
* **Validation errors** — any malformed request body (missing or invalid fields, unknown template variables, unrecognized keys) — are returned as a single **`473`** response with a `details` object that lists the offending fields. `details` is the validator's output (`formErrors` plus `fieldErrors`). Branch on `details`, not on a per-condition code. In `fieldErrors`, keys are the top-level request fields (`triggers`, `actions`): a problem inside a specific entry, such as a trigger missing its `keywords`, is reported under that field (e.g. `triggers`), while `formErrors` holds object-level issues such as unrecognized keys.

| Code | HTTP | Meaning                                                                 |
| ---- | ---- | ----------------------------------------------------------------------- |
| 468  | 403  | Business or Enterprise plan required                                    |
| 469  | 404  | Automation not found (also returned when the caller doesn't own it)     |
| 470  | 429  | Active automation cap reached for your plan tier                        |
| 471  | 400  | No social account linked on the requested platform for this profile     |
| 472  | 403  | Feature not yet available on your account — contact us for early access |
| 473  | 400  | Validation failed (malformed request body) — inspect `details`          |

## What Meta does NOT allow

A few commonly-requested capabilities are not supported because Meta doesn't permit them on the public Instagram API:

* **Auto-DM on new followers.** Instagram does not publish a follow webhook.
* **First-message DMs to strangers.** Meta requires the recipient to initiate contact (comment, reply, DM, reaction) before a business account can message them — which is exactly what every supported trigger here represents.
* **Bulk outbound campaigns.** Hourly DM caps and anti-abuse heuristics apply at the platform level.

## Multi-profile usage

The endpoints respect the `profileKey` header. Pass a child profile's key and the automation is created/managed under that profile. Rate limits split across profiles via a per-profile sub-cap so one chatty profile doesn't drain the parent account's quota.

## FAQ

<AccordionGroup>
  <Accordion title="Can I trigger on a new follower?">
    No. Instagram does not publish a follow webhook, and Meta does not allow third-party apps to send a DM to a user who has not initiated a conversation. Every supported trigger (`comment_keyword`, `story_reply`, `dm_reaction`, `dm_keyword`) satisfies the "user contacted you first" requirement.
  </Accordion>

  <Accordion title="What happens if my access token is invalid when an automation fires?">
    The activity row records status `auth_error` and the DM is not retried. Relink the account, then the next matching engagement will fire normally.
  </Accordion>

  <Accordion title="Why is there a delay before the DM is sent?">
    Each `send_dm` dispatch is scheduled 20–60 seconds after the engagement to look organic to Instagram's anti-spam systems. `fire_webhook` and `send_email` actions do NOT have jitter. The activity row's `created` timestamp is when the trigger matched; `completedAt` is when dispatch finished.
  </Accordion>

  <Accordion title="Are activity rows kept forever?">
    Activity rows are retained indefinitely for trace and analytics. The `GET /automations/:id/activity` endpoint returns rows from the last 30 days for performance. (The dedup guard uses its own per-action window — defaulting to 7 days — which is unrelated to the activity lookback.)
  </Accordion>

  <Accordion title="Does deleting an automation remove its activity history?">
    No. Delete is a soft-delete: the master row is flagged `deleted`, no new dispatches occur, but historical activity rows remain readable via the activity endpoint.
  </Accordion>
</AccordionGroup>

## Endpoints

* [`POST /automations`](/apis/automations/create-automation) — create a new automation
* [`GET /automations`](/apis/automations/list-automations) — list your automations
* [`GET /automations/:id`](/apis/automations/get-automation) — fetch one automation with its triggers and actions
* [`PUT /automations/:id`](/apis/automations/update-automation) — partial update; pause via `active: false`
* [`DELETE /automations/:id`](/apis/automations/delete-automation) — soft-delete
* [`GET /automations/:id/activity`](/apis/automations/get-activity) — cursor-paginated dispatch audit log
