> For the complete documentation index, see [llms.txt](https://help.blotato.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.blotato.com/start-with-an-ai-agent/llm/automations.md).

# DM automation requests

Compact reference for agents making REST requests. All endpoint paths below are relative to `https://backend.blotato.com/v2`. For task instructions, read the [workflow guide](/agent-workflows/agent-workflows/automations.md). For tool calls, use the [MCP reference](/start-with-an-ai-agent/mcp/tools.md).

## Manage dm automations

```
A DM automation sends a direct message when someone comments on your post or sends
your account a message. Text plus up to 3 link buttons. An automation holds up to 2
triggers: one comment-received and one message-received. Any one firing starts a
single run.

Optional steps run in a FIXED order around that message:
  followGate (instagram only) -> emailGate -> dmMessage -> webhook

Supported platforms: instagram, facebook

Notes:
- A comment trigger answers with a PRIVATE REPLY to the comment (one per comment,
  within 7 days). A message trigger answers with a standard DM (24-hour window).
- Your own comments and the messages your account sends never start a run.
- Link buttons are optional. Set buttons to [] for a text-only message or an
  email-to-webhook flow. A button title is the text the recipient sees, and its
  url is the page the button opens.
- A button url and webhook.url serve different purposes. webhook.url receives
  captured email data after the DM sends.
- No cold outreach. An automation answers people who contact you first.
- An automation is created as a draft unless isActive is true.
- While a run waits on a contact's reply, their next DM RESUMES that run instead of
  starting a new one. A button tap never starts a run.
- Every DM an automation sends counts toward the monthly active-contacts limit.
- Every live automation whose trigger matches starts its OWN run. For best results, keep one live automation per keyword per account.
- Each matching comment starts a new run, so a repeat commenter gets a reply per comment.
- Messages without text (photos, stickers, voice notes, reactions) never start a run or
  advance a gate.
- Disconnecting an account ARCHIVES its automations. Reconnecting keeps them. Removing a
  Facebook Page leaves its automations live, but they stop receiving events until the
  Page is added back.

List automations, optionally with selected analytics:
GET /dm-automations?limit=20&cursor=OPTIONAL_CURSOR&metrics=triggered&metrics=issues
Response: { "items": [ { ...<DmAutomation>, "analytics": { "triggered": 412, "issues": 3 } }, ... ], "cursor": "..." }
Metric values: triggered | completed | failed | issues. issues counts failures from the
last 7 days and is a subset of failed. Unrequested metrics are absent.

Create an automation:
POST /dm-automations
{
  "accountId": "required-account-id",
  "platform": "instagram",                       // "instagram" | "facebook"
  "target": { "targetType": "instagram" },       // facebook also needs "pageId"
  "name": "Auto-DM links from comments",         // 1-60 characters
  "triggers": [                                  // 0-2 triggers, at most one per type
    {
      "type": "comment-received",                // or "message-received"
      "keywords": ["price", "link"],             // [] matches every comment/text message
      "postId": null                             // comment triggers only, see below
    }
  ],
  "dmMessage": "Tap below for the link.",        // 1-640 chars, instagram max 1000 bytes
  "buttons": [                                   // REQUIRED field, pass [] for none
    { "type": "url", "title": "View link", "url": "https://example.com" }
  ],
  "followGate": {                                // OPTIONAL, instagram only
    "message": "Follow me, then tap below.",     // 1-640 characters
    "buttonTitle": "I'm following"               // <= 20 chars, this is the default
  },
  "emailGate": {                                 // OPTIONAL, both platforms
    "message": "Reply with your email."          // 1-640 characters
  },
  "webhook": {                                   // OPTIONAL, both platforms
    "method": "POST",                            // GET|POST|PUT|PATCH|DELETE
    "url": "https://example.com/hooks/leads",    // public http(s), <= 2048 chars
    "headers": { "X-Api-Key": "secret" }         // optional
  },
  "isActive": true                               // default false (draft)
}
Response: 201 { "flow": <DmAutomation> }

GATE AND WEBHOOK RULES:
- followGate: INSTAGRAM ONLY. Sends the gate message with a confirm button, waits up
  to 30 DAYS for a reply, then checks follower status. Not following -> gate message
  again. Following or UNKNOWN status -> proceed. UNKNOWN means the contact never
  granted profile access, and Blotato proceeds rather than blocking them. A confirmed
  follow is cached 30 days, and a negative result is never cached. Every run still sends
  the gate message. Passing followGate on a facebook automation -> 422 (20303).
  ANY text reply advances the gate - the follower check runs on the contact's next
  text DM whatever it says, so the button tap is a shortcut, not a requirement.
  SETUP: the account must be subscribed to button-tap events, which happens at
  CONNECT time. An account connected before DM automations and follow gating landed
  does not reliably receive taps and runs reach "expired" - the user must reconnect it in
  Settings > Social Accounts.
  DESKTOP: Instagram renders the confirm button in the mobile app only. Write the
  gate message to ask for a typed reply (e.g. "Reply FOLLOWING once you have") so a
  desktop audience advances without a button.
- emailGate: sends the gate message, waits up to 72 HOURS, reads the FIRST email
  address out of the reply. No address found -> gate message again. Found -> saved to
  the contact, then proceed. Shape is validated, deliverability is not. The captured
  address is NOT returned by GET /conversations or GET /messages - a webhook is the
  only way to read it.
  SKIP: on automations published after September 14, 2026, a contact who already
  replied with an email to ANY email gate on the same connected account skips the
  gate. Older automations ask every time until saved again (PATCH with their
  current triggers, or Save and Publish in the web app).
- webhook: called AFTER dmMessage sends. Every method except GET carries a JSON body
  with Content-Type: application/json, with or without a gate on the automation:
    { "email": "them@example.com", "isFollower": true }
  GET carries no body. The host must resolve to a PUBLIC address - private, loopback,
  link-local and cloud metadata ranges are rejected. Redirects are NOT followed.
  Timeout 15s. A timeout logs a warning (code 20305) and the run STILL COMPLETES,
  with no retry. Response body read is capped at 16 KB. A non-2xx response is logged
  and the run STILL COMPLETES. A blocked address, DNS failure or connection error
  fails the run (code 20304). The URL and body appear in run logs, headers do not.

Update an automation (patch is NESTED under "patch"):
PATCH /dm-automations/:id
{ "patch": { "dmMessage": "New text", "buttons": [], "isActive": true } }
Patchable: name, triggers, dmMessage, buttons, followGate, emailGate, webhook, isActive.
triggers replaces the WHOLE trigger set ([] clears it). Pass null for followGate,
emailGate or webhook to REMOVE it.
accountId, platform, and target are fixed at creation.
Content changes to a LIVE automation publish immediately. Changes to a draft stay
saved until isActive is true. A live automation needs at least one ACTIVE trigger - to
clear every trigger, pass an empty triggers array and set isActive false in the same
request. To drop one trigger, send triggers holding the one you keep. A run already in
progress keeps the version it started with.

Update a single trigger, e.g. turn it on or off (immediate on a live
automation, no republish):
PATCH /dm-automations/:flowId/triggers/:triggerId
{ "patch": { "isActive": false } }
Response: 200 { "trigger": <Trigger> }
- triggerId comes from the automation's triggers[].id. IDs CHANGE whenever the
  automation's content or triggers are updated - re-read the automation first.
- Turning off the last active trigger on a live automation is rejected with 422
  (code 20303).

Archive an automation:
DELETE /dm-automations/:id
Response: 200 { "flow": <DmAutomation> }  // status archived, triggers stop listening

<DmAutomation>:
{
  "id": "flow_abc123",
  "accountId": "98434",
  "name": "Auto-DM links from comments",
  "platform": "instagram",
  "target": { "targetType": "instagram" },
  "triggers": [
    { "id": "trg_abc123", "type": "comment-received", "keywords": ["price"], "postId": null, "isActive": true },
    { "id": "trg_def456", "type": "message-received", "keywords": ["price"], "isActive": true }
  ],
  "dmMessage": "Tap below for the link.",
  "buttons": [ { "type": "url", "title": "View link", "url": "https://example.com" } ],
  "followGate": { "message": "Follow me, then tap below.", "buttonTitle": "I'm following" },
  "emailGate": { "message": "Reply with your email." },
  "webhook": { "method": "POST", "url": "https://example.com/hooks/leads" },
  "isActive": true,
  "publishedVersionId": "ver_001",              // null for a draft
  "createdAt": "2026-08-01T12:00:00Z",
  "updatedAt": "2026-08-02T09:30:00Z"
}

TRIGGER RULES:
- Up to 2 triggers per automation, at most one per type. One matching event starts
  ONE run. Each trigger has its own keywords and, for comment triggers, its own postId.
- id: read-only, present on every trigger Blotato returns. Omit it on create/update.
- isActive: false pauses one trigger while the automation stays live. A live automation
  needs at least one active trigger.
- type "comment-received": fires on a comment on your post or reel.
- type "message-received": fires on a DM someone sends your account.
- keywords: case-insensitive, whole-word match, tolerant of line breaks inside a
  multi-word keyword. Any one keyword matching fires the automation. [] matches all
  text.
- postId (comment triggers only): the BLOTATO post id from GET /published-posts.
  Per-post targeting covers posts published through Blotato ONLY. For a post published
  outside Blotato, set postId null and match on the keyword the post asks people to
  comment. null watches every post and reel on the account.
- Buttons in an automation are type "url" only. For postback buttons or quick
  replies, send the message yourself with POST /messages.
  - The followGate confirm button is a postback button Blotato manages - you set
    its label only.
- INSTAGRAM: automation buttons show in the Instagram mobile app only. Put a button
  OR a URL inside dmMessage, never both - a message carrying both leaves the URL
  unclickable on desktop. For a desktop audience, pass buttons: [] and put the URL
  in dmMessage.

INSPECT ACTIVITY:
GET /dm-automations/:id/runs?limit=20&cursor=OPTIONAL_CURSOR&status=failed&since=ISO_8601
Response: { "items": [ <DmAutomationRun>, ... ], "cursor": "..." }
status accepts multiple values: running | waiting | completed | expired | superseded | failed.
since returns runs with activity at or after the ISO 8601 timestamp.

<DmAutomationRun>:
{
  "id": "run_abc123",
  "contactId": "1784000000",                    // platform id of the person
  "platform": "instagram",
  "status": "failed",                           // running | waiting | completed | expired | superseded | failed
  "error": {
    "code": 20102,
    "message": "Messaging window has expired",
    "action": "Ask the contact to send a new message, then retry.",
    "details": { "retryable": false }
  },
  "createdAt": "2026-08-02T09:30:00Z",
  "updatedAt": "2026-08-02T09:30:04Z"
}

GET /dm-automations/:id/logs?flowRunId=OPTIONAL_RUN_ID&limit=20&cursor=OPTIONAL_CURSOR
Response: { "items": [ <AutomationLog>, ... ], "cursor": "..." }

<AutomationLog>:
{
  "id": "log_abc123",
  "flowRunId": "run_abc123",
  "nodeId": "nd_9fJ2",                          // null for run-level entries
  "level": "info",                              // info | warning | error
  "message": "Queued message for send",
  "context": { "messageId": "msg_abc123" },
  "createdAt": "2026-08-02T09:30:01Z"
}

GET /dm-automations/:id/analytics
Response: { "analytics": { "triggered": 412, "completed": 398, "failed": 9, "issues": 3 } }
A run stays open until its message settles, so completed + failed is sometimes
lower than triggered. failed counts all-time runs ending on an error. issues counts
failures from the last 7 days and is a subset of failed. Expired and superseded runs
count toward triggered only.

ERRORS:
- 404: automation or trigger not found
- 422: invalid for publishing (code 20303: no trigger, two triggers of one type, no
  active trigger, or a bad message/button/gate/webhook), or missing connected account
  (code 5000)
```

Return to the [agent reference index](/start-with-an-ai-agent/llm.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://help.blotato.com/start-with-an-ai-agent/llm/automations.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
