# Instagram Comments Scraper — Scrape Instagram Comments, Replies (`steadyfetch/instagram-comments-scraper`) Actor

Scrape Instagram comments and their replies for any post, reel or IGTV URL — text, author, likes, timestamps. No login. Replies open thread by thread until the run's reply budget is spent, and each comment says whether its thread was opened. A reply costs the same. From $1.00 per 1,000 comments.

- **URL**: https://apify.com/steadyfetch/instagram-comments-scraper.md
- **Developed by:** [Steadyfetch Team](https://apify.com/steadyfetch) (community)
- **Categories:** Social media, Lead generation, Automation
- **Features:** Limited permissions, Multi-tenant, Server interface
- **Stats:** 70 total users, 26 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 comments

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Instagram Comments Scraper — Scrape Instagram Comments, Replies

Paste the Instagram post, reel or IGTV links you want and get the comments underneath them: text, author, likes, timestamp, and the replies in each thread. No login, no cookies, no proxy to configure. **Your limit is per post — set 200 and the run delivers up to 200 from each link**, and a comment we could not deliver is never charged. **Replies cost the same as a top-level comment**, on the same single event. Nothing is charged for starting a run.

**Using an AI agent?** Pin this actor in Apify's MCP server with one link: `https://mcp.apify.com?tools=steadyfetch/instagram-comments-scraper`

- **Actor id:** `steadyfetch/instagram-comments-scraper`
- **Input:** `{ "postUrls": ["DcOX3hWFiey"] }` — the one field to set. A bare post code as shown, or the whole post, reel or IGTV link you copied. Add `"resultsLimit": 200` and `"includeReplies": true` to set the per-post limit and pull the threads.
- **Cap the bill:** set `maxTotalChargeUsd` on the run (a run option, not Actor input), e.g. `0.50` — the run stops when it reaches it.
- **Price:** from **$1.00 per 1,000 comments** on Gold and above, $2.40 per 1,000 on the Apify free plan — a reply is charged on that same event, at that same price. No start fee, and a comment we could not deliver is never charged. Full table below.
- **How often it changes:** comments keep arriving for days after a post goes up and the busiest hours are the first few, so the useful pattern is the same links again tomorrow. A re-run charges nothing for what you hold: it reads on past the comments you already have to the ones you do not have yet.
- **Run it on a schedule:** save your input as a Task (Actor page → Save as a new task), then add that task to an Apify Schedule (Console → Schedules → Create) with a cron such as `0 7 * * *` for daily. Comments this account already received in the last 90 days are skipped and not charged again, so a daily watch on the same links pays only for what is new.

*Unofficial. Not affiliated with, endorsed by, or sponsored by Instagram or Meta Platforms, Inc.*

### What you get

One row per comment, at one price — a reply is a row like any other, and there is no second charge for "threads":

| Column | What it holds |
|---|---|
| `commentId` | the comment's own Instagram id |
| `text` | what they wrote (empty when the comment is a GIF — `isGif` says so) |
| `gifUrl` · `gifWidth` · `gifHeight` · `gifTitle` | the GIF a GIF comment posted: a link to the image, its width and height in pixels, and its title (`null` on a text comment). The link is signed by Instagram and stops working about a day after the run, so save the GIF if you need it later |
| `username` · `userFullName` · `userId` · `userIsVerified` · `userIsPrivate` | who wrote it, and whether their account is private |
| `likeCount` | likes on the comment |
| `isReply` · `parentCommentId` · `repliedToCommentId` | whether it is a reply, to which comment, and to which reply inside the thread |
| `replyCount` · `repliesCollected` | how many replies the comment has, and whether this run opened the thread |
| `createdAt` | when it was posted, ISO-8601 UTC |
| `isPinned` · `isEdited` · `isByPostAuthor` | pinned by the author, edited, or left by the account that posted |
| `postShortcode` · `postUrl` · `source` | which post it belongs to (`source` is always `instagram`) |
| `charged` · `status` · `statusReason` | whether this row was billed, and why, so your invoice reconciles from the dataset itself |

Replies land directly under the comment they answer, so the thread reads in order. The requesting network address is stripped out of every link before it reaches your dataset.

### What a row looks like

One delivered row from a real run — the post `DcOX3hWFiey`, 2026-09-11. Every comment is one `comment` charge, and a reply costs the same as a top-level comment: `isReply` and `parentCommentId` tell them apart. Profile pictures are signed and time-limited by Instagram, so they are shown here as placeholders.

```json
{
  "commentId": "17979728991110710",
  "postShortcode": "DcOX3hWFiey",
  "postUrl": "https://www.instagram.com/p/DcOX3hWFiey/",
  "text": "Image description: The Tarantula Nebula—vibrant and colorful. Clouds of red, yellow, orange, and green crest over a background of deep blue haze, black space, and red stars. Bright spots of light line the center, causing parts of the clouds to glow luminous gold.",
  "username": "nasa",
  "userId": "528817151",
  "userFullName": "NASA",
  "userIsVerified": true,
  "userIsPrivate": false,
  "userProfilePicUrl": "https://…",
  "isReply": false,
  "parentCommentId": null,
  "repliedToCommentId": null,
  "likeCount": 2213,
  "replyCount": 0,
  "createdAt": "2026-08-19T14:11:50.000Z",
  "isPinned": true,
  "isEdited": false,
  "isByPostAuthor": true,
  "isGif": false,
  "gifUrl": null,
  "gifWidth": null,
  "gifHeight": null,
  "gifTitle": null,
  "repliesCollected": true,
  "source": "instagram",
  "input": "https://www.instagram.com/p/DcOX3hWFiey/",
  "sourceIndex": 0,
  "scrapedAt": "2026-09-11T10:25:40.411Z",
  "repeat": false,
  "charged": true,
  "status": "delivered",
  "retryable": false,
  "statusReason": null,
  "#k": "17979728991110710",
  "#ce": {"comment":1},
  "#ask": {"delivered":1}
}
```

### Price

One charged event, `comment`, and it lands only on a comment that was actually delivered. A reply is charged on that same event, at that same price.

| Apify plan | Per comment | Per 1,000 comments |
|---|---|---|
| Free plan | $0.0024 | $2.40 |
| Bronze | $0.0014 | $1.40 |
| Silver | $0.0013 | $1.30 |
| Gold and above | $0.001 | **$1.00 per 1,000** |

Platform usage is included in that price — there is no separate compute bill on top, and no start fee. Set `maxTotalChargeUsd` on the run and it is a hard ceiling: the run stops cleanly under it and the last row says what is left.

### Honest rows: what "not charged" actually means

Every run closes its own books. A post that returned less than it was asked for leaves an uncharged row saying which of these happened, and none of them is billed:

- **`post_not_found`** — this version reports such links as `try_again` (below), because a deleted post, a mistyped code, a post on a private account and a read that did not go through look the same from here. Scripts that filter on the old word should use `try_again`.
- **`no_comments`** — Instagram's own answer about the post, and the row says which half it is. The post is real and has none, because comments are turned off for it or nobody has commented yet; or it served no further page after the comments you were given — including when the page it pointed us at had nothing behind it, which is how Instagram ends a list. Or an earlier run on your account had already read that post to the end and its published comment count has not moved since, so this run did not read the pages below the first — the row names the day that read happened and the count it is unchanged from. That is Instagram's answer on this run, not a failure, and new comments would show on a later run.
- **`skipped.page_cap`** — one of this actor's own bounds stopped the walk: it reads at most 400 comment pages for a single post, so one post cannot spend a whole run. Instagram had more to give — that bound is ours, the row says so, and it tells you how to carry on from where the run stopped.
- **`skipped.all_repeat`** — this actor's other bound: the pages it read held only comments you already have, so the walk stopped rather than keep reading pages that add nothing. How far it reads past comments you already have grows with the number of comments you ask for per post — at least three pages, more for a bigger ask — so a higher per-post limit reaches further down the list. That bound is ours too, and the row names the setting that collects them again.
- **`stopped_at_limit`** — one of your own limits, the run clock, or the Apify free plan's daily allowance (below) stopped it first. The row names which — for the daily allowance, ONE row counts every comment it held and says when it resets (UTC).
- **`try_again`** — the read did not go through, or Instagram did not return the post on this run. Neither says anything final about your post, and a re-run is the fix — for a post that did not come back, check the code in the link first. If comments for that post had already been delivered before it went dark, the row opens with how many: those were charged, as each of their rows says, and the re-run is for the rest.
- **`source_refused`** — Instagram refused to answer for that link at all, and it refuses it the same way every time, so a re-run cannot change it. Open the link in a browser; if the post is there, say so on the Issues tab.
- **`unsupported_shape`** — Instagram answered in a shape this actor does not read yet. That is on us, not on your post — please tell us which post on the Issues tab and support for it will be added.
- **`limit_reached`** — this run reached its limit before it could collect more. Usually that is the limit for one run, and a new run on the same links continues, skipping comments you already have; rarely this actor has paused collection until the start of next month. The row says which.

The last row of every run is a receipt: delivered, how many were replies, asked for, what was skipped, what stopped it, the charged-event totals, and `retryable: true` when a re-run can collect the rest. A time limit ends the collecting, never the delivering — comments already in hand are always written out.

### How often this data changes

Comments keep arriving on a post for days after it goes up, and the busiest hours are the first few. So the useful pattern here is not one run — it is the same links again tomorrow. Re-running would otherwise re-buy the comments you already have, so it does not: every run remembers what it delivered to your account, in the key-value store `ig-comments-account` in your own Apify account, and a later run skips those. Not delivered again, not charged, and the slot goes to the next new comment instead, so you still get up to the number you asked for. Holding the top of a post's comment list does not stop a re-run there: it reads on past the comments you already have to the ones below them — how far grows with **Max comments per post**, at least three pages and more for a bigger ask — and only the new comments it delivers are charged. The memory is a key-value store in your own Apify account, so a run started with a scoped API token in restricted-access mode needs key-value store Read, Write and Create permission (Write is what lets the run remember what it delivered; without it every run pays again) or Actor runs set to Full access; without it the run says so on its status line and on an uncharged row, and cannot skip the comments you already have. A post your account has already read to the end is not walked again either: the run reads its first page — the page that says whether anything has been added — and where that page holds only comments you already have and the post's own published comment count has not moved since, it stops there rather than reading the pages below it, and the row says so. The moment that count rises, the run goes and collects what is new. That makes a daily watch on the same posts cost only what is new, as long as the token the watch runs on can open that store. Switch **Include comments you already have** on to get them back anyway, marked `repeat: true` and still uncharged, and use **Skip comments in this dataset** to carry the same memory across accounts.

A plain cadence to schedule: daily for a post in its first week, weekly after that. Most comments land in a post's first days, so a weekly run on an older post still catches what has trickled in since, and pays only for that.

### Replies, and when a thread is not opened

With **Include replies** on (the default), a comment that has a thread gets that thread collected and delivered right under it, at the same price. A very long run of one-reply threads costs more to collect than the comments are priced at, so the run bounds that: when it happens, the remaining threads are left unopened, those comments carry `repliesCollected: false`, and the run line and the receipt both say so rather than leaving you to notice. Top-level comments are never affected.

### Reliability

There is nothing to log into and nothing to configure. When a read cannot be answered, the run says so on an uncharged row and finishes successfully — a failed read is never billed and never dressed up as a post with no comments.

### Related actors

- One account's own posts, to feed this one links: **Instagram Profile Posts Scraper** — https://apify.com/steadyfetch/instagram-profile-posts
- The spoken words inside a reel: **Instagram Reel Transcript Scraper** — https://apify.com/steadyfetch/instagram-reel-transcript-scraper

### Limits and good manners

**Your settings are never refused for being too big.** This actor delivers up to 5,000 comments per post (`resultsLimit`) and runs for up to 1 hour (`maxRunSeconds`, minimum 30 seconds). Ask for more than that — or for a shorter clock — and the run still starts: it continues at the nearest limit and leaves one uncharged row saying what you asked for and what bound it. Nothing is refused before it starts.

On the Apify free plan this actor makes at most 30 pages of comments per account in any 24 hours (a reply thread is one page), and accounts on that plan share one daily allowance on this actor. Past either, the run keeps everything it already delivered, ships one uncharged row saying when it resets (UTC) and what Bronze, Apify's first paid plan, costs per 1,000 comments, and charges nothing past it. That row is marked `retryable: true` and carries `retryAfter` (the reset time), so a script walking a list of posts can wait for it instead of re-running. Paid Apify plans carry no daily allowance.

Only public posts are returned. Instagram serves comments in its own ranked order rather than strictly newest-first, and a very deep ask on a post will eventually run out — the run says so instead of padding. This actor may stop working if Instagram changes how the data is served; if it does, failed items are never charged.

### Support

Something off, or a column you need that is not here? Open an issue on the Issues tab — we usually reply within a couple of hours.

If it earned its keep, a rating helps other buyers find it, and saving the actor keeps it one click away.

# Changelog

This Actor's version history is a separate document: https://apify.com/steadyfetch/instagram-comments-scraper/changelog.md

# Actor input Schema

## `postUrls` (type: `array`):

Instagram post, reel or IGTV links, or the bare code at the end of one, e.g. \["https://www.instagram.com/p/DcOX3hWFiey/", "DewSznfCrzE"]; "\_demo" is the sample switch, not a link. Never charged: a dead link, a post with comments turned off, or a comment this account already has, so a re-run on the same posts pays only for comments you do not have yet. A profile or hashtag link gets an uncharged row naming the actor for it. Untouched, Start returns built-in sample rows at no result fee.

## `resultsLimit` (type: `integer`):

How many comments to deliver for EACH post link (default 100, at most 5,000) — three links at 200 collect up to 600 in total. Replies count against this limit like any other comment. A post with fewer comments delivers fewer, and nothing is charged for the rest — its uncharged row says so. This actor delivers at most 5,000 comments per post: ask for more and the run continues at 5,000, with one uncharged row saying so, rather than being refused.

## `includeReplies` (type: `boolean`):

ON (default): the replies under a comment are collected and delivered right after it, with `isReply: true` and `parentCommentId` set — at the SAME price as a top-level comment, and counted against the same limit. OFF: only top-level comments. A comment whose thread was not opened carries `repliesCollected: false`.

## `maxRunSeconds` (type: `integer`):

How long the run may collect, in seconds (default 600): it stops cleanly before then and reports what is left, instead of being killed by a timeout. A time limit ends the collecting, never the delivering: comments already in hand are always written out. The clock runs between 30 seconds and 1 hour: set it outside that and the run continues at the nearest one, with one uncharged row saying so, rather than being refused.

## `includeSeen` (type: `boolean`):

OFF (default): a comment already delivered to your account is skipped — no row, no charge — and the next new comment takes its place, so you still get up to the number you asked for. ON: those comments are delivered again anyway, marked `repeat: true`, and still not charged. Comments older than 90 days in your account's memory count as new again either way.

## `resumeFromDatasetId` (type: `string`):

The dataset ID of a previous run of this actor. Comments already in it are treated as comments you already have and are skipped, so a follow-up run collects only what is new even from a different Apify account. Selecting it here is what grants this run permission to read it.

## Actor input object example

```json
{
  "postUrls": [],
  "resultsLimit": 100,
  "includeReplies": true,
  "maxRunSeconds": 600,
  "includeSeen": false
}
```

# Actor output Schema

## `comments` (type: `string`):

One row per comment delivered: text, author, user id, whether the author's account is private, likes, reply count, timestamp and the post it belongs to. On a GIF comment the row also carries the GIF: a link to it, its width and height in pixels, and its title. A reply is a row like any other, marked `isReply: true` with `parentCommentId` set, and it is charged at the SAME price as a top-level comment — there is no second event for replies and no charge for starting a run. Every row carries `charged`, `chargedEvents` (which events that row cost, by name: `{"comment": 1}` on a billed comment, `{}` on everything else) and `status`, so the invoice reconciles from the dataset itself: only rows with charged = true were billed. Comments your account already had are skipped before they take a slot and are never charged again — that check reads a key-value store in your own account, so a run on a scoped API token without key-value store permission cannot make it and says so on its status line and on an uncharged row; with "Include comments you already have" they come back marked `repeat: true`, still uncharged.

## `misses` (type: `string`):

One uncharged row for every post that returned less than it was asked for, saying which of these happened: `post_not_found` (this version reports such links as `try_again`; scripts that filter on the old word should use `try_again`), `no_comments` (Instagram's own answer — the post is real and has none, or it served no further page), `skipped.page_cap` (this actor reads at most 400 comment pages for one post — that bound is ours, and the row says how to carry on), `skipped.all_repeat` (the pages it read held only comments you already have, so the walk stopped — how far it reads past them grows with the comments you ask for per post, and that bound is ours too), `stopped_at_limit` (one of your own limits, the run clock, or the Apify free plan's daily allowance stopped it first), `try_again` (the read failed and a re-run is the fix), `source_refused` (Instagram refused to answer for that link at all, the same way every time — a re-run cannot change it), `unsupported_shape` (Instagram answered in a shape this actor does not read yet — on us, please tell us which post on the Issues tab), `limit_reached` (this run reached its limit — the row says whether a new run continues now or this actor has paused collection until next month). None of these is charged. A row held by the Apify free plan's daily allowance also carries two fields a script can read. `retryAfter`: When the Apify free plan's daily allowance lets this account run again (UTC). `reason`: Why this item was held: free_plan_allowance = the Apify free plan's daily allowance was spent.

## `summary` (type: `string`):

Comments delivered, how many of them were replies, what was asked for, comments your account already had, what stopped the run, the charged-event totals, `retryable: true` when a re-run can collect the rest, and — when this run's token could not open the repeat memory — `repeatCheck: "unavailable_scope"`. When the Apify free plan's daily allowance held part of the run, it also carries the held row's `retryAfter` and `reason`.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "postUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyfetch/instagram-comments-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "postUrls": [] }

# Run the Actor and wait for it to finish
run = client.actor("steadyfetch/instagram-comments-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "postUrls": []
}' |
apify call steadyfetch/instagram-comments-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadyfetch/instagram-comments-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/599Fdg1hn6Pga2rEG/builds/Zsr0cJKyPoVnmIQj5/openapi.json
