Comment-to-DM Automations API: Instagram keyword DMs
A comment-to-DM automation answers a keyword with a private message. When somebody comments "guide" on your Instagram post, the account sends them the guide in a DM, once per person, and can post a public reply under the comment ("Sent you a DM!"). The same automation can listen to a keyword in a DM to the account or in a reply to a story.
Automations are managed with the Automations endpoints (GET/POST /automations, GET/PATCH/DELETE /automations/:id, GET /automations/:id/events), or attached to a new post with comment_dm_automation on POST /posts/create. Everything you create here shows up on the Automations page in the dashboard, and the other way round.
Enabling automation access
The scopes are opt-in, like the inbox scopes: an automation sends DMs in your account's name, and its activity carries what people wrote. When you create an API key, turn on Automations to grant automations:read (list, get, activity) and automations:write (create, update, delete, and comment_dm_automation on posts). Existing keys are not given these scopes automatically; without them every endpoint answers 403 insufficient_scope.
Creating an automation
Code
| Field | Meaning |
|---|---|
trigger.kinds | What starts the automation: comment (default) = a comment on a post or reel, dm = a keyword in a DM to the account, story_reply = a keyword in a reply to a story. One automation can listen to several. |
trigger.scope | For the comment kind: post (default) = one post, next_post = the next Instagram post the workspace publishes, any = every post of the account. |
post_id | For scope post: an OmniSocials post that goes to Instagram, scheduled or already live. A scheduled post starts listening once it is live. One automation per post. |
instagram_media_id | For scope post: the numeric id of a post or reel that was published outside OmniSocials (from Instagram's API, GET /me/media). OmniSocials reads it from Instagram to check it belongs to the connected account. Pass post_id or instagram_media_id, not both. |
trigger.keywords | Up to 20 keywords, matched case-insensitively. Required unless match is any. |
trigger.match | word (default): the keyword stands on its own ("guide", "Guide!" match; "linkedin" does not match "link"). contains: the keyword appears anywhere. any: every comment or message with text. |
actions.dm.text | The DM, max 1000 characters. Sent once per person per automation. |
actions.public_reply | A public reply under the comment, for the comment kind only. One of variations (up to 10) is picked at random per comment. |
limits.max_sends | Stop after this many DMs; the automation becomes completed. |
limits.stop_at | Stop at this time (ISO 8601). |
status | active (default) or paused to create it without turning it on. |
The response is the automation with instagram_media_id (set once the post is live), a post snapshot (source is omnisocials or instagram, with caption, thumbnail_url, permalink) and stats (matched, sent, skipped, failed, last_event_at).
A keyword in any DM or story reply, no post needed:
Code
Instagram rules
- One private reply per comment, and only within 7 days of the comment. A comment older than 7 days is skipped (
window_passed). - A follow-up message is possible only after the person answers (Meta's 24-hour window).
- The DM goes to each person once per automation (
already_sent). - OmniSocials sends at most 180 DMs an hour per account (
rate_limited); Instagram's own limit for private replies is 750 an hour. - Public replies belong to comments; a DM or story-reply trigger has none.
Updating, pausing, deleting
PATCH /automations/:id takes the same fields. status: "paused" pauses, "active" resumes. trigger, actions and limits are merged into the stored sections: keys you omit keep their value, so { "trigger": { "keywords": ["pdf"] } } keeps the kinds, the scope and the match, and a new dm.text keeps the public reply. public_reply.enabled: false (or variations: []) turns the public reply off; limits.max_sends: null removes that limit. Another post_id or instagram_media_id moves the automation to that post. A completed automation starts again when its limits or trigger change. DELETE /automations/:id removes it and its activity; DMs already sent stay sent.
Activity
GET /automations/:id/events returns one row per comment or message the automation acted on, newest first, with counts per outcome and offset pagination:
Code
outcome is sent, skipped (with reason: already_sent, limit_reached, stopped, window_passed, rate_limited, and reason_label in plain words), failed (with Instagram's error in reason) or pending. Filter with ?outcome=. The text of a DM or story reply is private: trigger_text on dm and story_reply rows is returned only to a key with the inbox:read scope; a comment is public and always returned.
On a new post
Pass comment_dm_automation on POST /posts/create to bind an automation to the post you are creating (Instagram must be in accounts). It is the create body without post_id, scope and kinds: always this post, comment trigger. The created automation comes back as comment_dm_automation on the post, GET /posts/:id returns it, and PATCH /posts/:id replaces it with a new object or removes it with null. See Creating posts.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | Bad input; the message says what (for example "Write the DM", "Add at least one keyword, or choose any comment") |
| 400 | post_not_instagram | The post does not go to Instagram |
| 400 | post_required | Scope post without post_id or instagram_media_id |
| 403 | insufficient_scope | The key has no automations:read / automations:write |
| 404 | post_not_found, instagram_media_not_found, not_found | No such post, Instagram media or automation in this workspace |
| 409 | instagram_not_connected, instagram_needs_reauth | Connect or reconnect Instagram first |
| 409 | automation_exists | The post already has an automation; the response carries its automation_id |
Using it from an AI assistant
The MCP server has list_automations, get_automation, create_automation, update_automation, delete_automation and get_automation_activity, and create_post takes comment_dm_automation. The CLI has the automations:* commands and the --dm-* flags on posts:create.