π§΅ Threads User Posts Scraper
Pricing
from $4.99 / 1,000 results
π§΅ Threads User Posts Scraper
π§΅ Threads User Posts Scraper pulls public posts from Meta Threads profilesβtext, media URLs, timestamps, hashtags, mentions, links, and engagement (likes, replies, reposts). π₯ Export CSV/JSON. π Ideal for social listening, competitor intel, and research. π
Pricing
from $4.99 / 1,000 results
Rating
0.0
(0)
Developer
API Empire
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
0
Monthly active users
3 days ago
Last modified
Categories
Share
Threads Posts Scraper β Extract Posts, Media and Mentions as JSON
Threads Posts Scraper pulls every recent post from any public Threads profile β captions, engagement counts, media attachments, mentions, tagged users, and timestamps β and streams each one to your Apify dataset as typed, normalized JSON. Give it a profile URL or a plain @username, and it returns posts, embedded media objects, and mentioned/tagged accounts with no HTML, no login, and no manual token wrangling. It connects to Threads directly and automatically falls back to Apify's Datacenter and Residential proxies only if the site limits the run. After reading this, you'll know exactly what every input field does, what every output field contains, and how to wire the results into a script, a spreadsheet, or an AI agent.
What is Threads Posts Scraper?
Threads Posts Scraper is an Apify Actor that turns a list of public Threads profiles into a structured feed of that profile's posts, plus everything embedded inside each post β media, mentions, and tagged accounts. It talks to Threads' own GraphQL endpoint the same way a browser tab does; no Threads or Instagram account, password, or session cookie is required anywhere in its input schema. Rows appear in the Output tab live, as they're scraped, rather than only after the run finishes.
- π§΅ Scrape recent posts from any public Threads profile, one call per profile or a bulk list
- πΌοΈ Capture every media attachment on a post β single images, single videos, or full carousels
- π·οΈ Capture mentions and tagged users embedded in the post body and media
- π€ Export results as JSON, CSV, Excel, or HTML directly from the Apify dataset
- π« No proxy setup and no browser required β the Actor manages its own backup-proxy escalation
π What data does Threads Posts Scraper collect?
Every dataset row is a post record that carries four distinct kinds of data pulled out of Threads' response in one pass: the post itself, an author snapshot, its media attachments, and the accounts referenced inside it.
| Data Type | Key Fields | JSON Field Names |
|---|---|---|
| Posts | caption text, edit flag, engagement counts, timestamps, permalink | caption, caption_is_edited, like_count, direct_reply_count, quote_count, repost_count, taken_at, taken_at_iso, url |
| Author snapshot | posting account's username and avatar | username, profile_pic_url |
| Media attachments | images and videos attached to the post, including audio tracks on video posts | medias, audio |
| Mentions & tagged users | accounts referenced with @ in the caption, and accounts tagged on the media itself | mentions, tagged_users |
Need more Threads data?
If you also need to identify which of a profile's followers are worth prioritizing β ranked by follower count, verified badge, or public/private status β API Empire also publishes Threads Followers Scraper By Influencer Filter, which pulls and tiers a profile's follower list separately from this Actor's post-level data.
Why not build this yourself?
Threads doesn't expose a stable, documented way for a third party to pull an arbitrary public profile's post history β Meta's GraphQL endpoint that actually serves this data isn't meant for outside consumers, and using it directly means solving several moving problems yourself:
-
Rotating operation identifiers. Every GraphQL call needs a
doc_idand a set of Relay "provided variables" keys that Meta regenerates whenever it ships a frontend build. This Actor extracts them fresh from the live JavaScript bundle on every profile fetch (helper.py) instead of hardcoding values that go stale. -
CSRF and session-token handshakes. The endpoint validates the
x-csrftokenheader against acsrftokencookie set by an initial HTML request, plus anx-fb-lsdtoken scraped out of the page. Get any one of these wrong and the endpoint returns empty results with a200status β no error, just nothing. -
Anti-bot escalation. A direct connection gets rate-limited under real load. This Actor already implements a three-tier fallback β direct, then Apify Datacenter proxy, then Apify Residential proxy with up to three rotated session attempts β plus exponential-backoff retries on transport errors.
-
Pagination and de-duplication. Threads' feed connection is a Relay cursor-paginated edge list β you have to track
end_cursorandhas_next_pagefrompage_infocorrectly across requests, and guard against the same post ID appearing twice across pages (a real occurrence when a profile posts while you're mid-paginate). This Actor tracks seen post IDs per profile within a run and only pushes and charges for a post once.
Maintaining all of that yourself means re-reverse-engineering Meta's bundle every time the frontend changes, re-implementing cursor pagination correctly, and building your own proxy fallback logic before you've collected a single post. This Actor already does all of it and keeps the output schema stable on your end regardless of what changes underneath.
Why do developers and teams scrape Threads?
For AI engineers and agent builders
Threads posts are a live, high-signal text corpus for RAG indexing and LLM fine-tuning β captions, engagement counts, and mention graphs in one typed JSON row per post, with no scraping logic to write or maintain on the model-facing side. An agent tool can call this Actor with a watchlist of usernames, pull caption and like_count straight into a vector store or a ranking function, and re-run on a schedule without writing a single HTML parser or handling Threads' anti-bot behavior itself. Because mentions and tagged_users are already resolved to structured objects with username and id fields rather than bare text, an agent building a social graph can traverse account relationships directly from the dataset instead of running a second extraction pass over free text.
For marketers and brand teams
Tracking how a competitor or a partner brand's Threads presence performs over time means pulling their recent posts on a cadence and comparing like_count, direct_reply_count, quote_count, and repost_count run over run to spot which content formats or topics are landing. Feeding a list of ten competitor usernames into urls with a modest maxPostsPerProfile produces a comparable engagement snapshot across all of them in a single run, without opening ten browser tabs or manually copying numbers into a spreadsheet β the dataset export drops straight into a reporting tool or a scheduled digest.
For researchers and analysts
Threads posts are public discourse data useful for studying discussion patterns, sentiment, or topic spread over time β as long as the study stays within what the profile owner already made public on their own profile. Setting maxPostsPerProfile toward its 5000-post ceiling on a sample of profiles produces a dataset with normalized timestamps (taken_at_iso is timezone-explicit UTC, so records from different collection runs or machines line up correctly) suitable for time-series or cohort analysis without a separate cleaning pass to reconcile time zones first.
For developers building data products
Any product that needs a "recent activity" feed for Threads accounts β a social-listening dashboard, a creator-analytics tool, a competitive-intelligence aggregator β can call this Actor on a schedule per tracked username and treat the resulting dataset as the feed source, rather than standing up and maintaining its own scraping infrastructure. The output schema doesn't change when Meta reshuffles its frontend, since the Actor re-derives its GraphQL operation identifiers from the live JavaScript bundle on every run instead of depending on hardcoded values that go stale β so a product built on top of it doesn't need a code change every time Threads ships a routine update.
How to scrape Threads posts (step by step)
- Open Threads Posts Scraper on the Apify Store, or find it in your Apify Console under your saved Actors.
- Provide
urlsβ the one required input: a list of Threads profile links (threads.com/threads.net) or plain usernames, one per line. - Set
maxPostsPerProfileif you want something other than the default of 10 recent posts per profile (accepts 1β5000). - Click Start. The Actor connects to Threads directly first, then automatically escalates to Apify Datacenter and, if needed, Apify Residential proxy if the site limits the run.
- Watch rows land live in the Output tab, or download the finished dataset as JSON, CSV, Excel, or HTML once the run completes.
What to do when Threads changes its structure
The Actor doesn't hardcode Meta's internal GraphQL operation ID or its Relay "provided variables" keys β it re-extracts both from the live JavaScript bundle on every profile fetch. That's the same mechanism that would otherwise break first when Meta ships a frontend update, so the Actor is built to track it automatically rather than needing a manual patch for every cosmetic Threads change. The output field names and types stay the same on your end either way. No specific turnaround time is promised for deeper breaking changes to Threads' access model.
β¬οΈ Input
| Parameter | Required | Type | Default | Constraints |
|---|---|---|---|---|
urls | Yes | array of strings | β | List editor (stringList); one Threads profile URL or plain username per line |
maxPostsPerProfile | No | integer | 10 | Minimum 1, maximum 5000 |
proxyConfiguration | No | object | {"useApifyProxy": false} | Proxy editor; only affects the automatic Datacenter/Residential backup steps |
urls β Paste Threads profile links (threads.com or threads.net) or plain usernames, one per line. Both https://www.threads.com/@zuck and a bare zuck are accepted; the Actor strips the @, lowercases the value, and trims trailing slashes before using it.
maxPostsPerProfile β Caps how many recent posts to save per profile. Lower values finish faster; higher values pull deeper history. The Actor paginates Threads' GraphQL connection with a cursor and stops as soon as it hits this cap or runs out of posts, whichever comes first.
proxyConfiguration β Optional and off by default ({"useApifyProxy": false} is the schema's prefilled value). The Actor always tries a direct connection to Threads first regardless of this setting; it only reaches for a proxy if Threads limits or rejects the direct request, at which point it uses your Apify account's Datacenter and Residential proxy groups automatically, escalating tiers only as needed. Turn this on only if you need to force a specific proxy group, country, or sticky session for that backup step specifically β it has no effect on the initial direct attempt. Leaving it off is the recommended default for most runs.
Example input
{"urls": ["https://www.threads.com/@zuck","natgeo","@nasa"],"maxPostsPerProfile": 50,"proxyConfiguration": {"useApifyProxy": false}}
Common pitfall: each entry in urls is treated as a single username or link β don't put several usernames on one line separated by commas. The Actor doesn't split on commas, so "zuck, natgeo" as one list item is parsed as a single (invalid) username rather than two profiles, and that entry will fail to resolve any posts. Use the stringList editor's one-item-per-line behavior as intended: add a new line for every profile, however many you have.
β¬οΈ Output
Every pushed row is typed JSON with a stable field set β no HTML, no CSS selectors, nothing to parse. Download results as JSON, CSV, Excel, HTML, or an RSS/XML feed directly from the Apify dataset, or pull them programmatically through the Apify API or apify_client dataset iterator shown further down this page. The Actor's default dataset view in the Apify Console shows 18 of these columns; two more fields (caption_is_edited and like_and_view_counts_disabled) are written to every row but aren't in that default table β switch the dataset view to see them, or just read the raw JSON/API output, where all 20 fields are always present.
Scraped post
{"profile_pic_url": "https://scontent.cdninstagram.com/v/t51.2885-19/example_150x150.jpg","username": "zuck","id": "3220231147841639674_314216","pk": "3220231147841639674","code": "Cywjyrdv9T6","caption": "Good conversation about science, AI, and mixed reality.","caption_is_edited": false,"like_count": 3756,"direct_reply_count": 402,"quote_count": 20,"repost_count": 131,"like_and_view_counts_disabled": false,"audio": null,"medias": [{"type": "image","url": "https://scontent.cdninstagram.com/v/t51.29350-15/example_1.jpg","width": 1440,"height": 1800},{"type": "image","url": "https://scontent.cdninstagram.com/v/t51.29350-15/example_2.jpg","width": 1440,"height": 1800}],"tagged_users": null,"mentions": [{"username": "hubermanlab","profile_pic_url": "https://scontent.cdninstagram.com/v/t51.2885-19/huberman_150x150.jpg","id": "1234567890"}],"taken_at": "2026-05-14 09:22:11","taken_at_iso": "2026-05-14T16:22:11+00:00","url": "https://www.threads.net/@zuck/post/Cywjyrdv9T6","scrapedAt": "2026-07-25T10:03:44.812931+00:00"}
Field notes, read directly from the Actor's row-building code:
mediashas two shapes depending on the post. For a carousel (multi-image/video) post, it's the rawcarousel_mediaarray exactly as Threads' API returns it. For a single-image or single-video post, it's normalized to a list of{type, url, width, height}objects (images) or{type, url, video_type}objects (videos). Code that consumesmediasgenerically should handle both shapes.tagged_usersholds accounts tagged directly on the media by the author (Threads'usertags) β it is not a hashtag field. Hashtags aren't broken out as a separate structured field.taken_atis the post timestamp rendered in the Actor container's local timezone, kept for backward compatibility with earlier output.taken_at_isois the same timestamp as an unambiguous, timezone-explicit UTC ISO 8601 string β usetaken_at_isofor anything that needs to be comparable across machines or time zones.scrapedAtis the UTC timestamp of when the Actor collected that specific row, not when the post was published.idis Threads' compound post identifier (<pk>_<container id>);pkis the numeric post ID alone.
Scraped post β video attachment
Video posts and carousel (multi-item) posts carry a different medias and audio shape than the single-image example above. Here's a video post with an attached original-audio track:
{"profile_pic_url": "https://scontent.cdninstagram.com/v/t51.2885-19/example_150x150.jpg","username": "natgeo","id": "3221459098234561234_67890","pk": "3221459098234561234","code": "Cy1AbCdEfGh","caption": "A rare glimpse of the aurora from the research station.","caption_is_edited": false,"like_count": 91420,"direct_reply_count": 1830,"quote_count": 205,"repost_count": 3312,"like_and_view_counts_disabled": false,"audio": {"audio_src": "https://scontent.cdninstagram.com/audio/example.mp4","original_media_has_audio": true},"medias": [{"type": "video","url": "https://scontent.cdninstagram.com/v/t50.2886-16/example.mp4","video_type": 1}],"tagged_users": [{"username": "arctic_research_team","profile_pic_url": "https://scontent.cdninstagram.com/v/t51.2885-19/arctic_150x150.jpg","id": "9876543210"}],"mentions": null,"taken_at": "2026-06-02 21:47:03","taken_at_iso": "2026-06-03T01:47:03+00:00","url": "https://www.threads.net/@natgeo/post/Cy1AbCdEfGh","scrapedAt": "2026-07-25T10:04:12.220117+00:00"}
For a carousel post β multiple images or videos in one post β medias is instead the raw carousel_media array exactly as Threads' own API returns it, rather than the normalized {type, url, width, height} shape shown in the single-image example above. If you're parsing medias generically across a mixed dataset, branch on whether the source post was a carousel rather than assuming one fixed shape for every row.
How does Threads Posts Scraper compare to other Threads scrapers?
| Feature | Threads Posts Scraper | Generic alternative |
|---|---|---|
| Media field structure | Typed objects with type, url, dimensions, or video type β carousels return the upstream object list, single-media posts are normalized | One competing listing on the Apify Store (checked 2026-07-25) shows medias as a flat array of raw image URL strings, with no type or dimension data |
| Tagged-media accounts | Separate tagged_users field, distinct from mentions | The same listing's sample output has no equivalent field β only a mentions-style array |
| Mention data shape | Structured objects (username, id, profile_pic_url) per mention | The same listing's sample output returns mentions as plain "@username" strings |
| Timestamp format | Both container-local (taken_at) and absolute UTC ISO 8601 (taken_at_iso) | The same listing's sample output includes only a single local-format timestamp field |
| Proxy handling | Direct connection first, automatic Apify Datacenter β Residential escalation built in, no configuration required | Not documented on the same listing |
If you're building an AI agent or RAG pipeline, the media and mention field-shape rows are the decision-maker β parsing a flat array of raw URLs or "@username" strings back into structured entities inside an agent loop is a reliability failure mode, not a feature.
How many posts can you scrape with Threads Posts Scraper?
maxPostsPerProfile caps collection at up to 5000 posts per profile, with a default of 10 if left unset. The Actor paginates Threads' GraphQL connection with a cursor, requesting pages of up to 25 posts at a time over a direct connection (up to 15 per page once a backup proxy tier is active), and stops as soon as it either reaches your cap or Threads reports no further pages for that profile. If a profile has fewer posts than your cap, you get however many actually exist β there's no padding or retry to try to reach the requested number. There is no hard cap in the Actor itself beyond the 5000 schema maximum; that ceiling is set by the input schema, not observed as a platform limit.
The cap applies per profile, not per run β if urls contains multiple profiles, each one is collected up to maxPostsPerProfile independently, and the Actor works through the list one profile at a time rather than in parallel. A run with a large profile list and a high maxPostsPerProfile naturally takes longer than a small list with a low cap, since every additional page of posts is its own GraphQL request; there's no documented benchmark for how long any specific combination takes, so size your run based on how much history you actually need rather than assuming a fixed throughput.
Integrate Threads Posts Scraper and automate your workflow
Threads Posts Scraper works with any language or tool that can send an HTTP request to the Apify API β the examples below use Python, but the same call works from Node.js, Go, Ruby, or a plain curl request.
REST API integration
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run_input = {"urls": ["https://www.threads.com/@zuck", "natgeo"],"maxPostsPerProfile": 50,}run = client.actor("<YOUR_USERNAME>/threads-user-posts-scraper").call(run_input=run_input)for post in client.dataset(run["defaultDatasetId"]).iterate_items():print(post["username"], post["like_count"], post["caption"])
Works in Python, Node.js, Go, Ruby, cURL.
Automation platforms (n8n, Make, LangChain)
Apify publishes an official Make app and an official n8n community node, so you can add this Actor as a step in either platform without hand-rolling the HTTP calls β configure the Actor ID and your urls list as the node's input, and the resulting dataset rows flow straight into the rest of your automation (a Slack digest of new posts, a spreadsheet row per post, a webhook to your own service). For LangChain or a custom agent framework, call the Actor through apify_client as shown above and pass the returned JSON rows straight into your chain or tool output β because every field is already typed and named consistently, there's no parsing step, no HTML stripping, and no schema negotiation needed between the Actor's output and whatever consumes it next.
Is it legal to scrape Threads?
Scraping publicly available Threads data is generally lawful in most jurisdictions, but how you store and use it afterward can still trigger data-protection obligations. Threads Posts Scraper only returns content a Threads profile owner has already made public on their own profile page β it does not log in, does not bypass a login wall, does not use anyone's password or session cookie, and cannot see private-account posts because it never authenticates in the first place. Because posts, usernames, avatars, and mentioned or tagged accounts are all personal data tied to identifiable individuals, GDPR (EU/UK) and CCPA (California) can still apply to how you subsequently store, process, retain, or reuse this output once it leaves the Actor β collecting only public data doesn't automatically exempt downstream processing from those regimes. Consult legal counsel before any commercial use case involving bulk personal data, especially one that combines this output with other identifying information.
β Frequently asked questions
Does Threads Posts Scraper work without a Threads account?
Yes. The input schema has no login, session cookie, or API key field at all β the Actor requests the public profile page the same way a signed-out visitor would, then extracts the app ID, CSRF token, user ID, and LSD token it needs straight out of that page's HTML and embedded JavaScript, and uses those to call Threads' own GraphQL endpoint. You don't provide any Threads or Instagram credentials, and there's nothing to log in with even if you wanted to β this is a logged-out scraper by design.
How often is the scraped data updated?
Every run fetches live data directly from Threads at the moment it executes β there's no cache, no pre-scraped snapshot, and no delay layer between the request and the response you get back. Run it again on demand, or set up an Apify schedule (hourly, daily, or any cron expression) to get the current state of a profile's posts each time it runs.
What happens if a profile has no posts, is private, or doesn't exist?
The Actor logs a warning and moves on to the next profile in your list without pushing β or charging for β any rows for that one. Internally, a profile it can't extract valid tokens for (a typo'd username, a private account, or a deleted one) triggers the same fallback path as a genuine rate limit: it retries the direct connection, then escalates through the Datacenter and then Residential proxy tiers (up to three rotated attempts) before giving up on that specific profile and logging an error. That means a bad username sitting in a bulk urls list can add noticeable time to the overall run even though it ultimately contributes zero dataset rows and zero charges.
Can I scrape private Threads profiles?
No. The Actor never authenticates, so it only ever sees what an anonymous, signed-out visitor sees. A private account's profile page doesn't expose its posts to that kind of unauthenticated request, so no rows are produced for it β the run simply moves on to the next profile in your list.
What happens if I call this Actor directly via the API with an unusual maxPostsPerProfile value?
The Apify Console's input editor enforces the schema's 1β5000 range before a run ever starts, but a direct API call bypasses that UI validation. Reading the Actor's own input-handling code: passing maxPostsPerProfile: 0 causes it to fall back to the default of 10 rather than collecting zero rows, since the code treats 0 as "not set." A negative value, on the other hand, does not fall back β it's used as-is, and since the collection loop's exit condition is never true for a negative target, that profile simply returns zero rows without a fallback. Stick to the documented 1β5000 range for predictable behavior regardless of how you call the Actor.
What does pricing look like, and is there a way to try it first?
Threads Posts Scraper runs on Apify's pay-per-event pricing, charged per post row saved to your dataset (the row_result event) β check the Pricing tab on the Actor's Apify Store listing for current terms, since pricing is set on the platform, not in this document.
Does Threads Posts Scraper work for AI agent workflows and LLM pipelines?
Yes β it's callable as a standard Apify Actor run from any agent framework via apify_client or a plain HTTP request, and every dataset row is already typed JSON with stable field names. There's no MCP server documented for this Actor; integrate it as an HTTP-callable tool the same way you would any other Apify Actor run.
How does Threads Posts Scraper handle Threads' anti-bot system?
It connects directly to threads.com first, using the same public profile page and GraphQL endpoint a signed-out browser tab would use. If Threads blocks or rate-limits that request, the Actor switches to an Apify Datacenter proxy and stays on that tier for the rest of the run unless it's blocked again, at which point it moves to an Apify Residential proxy and retries up to three times with rotated sessions per profile before giving up on that profile specifically. Every GraphQL request also carries a fresh doc_id and Relay "provided variables" keys extracted from Threads' current JavaScript bundle rather than hardcoded values, plus a CSRF token matched to a cookie set during the initial page load β the combination Threads' endpoint checks before returning real data instead of an empty response. Transport-level failures (timeouts, connection errors) get retried with exponential backoff, capped at 30 seconds between attempts. None of this requires you to configure anything β proxyConfiguration only overrides which proxy group the backup tiers use, it doesn't turn the escalation itself on or off.
How does Threads Posts Scraper compare to other Threads scrapers?
Based on the top-ranked competing listing on the Apify Store (checked 2026-07-25), the main observable differences are in output structure rather than input flexibility: that listing's sample output returns medias as flat image URL strings and mentions as plain "@username" strings, with no tagged_users-equivalent field at all and only one local-format timestamp per post. This Actor returns typed media objects with dimensions or video type, structured mention objects with resolved username and id fields, a separate tagged_users field for accounts tagged directly on the media, and both a container-local and an absolute UTC ISO 8601 timestamp per post β differences that matter most once you're piping the output into code rather than just reading it in a spreadsheet.
Does Threads Posts Scraper return data in a format LLMs can use directly?
Yes. Every row is typed, normalized JSON with stable field names and no HTML or markup to strip. Pass rows directly into an LLM's context window, index them into a vector store, or route them through an agent tool without a parsing step.
Can I use Threads Posts Scraper without managing proxies?
Yes. Leave proxyConfiguration unset or off β the Actor handles proxy escalation internally using your Apify account's own Datacenter and Residential proxy access whenever Threads limits a direct request. You only need to touch this input if you want to force a specific proxy group for that backup step.
What happens when Threads changes its structure or blocks the scraper?
The Actor is maintained, and it's specifically built to survive Meta's routine frontend churn by re-extracting its GraphQL operation ID and Relay variable keys from the live JavaScript bundle on every run rather than hardcoding them. The output schema β field names and types β stays the same on your end even as the underlying request logic adapts. No specific turnaround time is promised for a deeper breaking change to Threads' access model.
π Related Threads scrapers
Threads Posts Scraper covers post-level data β captions, engagement, media, mentions. It doesn't collect follower lists, so if your workflow also needs to know who follows a profile, API Empire publishes Threads Followers Scraper By Influencer Filter as a separate Actor: it pulls a profile's followers, then keeps and ranks only the influential ones by follower count, verified status, or public/private state, tiering each kept follower as micro, mid, macro, or mega. The two Actors are built to be run side by side β one for what an account posts, the other for who follows it β rather than as a single combined tool, so each keeps a simple, predictable input schema.
π¬ Your feedback
Found a bug, or need a field this Actor doesn't currently return? Open an issue from the Issues tab on this Actor's Apify Store page β API Empire actively maintains this scraper and reviews reports there.