Instagram comment to DM API
Comment to DM.From your code.
Instagram's API can DM someone who comments: Meta calls it a private reply. Building on it means a Meta app, App Review, webhooks and limits like 750 an hour and one reply per comment. Or call DMpop's REST API: one request creates a keyword campaign, and DMpop handles the Instagram side.
Updated
curl -X POST https://app.dmpop.com/api/v1/campaigns \ -H "Authorization: Bearer $DMPOP_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Reel: free guide", "nextReel": true, "keywords": ["GUIDE"], "message": "Hey {username}, here is the guide.", "link": "https://example.com/guide", "buttonLabel": "Get the guide", "requireFollow": true }'Instagram private reply API: building on Meta directly
The Meta route.What it takes.
If you want to own the whole thing, this is the list. Every point links to Meta's own docs, checked November 2026.
- A Meta developer app using the Instagram API with Instagram Login. It serves Instagram professional accounts (Business or Creator) without a linked Facebook Page.
- Permissions. Meta's private replies guide lists
instagram_business_basicandinstagram_business_manage_comments; the messaging guide addsinstagram_business_manage_messages. - App Review and Advanced Access. Standard Access only covers accounts you own or manage. To serve anyone else you need Advanced Access, and Meta's overview says that needs App Review and Business Verification.
- Webhooks for new comments. Meta's webhook setup says comment notifications need Advanced Access and a Live app, and private Instagram accounts don't get them. Webhooks also don't arrive for every comment, so plan a polling fallback.
- The private reply call. A POST to
/<IG_ID>/messageswithrecipient.comment_idinstead of a user ID. - The rules. 750 private replies an hour per account. One private reply per comment. Within 7 days of the comment. Follow-ups only once the person responds, and within 24 hours of that.
- Everything around it. Keyword matching, skipping your own comments, remembering which comments you answered, a queue under the hourly limit, retries, logs, and token refreshes.
# Meta's own call: one private reply to one commentcurl -X POST "https://graph.instagram.com/<API_VERSION>/<IG_ID>/messages" \ -H "Authorization: Bearer <INSTAGRAM_USER_ACCESS_TOKEN>" \ -H "Content-Type: application/json" \ -d '{ "recipient": { "comment_id": "<COMMENT_ID>" }, "message": { "text": "Here is the guide you asked for" } }'That call is the easy part. The work is everything that decides when to make it, and never twice for the same comment. If you're building a product on Instagram messaging, it's worth doing. If you want comment-to-DM inside your own app, script or workflow, it's a lot of plumbing.
The same trade-off for no-code tools is on Zapier, Make and n8n comment to DM.
Send Instagram DM via API: DMpop REST API reference
Or one call.The reference.
You describe the campaign; DMpop watches the comments and sends the DMs within Instagram's rules. Here is the whole API on one page.
Base URL, auth and limits
- Base URL:
https://app.dmpop.com/api/v1. Requests and responses are JSON. - Auth:
Authorization: Bearer dmp_.... Create a key in DMpop under Settings → API keys and MCP. It's shown once. Owners and admins can create keys, up to 10 active at a time. - A key acts for its whole workspace, every connected account included. Treat it like a password.
- Rate limit: 60 requests a minute per key. The API is on every plan, including Free.
- Success:
{ "data": ... }. Errors:{ "error": { "message", "details" } }with a status code.
Endpoints
| Endpoint | What it does | Key details |
|---|---|---|
GET /accounts | The Instagram accounts connected to the workspace. | Returns id, username, connectedAt. |
GET /posts | Recent posts and reels, newest first, to get a postId. | Query: accountId (defaults to the most recently connected account), limit (1 to 25, default 12). Returns id, caption, type (reel, image, video, carousel_album), permalink, publishedAt. |
GET /campaigns | Every campaign in the workspace, newest first. | Each with its settings and stats. |
POST /campaigns | Create a campaign. | Answers 201 with the new campaign. |
GET /campaigns/{id} | One campaign. | Settings plus sent, skipped, failed, clicks and click rate. |
PATCH /campaigns/{id} | Change a campaign. | Only the fields you send change. {"active": false} pauses it. Sending postId moves it to that post. The account can't be changed. |
DELETE /campaigns/{id} | Delete a campaign. | Returns {"id": "...", "deleted": true}. |
Campaign fields
For POST and PATCH. Every field except accountId can be changed with PATCH.
| Field | What it does |
|---|---|
name | Required. A label, up to 100 characters. |
message | Required. The DM, up to 1,000 characters. {username} becomes the person's handle; {link} puts the tracked link in the text. |
accountId | Which connected account. Defaults to the most recently connected one, so set it if you have more than one. |
postId · nextReel · anyPost | Pick one target: a post or reel by ID, your next reel (attached within about five minutes of it going live), or every post. |
keywords · anyWord | Up to 10 keywords, 50 characters each. Or anyWord: true to reply to every comment. |
wholeWord | Whole-word matching. Default true, so GUIDE doesn't match guidebook. |
link · buttonLabel | The link to send and the button text (up to 20 characters). Taps are counted unless trackClicks is false. |
trackClicks | Default true. False sends links exactly as given, with no redirect and no click counting. For affiliate programmes that don't allow redirects. |
secondLink · secondButtonLabel | An optional second link button. |
publicReplies | Up to 10 public replies posted under the comment; one is picked at random each time. |
openingMessage · openingButtonLabel | An opener with a button, sent first; the link follows when it's tapped. Needs both fields. |
requireFollow · followPrompt · followButtonLabel | The follow gate. DMpop checks the follow with Instagram before sending the link. |
followUpMessage · followUpDelayMinutes | A later message, 0 to 1,440 minutes after. |
alsoOnDms | Also reply when someone sends the keyword in a DM or a Story reply. |
active | Default true. Send false to create it paused. |
Instagram automation API for developers: code samples
curl, Node,Python.
Set DMPOP_API_KEY in your environment. These run as they are.
# Connected Instagram accountscurl https://app.dmpop.com/api/v1/accounts \ -H "Authorization: Bearer $DMPOP_KEY"# Recent posts and reels of one account, to get a postIdcurl "https://app.dmpop.com/api/v1/posts?accountId=ACCOUNT_ID&limit=5" \ -H "Authorization: Bearer $DMPOP_KEY"import osimport requestsAPI = "https://app.dmpop.com/api/v1"HEADERS = {"Authorization": f"Bearer {os.environ['DMPOP_API_KEY']}"}# Every campaign in the workspace, with its numberscampaigns = requests.get(f"{API}/campaigns", headers=HEADERS).json()["data"]for c in campaigns: s = c["stats"] print(c["account"]["username"], c["name"], s["sent"], s["clicks"], s["ctr"])# Pause the first oner = requests.patch( f"{API}/campaigns/{campaigns[0]['id']}", headers=HEADERS, json={"active": False},)r.raise_for_status()const API = "https://app.dmpop.com/api/v1";const headers = { Authorization: `Bearer ${process.env.DMPOP_API_KEY}`, "Content-Type": "application/json",};const get = (path) => fetch(API + path, { headers }).then((r) => r.json());// 1. Pick the account and its newest reelconst { data: accounts } = await get("/accounts");const accountId = accounts[0].id;const { data: posts } = await get(`/posts?accountId=${accountId}&limit=5`);const reel = posts.find((p) => p.type === "reel");// 2. Put a campaign on itconst res = await fetch(API + "/campaigns", { method: "POST", headers, body: JSON.stringify({ name: "Newest reel: guide", accountId, postId: reel.id, keywords: ["GUIDE", "PDF"], message: "Hey {username}, here's the guide.", link: "https://example.com/guide", buttonLabel: "Get the guide", publicReplies: ["Sent! Check your DMs"], }),});const body = await res.json();if (!res.ok) throw new Error(body.error.message);console.log(body.data.id, body.data.links[0].trackedUrl);Response and errors
What comes back.And what fails.
{ "data": { "id": "cmv...", "name": "Reel: free guide", "active": true, "account": { "id": "...", "username": "yourhandle" }, "trigger": "next_reel", "postId": null, "postUrl": null, "keywords": ["GUIDE"], "anyWord": false, "message": "Hey {username}, here is the guide.", "links": [ { "url": "https://example.com/guide", "label": "Get the guide", "trackedUrl": "https://app.dmpop.com/r/..." } ], "publicReplies": [], "requireFollow": true, "alsoOnDms": false, "createdAt": "2026-11-09T10:00:00.000Z", "stats": { "sent": 0, "skipped": 0, "failed": 0, "clicks": 0, "ctr": 0 } }}{ "error": { "message": "Invalid request body.", "details": { "formErrors": ["Unrecognized key: \"keyword\""], "fieldErrors": {} } }}trigger is post, next_reel or any_post. Links come back with the trackedUrl people actually get, DMpop's redirect that counts clicks. With trackClicks set to false, trackedUrl is null and the DM carries your url exactly as given.
stats.skipped counts DMs DMpop held back for a logged reason, for example: the comment's private reply was already used by another campaign, the plan's monthly DMs ran out, or the hourly limit was still full after retries. ctr is clicks as a percentage of DMs sent.
Errors DMpop returns
| Status | Message | When |
|---|---|---|
400 | Invalid request body. | Not JSON, a wrong type, or an unknown field. Unknown fields are rejected, so a typo like keyword fails loudly. details names the field. |
400 | Invalid campaign. | The fields are valid but don't make a campaign: no target, no keywords without anyWord, or an opening message without a button label. |
400 | Connect Instagram before creating campaigns | No Instagram account is connected, or the accountId isn't one of this workspace's. |
401 | Missing API key. Send it as: Authorization: Bearer dmp_... | No key, or not a DMpop key. |
401 | This API key is invalid or has been revoked. | The key was revoked in Settings, or mistyped. |
403 | Your Free plan runs 5 campaigns at a time. … | Creating or resuming a campaign over the plan's live-campaign limit. Create it paused, or upgrade. |
404 | Campaign not found. · Account not found. | The ID isn't in this workspace. |
429 | Rate limit reached (60 requests a minute). Try again shortly. | Over 60 requests a minute on this key. Wait for the next minute. |
500 | Something went wrong on our side. Try again. | Our fault. Retry, and email us if it keeps happening. |
Meta errors you'll meet with private replies
Building on Meta directly, these come back from the Graph API. On DMpop, they show as Failed in the DM log with Instagram's own message.
| Error | What it means | What to do |
|---|---|---|
| “The comment is invalid for a private reply” | Instagram refused a private reply to that comment. The usual cause: the comment's one private reply was already used, by another tool, a second campaign or an earlier retry. | That comment can't get another private reply. Make sure only one tool and one campaign answer each post. |
| “Outside of allowed window” | Sent too late. A private reply must go out within 7 days of the comment. Any later message needs the person to have written back, within 24 hours of their reply. | Nothing to retry for those comments. Answer new comments promptly, and use a button so the person's tap opens the conversation. |
| “The requested user cannot be found” | Instagram couldn't find someone to deliver to. Often the account was deactivated or deleted, or can't be messaged. Meta doesn't publish a full list of causes. | Usually nothing to fix on your side. A public reply under the comment still reaches them. |
| Rate limit errors | Over Meta's 750 private replies an hour for that Instagram account. | Queue and retry later. DMpop retries every 30 minutes, up to 3 times, then logs a skip. |
| Token or permission errors | The access token expired, or the person removed the app's access in Instagram's settings. | Every send fails at once. Reconnect the account through Meta's login. |
Meta rules from its developer docs, checked November 2026. Meta doesn't document every cause of each error message; where it doesn't, we say what we see most often. DMpop is not affiliated with Meta or Instagram.
API, MCP or dashboard
Same campaigns.Three doors.
The API, the MCP server and the dashboard create exactly the same campaigns. Use the API from your own code or a workflow tool, and the MCP server when an AI assistant like ChatGPT, Claude, Claude Code or Cursor should do it from a sentence. The MCP server has seven tools that map to these endpoints, and it can't DM anyone either.
Managing campaigns for several clients? Pass accountId on every call and read DMpop for agencies. Before a big launch, read Instagram DM limits. Need a keyword? Try the keyword ideas tool. The full reference is in the developer docs.
Questions
API questions,answered.
Is there an Instagram API to send a DM when someone comments?
Yes. Meta's Instagram API has private replies: you send a message with the comment's ID as the recipient, and it lands in the commenter's DMs. You need your own Meta app, the comments and messages permissions, Advanced Access through App Review to serve other people's accounts, and a webhook for new comments. DMpop's REST API does all of that for you behind one call that creates a campaign.
What is the Instagram private reply API limit?
Meta's rate-limits page gives 750 calls an hour per Instagram professional account for private replies to comments on posts and reels. On top of that: one private reply per comment, sent within 7 days of the comment.
Which permissions does a private reply need?
With the Instagram API with Instagram Login, Meta's private replies guide lists instagram_business_basic and instagram_business_manage_comments, and its messaging guide lists instagram_business_manage_messages for sending messages. To serve accounts you don't own, you need Advanced Access, which needs App Review and Business Verification.
Can DMpop's API send a DM to any user?
No. The API manages campaigns: it lists accounts and posts and creates, changes or deletes campaigns. DMs only go to people who comment your keyword, or DM it to you or reply to your Story with it when alsoOnDms is on. That keeps you inside Instagram's rules.
Is DMpop's API free?
The API is on every plan, including Free (1,000 DMs a month). Each key can make 60 requests a minute. DMs themselves count toward your plan's monthly DMs.
Do I need a Meta developer app to use DMpop's API?
No. You connect Instagram to DMpop through Meta's own login, and DMpop's app handles permissions, webhooks and limits. You only need a DMpop API key from Settings → API keys and MCP. Owners and admins can create keys.
Does the API return click and send numbers?
Yes. Every campaign comes with stats: sent, skipped, failed, clicks and ctr (clicks as a percentage of DMs sent). Each person's click counts once per campaign.
Is there an SDK?
No SDK. It's a small JSON API, so fetch, requests or curl is all you need. If an AI assistant should drive it, use DMpop's MCP server instead, which exposes the same actions as tools.
API on every plan
One request.Let the DM pop.
Get an API key on any plan, Free included. The Free plan sends 1,000 DMs a month, and paid plans start at ₹99.