> ## 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 only reacts to engagement a user directs at you (a comment, story reply, DM, or reaction) — it never sends follow-triggered DMs, first-message DMs to strangers, or bulk outbound — and it inherits Ayrshare's per-account rate limits, per-recipient deduplication, and idempotent webhook ingestion.

<Info>
  **`sent` means Meta accepted the message, not that the recipient received it.** Delivery is ultimately dictated by the recipient's Instagram **Message requests** setting: if they don't allow message requests from everyone, Meta returns a success response and silently drops the message, and this is invisible at every API layer. Even a delivered comment-triggered DM arrives as a **message request the recipient must accept** (unless a conversation already exists between the two accounts). See [Automation DM Sent but Not Delivered](/docs/help-center/technical-support/automation_dm_sent_but_not_delivered) for the full explanation.
</Info>

## 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`](/docs/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 (per profile) | Daily DM cap (per account) |
| ---------- | -------------------------------- | -------------------------- |
| Business   | 10                               | 1,000                      |
| Enterprise | 50                               | 5,000                      |

The active-automation cap is counted **per [User Profile](/docs/apis/profiles/overview)**, not per parent account. Each profile under your account gets its own Business 10 / Enterprise 50, so an account with many profiles can run that many automations on each. It counts active automations and is enforced on both `POST` (create) and `PUT` re-activation (`active: false → true`), each surfacing error code `470`. Need a higher per-profile limit? [Contact support](mailto:support@ayrshare.com) to have it raised for your account.

The **daily DM cap** applies per parent Ayrshare account, shared across all your profiles, with a per-profile sub-cap so one busy profile cannot drain the whole account's quota. When a DM cap is hit, the activity row records status `rate_limited` and no DM is sent.

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`         | At least one action was **accepted** by the platform, and none hit an auth error. For `send_dm` this means Instagram accepted the message — **not** that the recipient received it (see the note below)                                                                                                        |
| `failed`       | No action was accepted, and at least one was rejected by the platform or could not run (and none hit an auth error). The reason is on `actionResults[].errorDetails`                                                                                                                                           |
| `auth_error`   | The account's Instagram credentials or messaging permissions could not be used — for example an expired or revoked access token, a missing Facebook Page selection, or Messaging not activated for the profile. The DM was not retried. Per-recipient delivery failures are recorded as `failed`, not here     |
| `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 was suppressed before dispatch — it became inactive or was deleted between fan-out and dispatch, it has no actions configured, or the engagement that triggered it came from the same account that would send the DM — most often a comment on your own post (an account cannot message itself) |

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

When an automation has more than one action, the top-level `status` is derived from the per-action results in this order: `auth_error` if any action hit an auth error, then `sent` if any action was accepted, then `deduplicated` if every action was deduplicated, then `skipped` if every action was deliberately suppressed (skipped or deduplicated), otherwise `failed`. Read `actionResults[]` for the per-action detail.

<Note>
  **`sent` is a platform-acceptance receipt, not a delivery receipt.** Instagram does not expose message delivery to any API. A `send_dm` action is marked `sent` the moment Instagram accepts the message; whether the recipient actually receives it depends on their Instagram **Message requests** setting, which Ayrshare cannot read or influence. See [Automation DM Sent but Not Delivered](/docs/help-center/technical-support/automation_dm_sent_but_not_delivered).
</Note>

## 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 have engaged first (comment, reply, DM, reaction) before a business account can message them. Every supported trigger is anchored to such an engagement — but note that being *allowed* to send is not the same as the message being *delivered*: the recipient's Instagram **Message requests** setting can still cause Meta to accept and then silently drop the message (see [Automation DM Sent but Not Delivered](/docs/help-center/technical-support/automation_dm_sent_but_not_delivered)).
* **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 engaged first. Every supported trigger (`comment_keyword`, `story_reply`, `dm_reaction`, `dm_keyword`) is anchored to such an engagement, which is what makes the send permissible.
  </Accordion>

  <Accordion title="Why does an activity say `sent` but the recipient never got the DM?">
    `sent` means Instagram **accepted** the message, not that it was delivered. Delivery depends on the recipient's Instagram **Message requests** setting — if they don't allow message requests from everyone, Meta returns a success response and silently drops the message, with no error, webhook, or other signal on any API surface. A successful comment-triggered DM also arrives as a **message request the recipient must accept** (unless a conversation already exists). This is a permanent Instagram platform limitation. See [Automation DM Sent but Not Delivered](/docs/help-center/technical-support/automation_dm_sent_but_not_delivered).
  </Accordion>

  <Accordion title="What happens if my access token is invalid when an automation fires?">
    If the account's Instagram credentials or messaging permissions genuinely fail — for example an expired or revoked access token, a missing Facebook Page selection, or Messaging not activated for the profile — the activity row records status `auth_error` and the DM is not retried. Resolving the account state (relinking, selecting a Page, or activating Messaging) lets the next matching engagement fire normally. This only applies to genuine credential and permission failures: if a specific DM could not be delivered for another reason — for example the recipient could not be found, or the triggering engagement came from the account that would send the DM — the row is recorded as `failed` or `skipped` with the reason on `actionResults[].errorDetails`, and relinking will not change the outcome.
  </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`](/docs/apis/automations/create-automation) — create a new automation
* [`GET /automations`](/docs/apis/automations/list-automations) — list your automations
* [`GET /automations/:id`](/docs/apis/automations/get-automation) — fetch one automation with its triggers and actions
* [`PUT /automations/:id`](/docs/apis/automations/update-automation) — partial update; pause via `active: false`
* [`DELETE /automations/:id`](/docs/apis/automations/delete-automation) — soft-delete
* [`GET /automations/:id/activity`](/docs/apis/automations/get-activity) — cursor-paginated dispatch audit log
