# Instagram comments

> Send comments on your Instagram professional account's posts to Dimension Labs with the universal API.

This brings in the comments and replies on your own Instagram posts: each post as one conversation, with what the public wrote as customer messages and your caption and replies as your side. You run it on your side: a small script pulls from Meta's Instagram API and posts each message to the [universal API](/api/tracker).

## What you need

- An Instagram professional account (Business or Creator). Meta's Instagram API only serves professional accounts.
- A Meta app with one of Meta's three Instagram API setups, and an access token for your account:

  | Setup | Host | Token | Permissions |
  |---|---|---|---|
  | Instagram API with Facebook Login (account linked to a Facebook Page) | `graph.facebook.com` | Facebook Page access token | `instagram_basic`, `instagram_manage_comments`, `pages_read_engagement` |
  | Instagram API with Instagram Login | `graph.instagram.com` | Instagram User access token | `instagram_business_basic`, `instagram_business_manage_comments` |
  | Instagram API with Unified Login (Meta's recommendation for new integrations) | `graph.facebook.com` | Business Integration System User access token | `instagram_basic`, `instagram_manage_comments` |

  If your role on the linked Page was granted through Business Manager, Meta also asks for `ads_management` or `ads_read`.
- **Standard Access** is enough for an app that serves only accounts you own or manage. Advanced Access, through App Review, is for serving other people's accounts.
- A Dimension Labs API key for a new **Universal API** source. See [Your API key](/api/tracker#your-api-key).

## 1. Get API access in Instagram

Meta's steps are in the [Instagram Platform overview](https://developers.facebook.com/docs/instagram-platform/overview/) and [Get started with Facebook Login](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-facebook-login/get-started/).

1. Create a Meta app and add the Instagram setup you picked. Sign in as a person who manages the account and grant the permissions above.
2. With Facebook Login, find your Instagram account id from the linked Page: `GET https://graph.facebook.com/v26.0/PAGE_ID?fields=instagram_business_account`. The `instagram_business_account.id` is your IG User ID. A long-lived Page access token (see the [Facebook Page comments](/integrations/facebook-comments) guide) does not expire on a schedule.
3. With Instagram Login, the long-lived Instagram User token lasts 60 days. Refresh it before then with `GET https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=...` (the token must be at least 24 hours old). Set `IG_HOST=graph.instagram.com` for the code below.
4. Meta puts the API version in the path. This guide uses `v26.0`, the version Meta's reference showed when it was checked. Pin a version and move it forward on purpose.

## 2. Pull new comments

```bash
curl -G "https://graph.facebook.com/v26.0/IG_USER_ID/media" \
  --data-urlencode "fields=id,caption,timestamp,media_type,permalink" \
  --data-urlencode "since=1759276800" --data-urlencode "access_token=$IG_ACCESS_TOKEN"

curl -G "https://graph.facebook.com/v26.0/IG_MEDIA_ID/comments" \
  --data-urlencode "fields=id,text,timestamp,from,user,parent_id,replies{id,text,timestamp,from,user,parent_id}" \
  --data-urlencode "access_token=$IG_ACCESS_TOKEN"
```

- `/{ig-user-id}/media` lists your posts and supports time-based pagination with `since` and `until`. Meta returns at most the 10,000 most recently created media, and no Stories (they have their own endpoint).
- `/{ig-media-id}/comments` returns top-level comments only, newest first, at most 50 per query. Replies come only when you request the `replies` field. Meta says comments **can't be filtered by timestamp**, so the code reads every comment on recent posts and skips the ones it has already sent.
- **Pagination:** follow `paging.next` until it is missing, for comments and for each comment's replies.
- **Your replies:** Meta returns the `user` field on a comment only when your account wrote it. That is how the code tells your replies from the public's.
- **Rate limits (Meta's):** Instagram calls count against Meta's Business Use Case limits; a throttled call returns error code `80002`. Meta strongly recommends webhooks for new comments to avoid rate limiting.

**Storing the data.** Meta's Platform Terms apply to what you pull. They let you use a service provider such as Dimension Labs only under a written agreement that it processes the data for you and for no other purpose. They also ask you to delete Platform Data when you no longer need it for a legitimate business purpose, or when Meta or the person asks. You can't delete individual records in Dimension Labs yourself: email support@dimensionlabs.io with the source and the comment ids for a targeted deletion.

## 3. Map the fields

Each post is one conversation. Your caption is the first, outgoing message; each comment and reply from the public is incoming; your account's replies are outgoing, in the same session.

| Dimension Labs | Instagram | Notes |
|---|---|---|
| `text` | media `caption`, comment `text` | Skip comments with no text. |
| `userId` | comment `from.id` | The Instagram-scoped id of the commenter. Your account's caption and replies use `instagram-account-IG_USER_ID`. Fall back to the `sessionId`. |
| `incoming` | comment `user` | `false` when `user` is present (your account wrote it), `true` otherwise. The caption is always `false`. |
| `sessionId` | media `id` | Prefix it, for example `instagram-17895695668004550`. |
| `conversationId` | media `id` | Same value as `sessionId`. |
| `dimensionlabs_timestamp` | `timestamp` | Like `2017-08-31T19:16:02+0000`. Convert with `toMillis`. A comment within a second of its post is moved a second later. |
| `platformJson` | IG User ID, media `id`, `media_type`, comment `id`, `parent_id` | `parent_id` ties a reply to the comment it answers. |

**Personal data.** Don't send `from.username` or `username`. Comments can hold names and handles: redact them before you send. See [PII redaction](/data/pii-redaction).

## 4. Send it

```js
// instagram-comments.mjs. Environment: IG_USER_ID, IG_ACCESS_TOKEN, DL_API_KEY.
// Optional: IG_HOST (default graph.facebook.com; graph.instagram.com for Instagram Login),
// IG_GRAPH_VERSION (default v26.0), IG_LOOKBACK_DAYS (default 30).


const HOUR = 60 * 60 * 1000, DAY = 24 * HOUR;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const IG = process.env.IG_USER_ID;
const API = `https://${process.env.IG_HOST || 'graph.facebook.com'}/${process.env.IG_GRAPH_VERSION || 'v26.0'}`;
const THROTTLED = new Set([4, 17, 32, 613, 80002]); // Meta's rate limit error codes (80002 is Instagram's)
const FIELDS = 'id,text,timestamp,from,user,parent_id';

// Retries rate limits (429 or a throttle error code) and server errors (5xx), honoring Retry-After when it is sent.
async function get(url) {
  for (let attempt = 0; ; attempt++) {
    const u = new URL(url);
    if (!u.searchParams.has('access_token')) u.searchParams.set('access_token', process.env.IG_ACCESS_TOKEN);
    const res = await fetch(u);
    const body = await res.json().catch(() => ({}));
    if (res.ok) return body;
    const throttled = res.status === 429 || THROTTLED.has(body.error?.code);
    if ((!throttled && res.status < 500) || attempt === 5) throw new Error(`Instagram returned ${res.status}: ${JSON.stringify(body)}`);
    await sleep((Number(res.headers.get('retry-after')) || 30 * 2 ** attempt) * 1000);
  }
}

// Follows paging.next until the last page.
async function* pages(url) {
  while (url) {
    const page = await get(url);
    yield page.data ?? [];
    url = page.paging?.next;
  }
}

// `user` is returned only when your own account wrote the comment.
const mine = (c) => Boolean(c.user);

// Sends every caption, comment and reply on your media created at or after `since` (Unix ms). Returns the new `since`.
export async function syncInstagramComments({ since, historical = false }) {
  let newest = since;
  const from = historical ? since : since - Number(process.env.IG_LOOKBACK_DAYS || 30) * DAY; // comments arrive on older media too
  const send = (msg) => sendToDimensionLabs(msg, { historical: historical || msg.dimensionlabs_timestamp < Date.now() - HOUR });
  const mq = new URLSearchParams({ fields: 'id,caption,timestamp,media_type,permalink', since: String(Math.floor(from / 1000)) });
  for await (const media of pages(`${API}/${IG}/media?${mq}`)) {
    for (const m of media) {
      const sessionId = `instagram-${m.id}`;
      const mediaTs = toMillis(m.timestamp);
      const base = { sessionId, conversationId: sessionId, platformJson: { igUserId: IG, mediaId: m.id, mediaType: m.media_type } };
      if (m.caption?.trim() && mediaTs >= since) {
        await send({ ...base, text: m.caption.trim(), userId: `instagram-account-${IG}`, incoming: false, dimensionlabs_timestamp: mediaTs,
          platformJson: { ...base.platformJson, kind: 'caption' } });
        newest = Math.max(newest, mediaTs);
      }
      const cq = new URLSearchParams({ fields: `${FIELDS},replies{${FIELDS}}` });
      for await (const comments of pages(`${API}/${m.id}/comments?${cq}`)) {
        for (const top of comments) {
          // Replies come with the comment; if there are more than one page of them, follow the rest.
          const replies = [...(top.replies?.data ?? [])];
          for (let next = top.replies?.paging?.next; next;) { const p = await get(next); replies.push(...(p.data ?? [])); next = p.paging?.next; }
          for (const c of [top, ...replies]) {
            const created = toMillis(c.timestamp);
            if (created < since || !c.text?.trim()) continue; // already sent, or no text
            newest = Math.max(newest, created);
            await send({ ...base, text: c.text.trim(), incoming: !mine(c),
              userId: mine(c) ? `instagram-account-${IG}` : c.from?.id ? `instagram-${c.from.id}` : sessionId,
              dimensionlabs_timestamp: Math.max(created, mediaTs + 1000),
              platformJson: { ...base.platformJson, kind: c.parent_id ? 'reply' : 'comment', commentId: c.id, parentId: c.parent_id } });
          }
        }
      }
    }
  }
  return newest;
}
```

On every run the code reads the posts from the last 30 days (`IG_LOOKBACK_DAYS`) and all their comments, because people keep commenting on older posts. It sends only what was written since the last run. A busy account with a long lookback uses many calls: shorten the lookback, or move new comments to Meta's webhooks.

## 5. Run it

Save the code as `instagram-comments.mjs` in a project set up as in [Set up your project](/api/tracker#set-up-your-project), and run it with the [scheduled sync](/api/scheduled-sync) runner: `import { syncInstagramComments as sync } from './instagram-comments.mjs';`.

- **History first:** `node run.mjs 2025-01-01` sends every caption, comment and reply on posts since that date to `/trackhistorical`. Then start a [backfill](/dimensions/backfill) from the **Backfill** tab.
- **Then every 15 minutes:** the scheduled job sends new comments to `/track`, and they are enriched automatically every day. Anything more than an hour old when the job sees it goes to `/trackhistorical`, and needs a backfill.
- **Re-runs are safe:** duplicates are removed automatically.

When Meta throttles the app, the code waits and tries again. If you have comments in a file instead, [upload it](/data/upload-a-file) and check it first with the [file checker](/data/check-a-file), then run a backfill.

## Instagram reference

- [Instagram Platform overview](https://developers.facebook.com/docs/instagram-platform/overview/)
- [Comment moderation](https://developers.facebook.com/docs/instagram-platform/comment-moderation/)
- [IG Media comments](https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-media/comments/)
- [IG Comment](https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-comment/)
- [IG Comment replies](https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-comment/replies/)
- [IG User media](https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/ig-user/media/)
- [Get started with Facebook Login](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-facebook-login/get-started/)
- [Refresh access token](https://developers.facebook.com/docs/instagram-platform/reference/refresh_access_token/)
- [Rate limits](https://developers.facebook.com/docs/graph-api/overview/rate-limiting/)
- [Meta Platform Terms](https://developers.facebook.com/terms/)

Checked against Meta's documentation on 2026-10-26.
