Instagram Comments Scraper - Date Filter & New-Comment Monitor avatar

Instagram Comments Scraper - Date Filter & New-Comment Monitor

Pricing

from $0.30 / 1,000 per comment returneds

Go to Apify Store
Instagram Comments Scraper - Date Filter & New-Comment Monitor

Instagram Comments Scraper - Date Filter & New-Comment Monitor

From $0.30 per 1,000. Scrape top-level comments on any public Instagram post or reel: text, GIF, likes, timestamp, commenter username, ID and verified badge. No login or cookies. Comments come back newest-first, so a date filter or the built-in monitor pulls only new ones. Replies not included.

Pricing

from $0.30 / 1,000 per comment returneds

Rating

0.0

(0)

Developer

Scrapers Delight

Scrapers Delight

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

5 days ago

Last modified

Share

💬 Instagram Comments Scraper — date filter & new-comment monitor

Scrape the top-level comments on any public Instagram post or reel. No login, no cookies, no browser. From $0.30 per 1,000 comments — roughly an eighth of what the category leader lists.

Paste a post URL. Get every comment's text (or GIF), likes, exact timestamp, deep link, and the commenter's username, numeric account id, verified badge and profile URL — plus the hashtags, @mentions, links and any email address the commenter typed, pulled out of the text for you.

⚠️ Read this first: threaded replies are NOT included.

This Actor reads Instagram's login-free comment stream, and that stream carries top-level comments only. child_comment_count was null on 3,189 of 3,189 comments measured, and parent_comment_id on 0 of 3,189. Every row you get says so in its repliesIncluded column.

Measured against Instagram's own comment count (which does include replies), a fully-read thread here comes to 64%–83% of the total — the rest are replies. Four threads read to the very end on 2026-09-07:

PostTop-level comments returnedInstagram's own countCoverage
DZ5T2XPllXv39761664.5%
Dc1U6TYGwJE41556174.0%
Dcv8Q_Pk06P44459674.5%
DczLHKOGpqh77493482.9%

If you need replies, several other Actors in this category ship them and you should use one of those. We would rather lose the sale than put a capability in the title we could not prove.


🎯 What this one does that the other 91 don't

Instagram serves this comment stream in strict reverse-chronological order. That is not a claim, it is a measurement: 0 inversions of created_at across 3,189 comments on 18 posts, and 0 again across a 2,030-row full-thread run. Two things fall out of that fact, and neither is comfortable for a scraper reading Instagram's ranked comment endpoint:

1. A date filter that actually stops early — so you pay for the window, not the thread

Set Only comments newer than to 7 days, 48 hours or 2026-09-01, and the crawl stops the moment the stream crosses the boundary. It does not download the thread and filter afterwards.

Measured on the same 2,118-comment post, same day:

RunCallsRows deliveredWall time
Whole top-level thread1482,118106 s
commentsNewerThan: 2026-06-25213.3 s

Same post. 74× fewer calls, and you are billed for the rows in your window instead of all 2,118. On a 79,000-comment post asking for "yesterday", the difference is three orders of magnitude.

2. A monitor that is exact, not heuristic

Switch on Monitor mode, put the Actor on a schedule, and each run delivers only the comments posted since the previous run. The last-seen comment id lives in a named key-value store and paging stops exactly at it — no dedupe guessing, no re-billing comments you already have.

And when a burst is bigger than your per-run cap, it does not silently drop the overflow. The watch pointer is held back and the comments already shipped are remembered, so the next run pages straight through them and picks up the backlog underneath. Verified live: run 1 delivered 8 comments, run 2 skipped exactly those 8, read 2 pages and delivered 8 different ones — 0 overlap, 0 double billing, 0 lost comments.

3. An honest coverage ledger on every row

topLevelComplete, dateWindowComplete, truncationReason, postCommentsScanned and topLevelCoveragePct tell you exactly what fraction of the thread you got and what stopped the read. Nothing else in this category tells you that.

And "complete" here needs three witnesses, on two different IPs. Instagram's logged-out comment stream intermittently answers has_next_page: false in the middle of a thread — measured 1 short answer in 42 calls — and an Actor that believes it tells you it read a 2,118-comment thread to the end when it read 15.

Re-issuing the call is not enough: on 2026-09-09 that short answer came back twice in a row on the same session for a post this Actor had read 2,119 comments from ninety minutes earlier. The short answer belongs to the exit IP, so the repeat is not an independent witness. An end-of-stream answer is now confirmed once on the same session and then again from a fresh proxy IP on a newly minted session, and if that third opinion cannot be obtained the read is recorded as truncationReason: "end-not-independently-confirmed" rather than as complete. Every confirmation page's rows are discarded, so none of this can double-bill you. A supposedly complete read that lands under 20% of Instagram's own comment count is still downgraded to truncationReason: "instagram-short-stream". POST_STATUS carries endOfStreamConfirmed and instagramFalseEndsRetried so you can see it happen.

4. A work budget, not just a delivery cap

maxCommentsPerPost bounds what is delivered. A filter or a date window drops a comment after it has been read, so "the 10 comments mentioning discount" can quietly mean reading a 79,000-comment thread to find them — a long, expensive run behind a five-row invoice.

maxCommentsScannedPerPost bounds the reading. Left at 0 it is automatic: no budget at all when nothing in your input can drop a row (then every comment read is a comment delivered), and otherwise 100 comments read per row you asked to be delivered, floor 1,000, ceiling 20,000 — so the default maxCommentsPerPost: 50 plus any filter gives a 5,000-comment search per post. Set your own number to search deeper; 500000 is effectively no budget.

Measured A/B, same input, both builds started at the same moment (2026-09-09) — a textIncludes term matching nothing on the 2,118-comment thread, at maxCommentsPerPost: 10, so the automatic budget is its 1,000 floor:

Without the work budgetWith it
Comments read2,1191,009
Instagram calls14872
Wall time on the post112.6 s37.9 s
Rows delivered / billed0 / $00 / $0
Reported astopLevelComplete: truetruncationReason: "scan-budget"

Same answer, 2.7× less work — and, importantly, the run now tells you it stopped early instead of implying the thread had nothing.

The budget is proportional, so read that table with its cap in mind. It scales with the rows you asked for, which means it bites hardest on the runs that would waste the most: the 79,000-comment thread above is stopped 16× early at the default cap, while a thread smaller than your budget is still read in full. Re-measured on that same filter-matches-nothing input at the default maxCommentsPerPost: 50 — a 5,000-comment budget against a 2,118-comment thread — the budget never trips and the whole thread is read: 2,119 comments, 0 rows, $0 charged,

scanBudgetReached: false
(run DFYxmxcpJdiOKsghL, build 0.2.12). Lower maxCommentsPerPost, or set maxCommentsScannedPerPost yourself, if you want a search that stops sooner than that.

When it stops a post, the run says so and names the filter, in the log, in the status message and as truncationReason: "scan-budget" — because a short answer that reads like "that's all Instagram had" is a lie when it was our bound that stopped the read. Comments read are never billed; only comments delivered are. POST_STATUS carries commentsScanBudget and scanBudgetReached.

5. GIF comments are handled, not shipped as blank rows

GIF comments come back from Instagram with text: "". Measured 85 of 1,200 rows (7.1%), and every single one carried a live GIF URL. They are typed commentType: "gif" with gifUrl, gifProxiedUrl and gifId filled in, instead of landing in your CSV as empty comments.

6. Resumable pagination for very big threads

Switch on Resumable pagination and each post's cursor is stored, so the next run continues the backfill where the last one stopped instead of paying to re-read the head. Verified live: two consecutive 20-row runs on the same post, 0 overlapping rows.

7. It repairs itself when Instagram changes

This route rides a Meta persisted-query id, and Meta rotates those on deploys. Most scrapers built this way simply return zero rows one morning and exit successfully. This one detects the rejected id, re-mints its session, retries with the ids compiled into the build, and only then sweeps Instagram's own JavaScript bundles for the new id, caches it and carries on.

Verified by deliberately poisoning both stored ids on the live platform (2026-09-08): the Actor detected the explicit GraphQL rejection, re-minted, swept 253 of 455 bundles, recovered both ids in 48 s and delivered its rows. Both ids are then re-validated — page 1 and a paginated page 2 — before they are cached, so a half-recovered pair can never be stored. Four such runs at once put one sweep past its own budget, so the cheap repair now runs first: if the stored ids are stale but the ids built into this Actor still work, the route comes back in two calls instead of a 48-second sweep. If it cannot repair the route at all, the run FAILS loudly and says the problem is ours — it never reports a broken route as "this post has no comments".


📊 What every row contains

The comment

  • 💬 text — the comment body · non-empty on 1,113 of 1,200 measured rows (92.8%)
  • 🎬 commentType (text / gif / empty), gifUrl, gifProxiedUrl, gifId
  • ❤️ likeCount · 100% present
  • 🕐 createdAt (ISO 8601) + createdAtUnix · 100% present
  • 🔗 commentUrl — deep link straight to the comment
  • 🆔 commentId — Instagram's own stable comment id · 100% present; 0 duplicates within every measured run (3,189 / 2,030 / 1,200 rows)
  • 🚩 isCovered, restrictedStatus, hasTranslation
  • 🧵 parentCommentId, replyCount, repliesIncluded — always null / null / false on this route, kept as columns so the limitation is visible in the data, not only in the docs

The commenter

  • 👤 ownerUsername · 100% · and ownerProfileUrl
  • 🔢 ownerId (numeric account id) + ownerGraphId · 100%
  • ownerIsVerified · true on 78 of 2,030 rows (3.8%) in a full-thread run
  • 🖼️ ownerProfilePicUrl — signed CDN link, it expires; fetch it now, don't archive the URL
  • 👑 isPostOwner — matched on numeric id, so it works even without the post-context fetch

Pulled out of the comment text (read from what the commenter typed — not a lookup, not enrichment)

  • #️⃣ hashtags · 0.7% of rows · @ mentions · 3.4% · 🔗 urlsInText · 0.6% · 📧 emailsInText · 0.1% · wordCount, textLength

The post

  • postShortcode, postUrl, postMediaId, postMediaType (image / carousel / video — all three verified live), postOwnerId, postOwnerGraphId, postTopics
  • With Add post context on: postOwnerUsername, postCommentCountTotal (Instagram's own, incl. replies), postLikeCount, postCaption, postTakenAt, postProductType, topLevelCoveragePct100% filled across 8 of 8 posts measured. These are read from your post's own media object, located by its shortcode in the page. That matters: the post page also ships the account's other recent posts, each with its own caption and product_type, so a scraper that takes the first match on the page hands you a different post's caption. (We shipped that bug for one build and caught it on 2026-09-08: two different posts came back with the same 701-character caption. Every context field is now anchored, and the offline test suite asserts the anchoring against captured live bytes.)

The ledger

  • topLevelComplete, dateWindowComplete, truncationReason, postCommentsScanned, postCommentsReturned, commentIndex, isNewSinceLastRun, scrapedAt

A per-post ledger is also written to the run's POST_STATUS key-value record: pages read, comments scanned, the scan budget in force (commentsScanBudget) and whether it stopped that post (scanBudgetReached), rows returned, rows filtered out, duplicates suppressed, whether the thread was read to the end, what stopped it, and coverage against Instagram's own count.


⚡ Measured performance

All numbers below are from real runs, not estimates. The first block is on the Apify platform at 256 MB — what you actually get. The second is the recon measurement set through an Apify datacenter proxy on 2026-09-07.

On-platform runs (build 0.2.x, 2026-09-08 — 45 runs, including a full adversarial re-test)

RunRowsChargedDuration
3 posts x 25 comments, post context on7575 comments + 3 contexts = $0.02856.6 s – 71 s (median ~8 s)
Empty input {} (falls back to the documented sample)75$0.02857.0 s – 57 s
One 397-comment thread read to the very end, context on397$0.121118 s – 126 s, depending on the proxy exit
Date window, 21 of 29 comments outside it8$0.0024 — the 21 were not billed3.7 s
Charge cap set to $0.0126$0.0098, cap named in the status message15.0 s
Monitor seed / Monitor with nothing new4 / 0$0.0062 / $0.00503.4 s / 3.9 s
Stored query ids poisoned -> self-heal5$0.00155.7 s (built-in retry) / 77 s (full bundle sweep)

In every one of those runs, chargedEventCounts equalled the number of rows in the dataset, and neither Apify auto-event (apify-actor-start, apify-default-dataset-item) appeared on the bill.

The wall-time spread is entirely the proxy exit you draw, not the parser: every one of those runs recorded 100% HTTP 200, and the slow ones simply averaged 3.0 s per call instead of 0.4 s. The Actor bounds every request by the run's own clock, so a bad exit costs you time, never a timeout with an empty dataset.

Throughput and reliability (recon set, Apify datacenter proxy)

RunPostsRowsCallsHTTP 200Avg call
8 posts x 150 comments, context on, concurrency 481,20088100%689 ms
4 posts read to the very end, concurrency 442,030142100%1,195 ms
Deep single-post pagination12,118148100%710 ms
Sustained load, one minted session183,189228100%932 ms
  • 0 duplicate comment ids across every one of those runs.
  • 0 ordering inversions — the property the date filter and the monitor are built on.
  • ~1.08 KB per comment on the wire.
  • ~13-15 comments per call, whatever page size is requested (first = 12, 24, 50 and 100 all returned 14 edges on the same post — this Actor never assumes otherwise).
  • One minted session held 176 consecutive calls at 100%; the Actor mints once per run and only re-mints (on a fresh proxy IP) if a page starts failing.
  • Proxy: datacenter auto is the default — 228/228 at ~932 ms. Residential US also works (10/10, ~1,049 ms) but is slower and costs bytes for no gain. One transient proxy ECONNRESET in ~350 calls; it is retried on a new IP, not treated as a block.
  • Memory: 256 MB. HTTP only — no browser anywhere in this Actor.

Rough guide: a 400-comment thread ≈ 28 calls. Wall time depends on the proxy exit you draw — the same 28-call thread measured 21 s and 126 s on the same day (avg call 0.7 s vs 3.6 s, 100% HTTP 200 on both). A 2,000-comment thread ≈ 148 calls ≈ 106 s. A 79,000-comment thread is ~5,400 calls — use the date filter, the per-post cap, or Resumable pagination rather than doing that in one run.


💵 Pricing

Plain pay-per-event. No Actor-start charge, no per-dataset-item charge — you pay for comments, nothing else.

EventPriceWhen it fires
Per comment returned$0.0003 ($0.30 / 1,000)Each top-level comment delivered to your dataset
Per post context$0.002Once per post whose page is fetched for the owner handle, Instagram's own comment count and the coverage ledger. Only when the toggle is on, and never for a post that returned no comments
Per monitoring run$0.005Once per Monitor-mode run. Never in normal Scrape mode, and never when Instagram returned nothing usable
Per alert delivered$0.002Each new-comment alert whose delivery is proven: a webhook or Slack post that returned 2xx, or an email digest whose apify/send-mail run came back SUCCEEDED. Once per sink per comment; once per recipient for the email digest

What you are not charged for, by construction:

  • comments a filter removed — filters run before delivery
  • comments outside your date window — the date bound runs before delivery
  • comments the Actor read but did not deliver — searching is our cost, not yours; the maxCommentsScannedPerPost work budget bounds it and names itself when it stops a post
  • an alert whose delivery failed, was refused, or did not finish — webhooks need a 2xx and the email digest needs a SUCCEEDED send-mail run before a cent is billed
  • duplicate comment ids — de-duplication runs before billing
  • comments on a post Instagram reported as unavailable
  • a post-context fetch on a post that delivered no rows
  • a monitoring run where Instagram never answered — nothing was checked, so nothing is billed
  • anything at all past your maxTotalChargeUsd — the run reserves the money for each row before delivering it, and names the cap as the reason if a batch is trimmed

Worked examples

JobCost
1,000 comments, no post context$0.30
Yesterday's comments on a big launch post (~50 rows) + context$0.017
A whole 2,118-comment thread + context$0.64
A daily monitor on 5 posts finding ~40 new comments a day$0.017 / day

How that compares, read off the live Store on 2026-09-08

ActorListed per comment1,000 commentsMonthly users
This Actor$0.0003$0.30new
apify/instagram-comment-scraper (category leader, 4.52★)$0.0019 – $0.0023$1.90 – $2.304,664
apidojo/instagram-comments-scraper-api$0.0005 / item + $0.0075 per post queried$0.50 + post fees409
scrapesmith/instagram-comments-scraper$0.0005 / item + $0.00005 start$0.50338
datadoping/instagram-comments-and-replies-scraper$0.00155 / item$1.55261

That is 6× to 8× under the category leader's listed rate and 40% under the cheapest rate among the actors in this category that have real monthly traffic.

Not the cheapest number on the Store, and we are not going to pretend otherwise. A handful of near-dormant listings price a dataset item at $0.00001 — omissive_zen/instagram-comment-scraper (6 monthly users), scrapers-hub/instagram-comments-scraper (1), dead00/instagram-comment-bot (23). If a headline number is all you are shopping for, they are cheaper than this Actor. What we claim is the cheapest maintained per-comment rate in the working half of this category, with the date filter, the monitor and the coverage ledger on top.


🚀 Getting started

The simplest run:

{
"startUrls": [{ "url": "https://www.instagram.com/p/DZ5T2XPllXv/" }],
"maxCommentsPerPost": 50
}

Only comments from the last 48 hours, on three posts:

{
"startUrls": [
{ "url": "https://www.instagram.com/p/DZ5T2XPllXv/" },
{ "url": "https://www.instagram.com/reel/Dcv8Q_Pk06P/" },
{ "url": "https://www.instagram.com/p/DczLHKOGpqh/" }
],
"commentsNewerThan": "48 hours",
"maxCommentsPerPost": 0
}

Watch a launch post and get Slack alerts (put this on an hourly Apify schedule):

{
"startUrls": [{ "url": "https://www.instagram.com/p/DZ5T2XPllXv/" }],
"monitorMode": true,
"monitorSeedComments": 0,
"includePostContext": false,
"slackWebhookUrl": "https://hooks.slack.com/services/XXX/YYY/ZZZ"
}

Mine a giveaway thread for entrants who left a contact:

{
"startUrls": [{ "url": "https://www.instagram.com/p/DZ5T2XPllXv/" }],
"maxCommentsPerPost": 0,
"commentsNewerThan": "2026-09-01",
"commentsOlderThan": "2026-09-08",
"textIncludes": ["done", "entered", "count me in"],
"requireEmailInText": true
}

Back-fill a 79,000-comment thread over several cheap runs:

{
"startUrls": [{ "url": "https://www.instagram.com/p/DZ5T2XPllXv/" }],
"maxCommentsPerPost": 5000,
"resumeMode": true
}

Run it again with the same input and it continues where it stopped.


🧰 Who buys this

  • Social listening / brand monitoring — watch a campaign post and route new comments into Slack the minute they land.
  • Crisis and PR watch — a post going sideways generates comments faster than a human can read them; the monitor gives you a live feed, filtered to verified accounts if you want only the ones that matter.
  • Giveaway and competition compliance — pull exactly the comments posted inside the entry window, with commentsNewerThan + commentsOlderThan, and prove it with the coverage ledger.
  • Comment-to-lead workflows — filter to comments carrying an email or a link, or to comments matching your buying-intent keywords ("price", "shipping", "where can I buy").
  • Creator and influencer research — engagement-quality analysis on the commenter side: verified share, like distribution, GIF vs text, mention graphs.
  • Data teams — one row per comment, one stable key set, CSV / JSON / Excel export, or call it from the API and schedule it.

🔧 Every input, and what it actually does

Nothing in this list is decorative — each one is wired to real behaviour.

What to scrapestartUrls (post / reel / IGTV URLs), shortcodes (bare codes), maxCommentsPerPost (how many rows you get), maxCommentsScannedPerPost (how many comments the Actor will read looking for them — see §4 above), maxComments.

Date windowcommentsNewerThan, commentsOlderThan (ISO date, timestamp, or a span like "7 days"), dateBoundGracePages.

MonitormonitorMode, monitorStateKey (run several independent watches on one post), monitorSeedComments, alertOnNewComment, webhookUrl, slackWebhookUrl, emailRecipients.

About the email digest. It is sent by calling apify/send-mail, so it needs an Apify plan that permits running public Actors (standard plans do; some developer-only plans do not — ours is one, which is how the failure path below was verified live). apify/send-mail itself accepts only your own account address on the free plan, and up to 20 recipients on a paid one. If the send is refused, fails, or does not finish, the run says which and bills nothingalert-delivered is charged only against a SUCCEEDED send-mail run. Webhook and Slack alerts have no such dependency; they need only a 2xx from your endpoint.

Filters (all run before delivery, so nothing filtered out is billed) — commentTypes, minLikeCount, onlyVerifiedCommenters, textIncludes, textExcludes, requireEmailInText, requireLinkInText, excludePostOwnerComments, onlyCommenters, excludeCommenters.

OutputincludePostContext, includeRawComment, deduplicateComments.

Resume & recoveryresumeMode, resetResumeState, docIdRecovery.

Performance & proxymaxPagesPerPost, maxSecondsPerPost, maxConcurrency, requestTimeoutSecs, maxRequestRetries, maxBufferedCommentsPerPost, proxyConfiguration.


❓ FAQ

Does it get threaded replies? No. The login-free route this Actor rides carries top-level comments only — child_comment_count was null on 3,189 of 3,189 comments measured. Every row says repliesIncluded: false, and the coverage ledger tells you what share of the thread that leaves you with (64%–83% on the four threads measured). Other Actors in this category do ship replies; use one of those if you need them.

Do I need an Instagram login, cookies or a session id? No. Nothing to configure, nothing to keep alive, no account to put at risk.

Does it work on reels? Yes. Image posts, carousels and video/reel posts were all verified live and are reported as postMediaType image / carousel / video.

Can I give it a profile URL and get every post's comments? No, and that is deliberate. One site, one job: this Actor reads the comments of the posts you give it. Pair it with a post scraper if you need to discover the URLs first.

Are the comments in order? Newest first, strictly — 0 inversions of the timestamp across 3,189 comments on 18 posts and 2,030 more on a full-thread run. commentIndex gives you the position in that stream.

Can I get the top / most-liked comments first? Not on this route — Instagram serves the logged-out stream chronologically, and this Actor will not pretend otherwise by re-sorting a partial read and calling it "top comments". Read the thread (or a date window) and sort by likeCount yourself; that is the only honest way to do it here.

How do I get only new comments each day? Monitor mode plus an Apify schedule. Set monitorSeedComments: 0 if you want the first run to just record a baseline and deliver nothing.

What happens if more new comments arrive than my per-run cap? Nothing is lost and nothing is billed twice. The watch pointer is held back and the comments already delivered are remembered, so the next run pages through them without re-delivering and picks up the backlog underneath. The run tells you it happened.

What if the post is deleted or private? Instagram answers with an empty media object. The run reports that post as unavailable, does not bill you for it, and — if every post you gave came back that way — checks the Actor's own known-live sample in the same run so it can tell you whether the problem is your posts or our scraper. It will never blame Instagram for something we did.

Will a zero-row run still charge me? No. In Scrape mode nothing is billed. In Monitor mode the flat run fee is charged only when Instagram actually answered, because that fee buys a check — a run that checked nothing pays nothing.

Why is the coverage percentage never 100%? Because it is measured against Instagram's own comment count, and that count includes the threaded replies this route cannot return. A fully-read thread lands at 64%–83%. We show you the real number rather than rounding it up.

Does it work at scale? Pagination inside one post is sequential (each page needs the previous page's cursor), so scale comes from running posts in parallel — up to 8 at a time. 8 posts and 1,200 comments took 27 seconds.

How current is the data? It is read live from Instagram at run time. There is no cache and no stored copy.

What is POST_STATUS? A record in the run's key-value store holding the authoritative per-post ledger — pages read, comments scanned, rows returned, what stopped the read and the coverage percentage. It is the one place that is always correct, including on very large threads whose rows had to be streamed out before the crawl finished.


Instagram's robots.txt. Its notice and its wildcard rule, quoted verbatim (fetched 2026-09-07):

# Notice: Collection of data on Instagram through automated means is
# prohibited unless you have express written permission from Instagram
# and may only be conducted for the limited purpose contained in said
# permission.
# All authorized user-agents listed on this page must comply with Meta's
# Automated Data Collection Terms available at:
# https://www.facebook.com/legal/automated_data_collection_terms
User-agent: *
Disallow: /

We are showing you that in full so you can make your own decision with the facts in front of you. Instagram's Terms of Use and Meta's Automated Data Collection Terms apply to what you do with this Actor, and complying with them is your responsibility, not ours.

This is personal data. Every row is an identifiable person: a username, a numeric account id, a profile photo URL, and whatever they typed. Under GDPR, CCPA and comparable laws you are the controller of anything you collect here. That means you need a lawful basis, you should keep only what you need for as long as you need it, and you must honour deletion requests. The emailsInText field makes this sharper, not softer — an email address a person typed into a public comment is still their personal data.

Only public posts. This Actor reads what a logged-out visitor can see. It does not log in, does not use anyone's session, does not touch private accounts, and cannot see anything behind a follow request.

Profile picture URLs expire. They are signed CDN links. Fetch the image if you need it; do not store the URL and expect it to work next month.

Nothing here is legal advice. If your use case is sensitive — profiling, marketing to individuals, anything involving minors — talk to your own counsel first.


🐛 Something wrong?

Open an issue on the Actor page with the run id. Every run writes a POST_STATUS record and a status message that names exactly what stopped it, which usually answers the question on its own.