---
title: "Create comment-to-DM automation"
method: POST
path: "/v1/comment-automations"
tags: ["Comment Automations"]
---

# Create comment-to-DM automation

`POST /v1/comment-automations`

Create a keyword-triggered DM automation on an Instagram or Facebook account.
When someone comments a matching keyword (or, with `trigger: story_reply`, replies
to your Instagram story with one), they automatically receive a DM.

Triggers (`trigger`):
  * `comment` (default): fires on keyword comments on a post or reel.
  * `story_reply`: fires when someone replies to your Instagram story with a keyword,
    and answers them with a DM. Set `platformPostId` to a story media id to scope to
    one story, or omit it to match replies to any story.

Targeting (comment trigger):
  * Per-post: set `platformPostId` to scope to one specific post (only one active
    per-post automation is allowed per post).
  * Account-wide ("any post"): omit `platformPostId` (and `postId`). The automation
    evaluates every comment on every post on the account. You can stack unlimited
    account-wide automations, each with its own keyword set, and they all run
    independently. Per-post automations take priority on their post.

Audience (`audience`, Instagram only): restrict the automation to followers or
non-followers, and/or to accounts above a follower count. Instagram only reveals the
follow relationship for people who have messaged the account, so `audience.whenUnknown`
decides what happens for everyone else - including `verify`, which sends a one-tap
confirmation DM (`followGate`) and then delivers the real DM automatically. People we
already know follow you skip the tap entirely.

Set `alsoMatchInDms: true` on a `comment` automation to also answer people who send
a keyword as a direct message instead of commenting it. One automation then covers
both doors, and each door is deduplicated separately (someone who already got the DM
from their comment still gets it if they later DM the keyword). Requires at least one
keyword.

Links in the DM's buttons can be click-tracked (`linkTracking`, on by default) and
clickers optionally tagged (`clickTag`) for segmentation. Stats returned include
delivered, read, and link clicks.

## Request body

- object
  - `profileId` string, required
  - `accountId` string, required — Instagram or Facebook account ID
  - `trigger` 'comment' | 'story_reply' — What fires the automation. 'comment' (keyword comment on a post) or 'story_reply' (keyword reply to an Instagram story). For 'story_reply', platformPostId is the story media id (omit for any story).
  - `platformPostId` string — Platform media/post ID (or story media id when trigger=story_reply). Omit for an account-wide (any-post / any-story) automation.
  - `postId` string — Zernio post ID. Required only when also targeting a specific post via platformPostId.
  - `postTitle` string — Post content snippet for display
  - `name` string, required — Automation label
  - `keywords` string[] — Trigger keywords (empty = any comment triggers)
  - `matchMode` 'exact' | 'contains' | 'word' — How a keyword is compared with the comment. 'contains' (default) matches anywhere, even inside another word (keyword 'app' fires on 'happy'). 'word' matches the keyword only as a standalone word. 'exact' requires the whole comment to be exactly the keyword.
  - `excludeKeywords` string[] — Comments containing one of these never trigger the automation, even when a trigger keyword also matches. Compared using the same matchMode.
  - `typoTolerance` boolean — Only with matchMode=word: also fire on close misspellings of a keyword (one edit for 4-7 character keywords, two from 8 up). Keywords shorter than 4 characters are never fuzzy-matched.
  - `dmMessage` string, required — DM text to send to commenter. Max 640 chars when buttons are set, otherwise ~1000.
  - `buttons` DmButton[] — Optional inline DM buttons (1-3). Phone buttons are Facebook-only. Omit or pass [] for a plain-text DM.
    - `type` 'url' | 'postback' | 'phone', required
    - `title` string, required — Button label (20 chars max)
    - `url` string, uri — Target URL (required when type is url)
    - `payload` string — Postback payload delivered via the messaging_postbacks webhook (required when type is postback)
    - `phone` string — Phone number, e.g. +14155551234 (required when type is phone; Facebook only)
  - `template` CommentAutomationTemplate — A Meta generic template (product card) sent as the automation's first DM. It REPLACES the plain `dmMessage` bubble: a Meta message carries one body shape, and a comment gets exactly one private reply, so the card and the text cannot both be delivered. Put your selling copy in `subtitle`. Mutually exclusive with `buttons` (sending both is a 400). Works on both the `comment` and `story_reply` triggers. Up to 10 elements, rendered as a horizontally swipeable carousel. Rendering confirmed on the Instagram and Messenger mobile apps.
    - `type` 'generic', required
    - `elements` CommentAutomationTemplateElement[], required
      - `title` string, required — Card headline (80 chars max). Also used as the Inbox preview for the sent DM.
      - `subtitle` string — Card description, e.g. the price or a short pitch (80 chars max).
      - `imageUrl` string, uri — Publicly reachable http(s) image rendered large above the card.
      - `buttons` object[] — Up to 3 card buttons. A generic template has NO phone button, on either platform. `url` buttons are click-tracked when linkTracking is on.
        - `type` 'url' | 'postback', required
        - `title` string, required
        - `url` string, uri — Target URL (required when type is url)
        - `payload` string — Postback payload delivered via the messaging_postbacks webhook (required when type is postback)
  - `commentReply` string — Optional public reply to the comment
  - `dmMessageVariations` string[] — Optional alternate DM texts for random rotation. When set, each triggered comment sends one picked at random from [dmMessage, ...dmMessageVariations], so repeat commenters get slightly different DMs (helps avoid identical-message patterns). Up to 5. Buttons are attached to whichever text is picked, not varied.
  - `commentReplyVariations` string[] — Optional alternate public replies, rotated at random alongside commentReply (picked independently of the DM). Up to 5.
  - `linkTracking` boolean — Wrap link buttons in the DM in a tracked redirect so clicks are counted (Link Clicks / CTR). Pass false to send links exactly as written. Defaults to on.
  - `clickTag` string — Optional tag applied to a contact when they click a tracked link (requires linkTracking). Lets you segment clickers for broadcasts/sequences.
  - `dmDelaySeconds` integer — Seconds to wait after the trigger before sending the DM. Omit or send 0 to reply immediately (the default). Max 86400 (24h). The trigger is still matched and deduplicated the moment the comment arrives, so a delay only moves when the response is sent.
  - `commentReplyDelaySeconds` integer — Seconds to wait before posting the public comment reply. Omit or send 0 to post it right after the DM (the default). The reply never goes out before the DM, so a value below dmDelaySeconds is raised to it. Ignored when trigger=story_reply, which has no public reply.
  - `alsoMatchInDms` boolean — Also fire these keywords on a plain inbound DM, so the automation answers people who message the keyword instead of commenting it. Requires at least one keyword (an empty keyword list means 'match anything', which would answer every inbound message) and is rejected on story_reply automations, which already trigger on DMs. Dedup is per door: a contact who already received the DM from their comment can still receive it from a DM.
  - `audience` CommentAutomationAudience — Who a comment automation answers. Instagram only - Meta exposes the follow relationship on no other platform, and only for people who have MESSAGED the account (a comment grants no consent). `whenUnknown` is therefore the important setting: it decides what happens for a first-time commenter.
    - `followerStatus` 'any' | 'follower' | 'non_follower'
    - `minFollowerCount` integer — Skip commenters with fewer followers than this. Omit for no size rule.
    - `whenUnknown` 'send' | 'skip' | 'verify' — What to do when Instagram will not reveal the follow relationship. * `send` (default) - deliver the DM anyway (fails open). * `skip` - stay silent. * `verify` - send `followGate.message` with a confirm button. Tapping it is a message, which grants consent, so the re-check on the tap resolves and the real DM (or `followGate.notFollowingMessage`) follows automatically.
  - `followGate` CommentAutomationFollowGate — Copy for the follow gate. Sensible defaults are used for any field left empty.
    - `message` string — Confirmation DM sent when whenUnknown=verify.
    - `buttonLabel` string — Confirm button label. Defaults to "I'm following".
    - `notFollowingMessage` string — Sent to a commenter we know does not follow (followerStatus=follower). Omit to stay silent on a keyword comment; a confirm tap always gets an answer.

## Response `200`

Automation created

- object
  - `success` boolean
  - `automation` object
    - `id` string
    - `name` string
    - `platform` string
    - `trigger` 'comment' | 'story_reply'
    - `platformPostId` string
    - `keywords` string[]
    - `matchMode` 'exact' | 'contains' | 'word' — How a keyword is compared with the comment. 'contains' (default) matches anywhere, even inside another word (keyword 'app' fires on 'happy'). 'word' matches the keyword only as a standalone word. 'exact' requires the whole comment to be exactly the keyword.
    - `excludeKeywords` string[] — Comments containing one of these never trigger the automation, even when a trigger keyword also matches. Compared using the same matchMode.
    - `typoTolerance` boolean — Only with matchMode=word: also fire on close misspellings of a keyword (one edit for 4-7 character keywords, two from 8 up). Keywords shorter than 4 characters are never fuzzy-matched.
    - `dmMessage` string
    - `buttons` DmButton[] — Inline DM buttons (up to 3). Omitted when none are set.
      - `type` 'url' | 'postback' | 'phone', required
      - `title` string, required — Button label (20 chars max)
      - `url` string, uri — Target URL (required when type is url)
      - `payload` string — Postback payload delivered via the messaging_postbacks webhook (required when type is postback)
      - `phone` string — Phone number, e.g. +14155551234 (required when type is phone; Facebook only)
    - `template` CommentAutomationTemplate — A Meta generic template (product card) sent as the automation's first DM. It REPLACES the plain `dmMessage` bubble: a Meta message carries one body shape, and a comment gets exactly one private reply, so the card and the text cannot both be delivered. Put your selling copy in `subtitle`. Mutually exclusive with `buttons` (sending both is a 400). Works on both the `comment` and `story_reply` triggers. Up to 10 elements, rendered as a horizontally swipeable carousel. Rendering confirmed on the Instagram and Messenger mobile apps.
      - `type` 'generic', required
      - `elements` CommentAutomationTemplateElement[], required
        - `title` string, required — Card headline (80 chars max). Also used as the Inbox preview for the sent DM.
        - `subtitle` string — Card description, e.g. the price or a short pitch (80 chars max).
        - `imageUrl` string, uri — Publicly reachable http(s) image rendered large above the card.
        - `buttons` object[] — Up to 3 card buttons. A generic template has NO phone button, on either platform. `url` buttons are click-tracked when linkTracking is on.
          - `type` 'url' | 'postback', required
          - `title` string, required
          - `url` string, uri — Target URL (required when type is url)
          - `payload` string — Postback payload delivered via the messaging_postbacks webhook (required when type is postback)
    - `commentReply` string
    - `dmMessageVariations` string[] — Alternate DM texts rotated at random with dmMessage. Omitted when none.
    - `commentReplyVariations` string[] — Alternate public replies rotated at random with commentReply. Omitted when none.
    - `linkTracking` boolean
    - `clickTag` string
    - `dmDelaySeconds` integer — Seconds waited after the trigger before the DM is sent. Absent when the DM goes out immediately.
    - `commentReplyDelaySeconds` integer — Seconds waited before the public reply is posted. Absent when it follows the DM immediately.
    - `audience` CommentAutomationAudience — Who a comment automation answers. Instagram only - Meta exposes the follow relationship on no other platform, and only for people who have MESSAGED the account (a comment grants no consent). `whenUnknown` is therefore the important setting: it decides what happens for a first-time commenter.
      - `followerStatus` 'any' | 'follower' | 'non_follower'
      - `minFollowerCount` integer — Skip commenters with fewer followers than this. Omit for no size rule.
      - `whenUnknown` 'send' | 'skip' | 'verify' — What to do when Instagram will not reveal the follow relationship. * `send` (default) - deliver the DM anyway (fails open). * `skip` - stay silent. * `verify` - send `followGate.message` with a confirm button. Tapping it is a message, which grants consent, so the re-check on the tap resolves and the real DM (or `followGate.notFollowingMessage`) follows automatically.
    - `followGate` CommentAutomationFollowGate — Copy for the follow gate. Sensible defaults are used for any field left empty.
      - `message` string — Confirmation DM sent when whenUnknown=verify.
      - `buttonLabel` string — Confirm button label. Defaults to "I'm following".
      - `notFollowingMessage` string — Sent to a commenter we know does not follow (followerStatus=follower). Omit to stay silent on a keyword comment; a confirm tap always gets an answer.
    - `alsoMatchInDms` boolean — Whether these keywords also fire on a plain inbound DM.
    - `isActive` boolean
    - `stats` object
      - `totalTriggered` integer
      - `totalSent` integer
      - `totalFailed` integer
    - `createdAt` string, date-time

## Other responses

- `400` — Validation error
- `401` — Unauthorized
- `409` — Active per-post automation already exists for this platformPostId. Does not apply to account-wide automations.

---

[API](https://skmtc.net/zernio/apis/zernio-api.md) · [All operations](https://skmtc.net/zernio/apis/zernio-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/zernio/zernio-api/revisions/3c7cf84cec44/schema)
