Threads Posts Scraper avatar

Threads Posts Scraper

Pricing

from $4.70 / 1,000 post returneds

Go to Apify Store
Threads Posts Scraper

Threads Posts Scraper

Scrape posts from any public Threads profile: full text, like/reply/repost/quote counts, media, and timestamps.

Pricing

from $4.70 / 1,000 post returneds

Rating

0.0

(0)

Developer

EndSpec

EndSpec

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Threads Posts Scraper: Every Post From Any Public Threads Profile

Scrape posts from any public Threads profile — full post text, engagement counts, media, and timestamps — with no login, no cookies, and no browser.


Simple Input-Output Example

Input:

{
"usernames": ["natgeo"],
"maxPostsPerProfile": 50
}

Output (one row per post):

{
"postId": "3942534943066968922",
"code": "Da2sqRsElNa",
"url": "https://www.threads.com/@natgeo/post/Da2sqRsElNa",
"username": "natgeo",
"fullName": "National Geographic",
"userId": "63269174602",
"isVerified": true,
"profilePicture": "https://scontent.cdninstagram.com/v/natgeo-avatar.jpg",
"text": "Find someone who's as excited for World Snake Day as this little garter snake.",
"likes": 1093,
"replies": 92,
"reposts": 45,
"quotes": 7,
"media": {
"type": "image",
"imageUrl": "https://scontent.cdninstagram.com/v/garter-snake.jpg",
"width": 1080,
"height": 1350,
"isCarousel": false,
"carouselCount": 0,
"altText": "A garter snake coiled on a mossy log"
},
"isQuote": false,
"quotedPostCode": null,
"createdAt": "2026-07-16T16:00:11+03:00",
"createdAtUnix": 1784206811
}

Important Notes:

  • Results come back in seconds — no login, no cookies, no browser automation
  • Pay per result — $0.00499 per post returned. Failed and empty lookups are never charged
  • Public profiles only, and profile feeds only — this Actor does not search Threads by keyword or hashtag
  • Posts are returned newest first; maxPostsPerProfile controls how deep each profile goes (1–500)
  • All data comes from public sources only
  • Contact: contact@endspec.net

Full Actor Documentation

Threads Posts Scraper

Get every public post from any Threads account, as clean structured data. Post text, like/reply/repost/quote counts, media details, author info, and exact publish timestamps — for one profile or a whole list of them.


Overview

The Threads Posts Scraper is an Apify Actor that scrapes posts from public profiles on Threads. Give it a list of Threads usernames or profile URLs and it walks each profile's feed newest-first, returning one clean row per post with the full text, all four engagement counters, media metadata, and timestamps.

It runs against a data service, not a browser — so there is no login step, no cookie handling, no proxy tuning, and no CAPTCHA to solve. You send usernames, you get rows.

What this Actor does not do: it does not search Threads by keyword, hashtag, or topic, and it does not read private profiles. It is a profile-feed scraper, deliberately and only. If you need keyword search across Threads, this is not the right tool.

What You Can Do

  • Scrape a Threads profile's full post feed: newest first, up to 500 posts per profile per run
  • Capture complete post text: the full body of each post, not a truncated preview
  • Measure engagement: likes, replies, reposts, and quotes for every post
  • Collect media metadata: media type (image / video / carousel), image URL, dimensions, carousel item count, and accessibility alt text
  • Identify quote posts: know whether a post quotes another, and which post it quotes
  • Track multiple accounts in one run: pass a list of usernames and get all their posts into a single dataset
  • Feed downstream systems: export to JSON, CSV, or Excel from the Apify dataset, or pull it via the Apify API into your own pipeline

Data Availability

All data returned by this Actor is publicly available information. It only returns posts that account owners have published publicly on Threads — the same content any logged-out visitor can see on a public profile page. Private profiles cannot be retrieved. This Actor simply makes public post data easier to collect programmatically and at scale.


Actor Input Parameters

The Actor accepts the following input parameters:

usernames • Type: array of strings • Required: Yes (in practice — the run fails with no valid entry) • Default: prefilled with ["natgeo"] in the Apify console • Description: Threads usernames or profile URLs to scrape. Each entry may be a bare handle, an @handle, or a full profile URL. e.g., ["natgeo", "@zuck", "https://www.threads.com/@mosseri"]

maxPostsPerProfile • Type: integer • Required: Optional • Default: 50 • Minimum: 1 — Maximum: 500 • Description: Maximum number of posts to return for each profile in usernames. Posts are fetched newest first. e.g., 100

Important Notes:

  • At least one valid username is required — the input schema marks nothing as strictly required, but a run with no usable entry in usernames fails immediately with No valid Threads username or profile URL provided. and returns no rows and no charges.
  • Three input formats, all equivalentnatgeo, @natgeo, and https://www.threads.com/@natgeo all resolve to the same profile. threads.net URLs work too. Mix formats freely in one list.
  • Invalid entries are skipped, not fatal — an entry that isn't a recognizable username or profile URL is dropped with a warning in the run log; the remaining profiles still run.
  • Duplicates are removed — the same profile listed twice (in any format) is scraped once.
  • maxPostsPerProfile is per profile, not per run — three profiles at maxPostsPerProfile: 100 can return up to 300 posts, and you are billed for every post returned.
  • The cap is a ceiling, not a target — a profile with 12 posts returns 12 posts, whatever the cap says.

Input Examples

Example 1: Single Profile, Default Depth

{
"usernames": ["natgeo"]
}

When to use: The simplest run. Returns up to 50 of @natgeo's most recent posts (the default maxPostsPerProfile).

Example 2: Multiple Profiles, Mixed Input Formats

{
"usernames": [
"natgeo",
"@zuck",
"https://www.threads.com/@mosseri"
],
"maxPostsPerProfile": 100
}

When to use: Competitor or portfolio tracking across several accounts. All three formats are accepted and normalized to the same thing — you do not need to clean your list first.

Example 3: Shallow Probe of a Large List

{
"usernames": ["natgeo", "zuck", "mosseri", "threads", "instagram"],
"maxPostsPerProfile": 10
}

When to use: Checking recent activity across many accounts cheaply, or test-driving the Actor before a big run. Low maxPostsPerProfile keeps both runtime and cost small.

Example 4: Deep Archive of One Profile

{
"usernames": ["natgeo"],
"maxPostsPerProfile": 500
}

When to use: Backfilling a profile's post history with full engagement data. 500 is the hard maximum per profile per run.

Example 5: No Usable Input (Will Fail)

{
"usernames": []
}

Result: The run fails with No valid Threads username or profile URL provided. No rows are produced and nothing is charged. The same happens if every entry in the list is malformed.


Output Structure

The Actor pushes one row per post to the Apify dataset. Results can be downloaded as JSON, CSV, Excel, XML, or fetched via the Apify API. The dataset's Overview table view shows username, text, likes, replies, reposts, quotes, createdAt, and url; the full field set is always present in the raw data and in exports.

Successful Post Row Format

{
"postId": "string",
"code": "string",
"url": "string",
"username": "string",
"fullName": "string",
"userId": "string",
"isVerified": true,
"profilePicture": "string",
"text": "string",
"likes": 0,
"replies": 0,
"reposts": 0,
"quotes": 0,
"media": null,
"isQuote": false,
"quotedPostCode": null,
"createdAt": "string",
"createdAtUnix": 0
}

Field Descriptions:

postId • Type: string • Description: Threads' numeric identifier for the post. Stable — use it as your deduplication key.

code • Type: string • Description: The post's short code, as it appears at the end of its Threads URL.

url • Type: string • Description: Direct link to the post on Threads.

username • Type: string • Description: Handle of the post's author (without @). Falls back to the username you supplied if the post carries no author record.

fullName • Type: string | null • Description: The author's display name.

userId • Type: string | null • Description: Threads' numeric identifier for the author.

isVerified • Type: boolean | null • Description: Whether the author's account carries a verification badge.

profilePicture • Type: string | null • Description: URL of the author's avatar image.

text • Type: string | null • Description: The full text of the post. null or empty for media-only posts.

likes • Type: number | null • Description: Like count at the time of scraping.

replies • Type: number | null • Description: Reply count at the time of scraping.

reposts • Type: number | null • Description: Repost count at the time of scraping.

quotes • Type: number | null • Description: Quote count at the time of scraping.

media • Type: object | null • Description: Media attached to the post, or null for text-only posts. When present, contains:

  • type — one of "image", "video", or "carousel"
  • imageUrl — direct URL to the post's image (for video and carousel posts, the cover image)
  • width / height — pixel dimensions of that image
  • isCarousel — boolean; true for multi-item posts
  • carouselCount — number of items in the carousel (0 when not a carousel)
  • altText — the accessibility caption, when the author or platform supplied one

isQuote • Type: boolean • Description: true when this post quotes another post.

quotedPostCode • Type: string | null • Description: The short code of the quoted post, when isQuote is true.

createdAt • Type: string | null • Description: Publish time as an ISO 8601 timestamp.

createdAtUnix • Type: number | null • Description: Publish time as a Unix timestamp, for easy sorting and filtering.

Error and Empty-Result Row Format

Rows that are not posts carry a status field. Successful post rows have no status field — testing for the presence of status is the reliable way to separate posts from problems:

{
"status": "error",
"username": "string",
"error": "string"
}

status • Type: string • Description: Always "error" on these rows.

username • Type: string • Description: The normalized username the row refers to, so you can tell which profile in your list had the problem.

error • Type: string • Description: One of exactly four messages (see the examples below). Every one of them ends with "You were not charged." — because you were not.


Output Examples

Example 1: Image Post With Full Engagement Data

{
"postId": "3942534943066968922",
"code": "Da2sqRsElNa",
"url": "https://www.threads.com/@natgeo/post/Da2sqRsElNa",
"username": "natgeo",
"fullName": "National Geographic",
"userId": "63269174602",
"isVerified": true,
"profilePicture": "https://scontent.cdninstagram.com/v/natgeo-avatar.jpg",
"text": "Find someone who's as excited for World Snake Day as this little garter snake.",
"likes": 1093,
"replies": 92,
"reposts": 45,
"quotes": 7,
"media": {
"type": "image",
"imageUrl": "https://scontent.cdninstagram.com/v/garter-snake.jpg",
"width": 1080,
"height": 1350,
"isCarousel": false,
"carouselCount": 0,
"altText": "A garter snake coiled on a mossy log"
},
"isQuote": false,
"quotedPostCode": null,
"createdAt": "2026-07-16T16:00:11+03:00",
"createdAtUnix": 1784206811
}

Example 2: Text-Only Post That Quotes Another Post

Text-only posts return media: null. Nothing is missing — there is simply no media on the post.

{
"postId": "3942534943066968923",
"code": "Da2sqRsElNb",
"url": "https://www.threads.com/@natgeo/post/Da2sqRsElNb",
"username": "natgeo",
"fullName": "National Geographic",
"userId": "63269174602",
"isVerified": true,
"profilePicture": "https://scontent.cdninstagram.com/v/natgeo-avatar.jpg",
"text": "This is the part everyone gets wrong about snake season.",
"likes": 141,
"replies": 12,
"reposts": 3,
"quotes": 1,
"media": null,
"isQuote": true,
"quotedPostCode": "DadRSwPoMbB",
"createdAt": "2026-07-15T12:13:31+03:00",
"createdAtUnix": 1784106811
}

Example 3: Carousel Post With Partial Author Data

Not every field is populated on every post. Missing values come back as null rather than being dropped, so your schema stays stable.

{
"postId": "3942534943066968931",
"code": "Da2sqRsElNc",
"url": "https://www.threads.com/@smallshop/post/Da2sqRsElNc",
"username": "smallshop",
"fullName": null,
"userId": "71422900135",
"isVerified": false,
"profilePicture": null,
"text": "Three new colorways, live now.",
"likes": 18,
"replies": 2,
"reposts": 0,
"quotes": 0,
"media": {
"type": "carousel",
"imageUrl": "https://scontent.cdninstagram.com/v/colorway-1.jpg",
"width": 1080,
"height": 1080,
"isCarousel": true,
"carouselCount": 3,
"altText": null
},
"isQuote": false,
"quotedPostCode": null,
"createdAt": "2026-07-14T09:02:44+03:00",
"createdAtUnix": 1783929764
}

Example 4: Profile Found, But No Posts (Empty Result)

The profile resolved, but its feed returned nothing — an account that has never posted, or one whose posts are not publicly visible.

{
"status": "error",
"username": "emptyaccount",
"error": "No posts were found for this profile. You were not charged."
}

Example 5: Profile Could Not Be Retrieved

Emitted when the profile cannot be resolved — a typo, a handle that no longer exists, a private account, or an intermittent lookup failure. Retrying is worthwhile: see the FAQ.

{
"status": "error",
"username": "no_such_handle_here",
"error": "This profile could not be retrieved. Check that the username is correct and the profile is public, then retry. You were not charged."
}

Example 6: Temporarily Rate Limited

{
"status": "error",
"username": "natgeo",
"error": "Our servers are busy right now — please retry shortly. You were not charged."
}

Example 7: Unexpected Failure

{
"status": "error",
"username": "natgeo",
"error": "Something went wrong on our side. You were not charged."
}

Those four error strings are the complete set. The Actor never emits anything else in that field — no raw technical errors, no stack traces, no partial internal detail. If you see one of these, it means what it says, and no charge was made for it.


Use Cases

For Social Media Marketers and Brand Teams

Competitive and brand monitoring: Track what competitor accounts publish on Threads, how often, and what actually lands. The four engagement counters on every post let you rank content by real performance rather than gut feel.

Example Workflow:

  1. List your competitors' Threads handles in usernames
  2. Set maxPostsPerProfile to cover your reporting window (e.g. 100)
  3. Schedule the Actor to run daily or weekly in the Apify console
  4. Export the dataset to CSV or push it to your BI tool
  5. Sort by likes and replies to see which themes outperform, and by createdAt to see cadence

For Analysts and Researchers

Engagement benchmarking: Build a baseline for what "good" looks like in your category. Because every post carries likes, replies, reposts, quotes, and createdAtUnix, you can compute engagement rates, posting frequency, and time-of-day effects across any set of accounts.

Example Workflow:

  1. Assemble a cohort of accounts in the same category
  2. Run the Actor at maxPostsPerProfile: 500 to backfill history
  3. Group rows by username and bucket createdAtUnix by week
  4. Compare median engagement per post across the cohort
  5. Re-run periodically and append to track drift over time

For Businesses and Content Teams

Content research and repurposing: Find which of your own Threads posts performed best, and mine adjacent accounts for format ideas — carousels vs. single images vs. text-only is directly visible in the media field.

Example Workflow:

  1. Scrape your own profile plus three accounts you admire
  2. Split rows by media.type (image, video, carousel, or null for text-only)
  3. Compare average engagement per media type
  4. Rebuild your content calendar around the formats that actually win

For Developers and Data Teams

Pipeline integration: Pull Threads content into a warehouse, a social listening product, or an LLM pipeline. The output shape is fixed and every field is documented, so it maps cleanly onto a table.

Example Workflow:

  1. Call the Actor via the Apify API or a scheduled task
  2. Use the presence of status to route error rows away from post rows
  3. Deduplicate against your store using postId
  4. Load text into your embedding or classification pipeline
  5. Keep likes / replies / reposts / quotes as time-series snapshots per scrape

Best Practices

Input

  • Don't pre-clean your list — bare handles, @handles, and profile URLs all work, and duplicates are removed for you
  • Start small — do a maxPostsPerProfile: 10 test run against your list before launching a 500-per-profile job
  • Remember the cap multipliesmaxPostsPerProfile applies to every profile, so a 20-profile list at 500 can return up to 10,000 billable posts
  • Batch sensibly — profiles are processed one after another, so very large lists mean longer runs; split them across several runs or scheduled tasks

Error Handling

  • Test for status, not for success — post rows have no status field; anything carrying status: "error" is a problem row, not a post
  • Retry a "profile could not be retrieved" — profile lookups can fail intermittently even for accounts that exist. The Actor already retries internally before giving up, but a fresh run on that single handle is often enough to get it
  • Treat "our servers are busy" as transient — wait a little and re-run just the affected profiles
  • If every profile in a run comes back busy or failed, contact us — a whole run failing is not normal and is worth a support ticket
  • Log the Apify run ID — include it when you contact support

Data Usage

  • Deduplicate on postId — it is stable across runs; code and url are fine for humans but postId is the key
  • Snapshot, don't assume — engagement counts are true at the moment of scraping and keep moving; store them with a scrape timestamp if you care about trends
  • Sort with createdAtUnix — it is the least ambiguous of the two timestamps for programmatic use
  • Respect privacy and platform terms — use public post data for legitimate business purposes only

Cost, Performance, and Limits

Billing

  • You pay per post returned — the Actor charges a single event, post-returned, once for each post row it delivers. $0.00499 per post.
  • Errors and empty results are free — every status: "error" row is unbilled, which is why each of those messages ends in "You were not charged."
  • Failed profiles don't poison the run — in a multi-profile run you pay for the posts you actually received, and nothing for the profiles that failed
  • Partial results are billed as delivered — if a profile returns some posts and then the feed stops early, you keep and pay for the posts you got, and no error row is added for that profile

Performance

  • Fast, and no browser — this is a data-service Actor, not a headless-browser scraper. Runs finish in seconds to a couple of minutes depending on how many posts you ask for.
  • Posts arrive in pages — the feed is paginated behind the scenes and rows stream into the dataset as pages come in
  • Retries are built in — profile resolution and page fetches are retried automatically with backoff before any error row is emitted, so ordinary transient hiccups never reach you

Limits

  • Profile feeds only. There is no keyword, hashtag, or topic search in this Actor. It cannot answer "who mentioned my brand on Threads" — only "what did this account post".
  • Public profiles only. Private accounts return This profile could not be retrieved...
  • 500 posts per profile per run is the hard maximum; the default is 50. For deeper history, that ceiling is the ceiling.
  • Newest first, no date filter. You control depth with maxPostsPerProfile; you cannot request a specific date range. Filter on createdAt / createdAtUnix after the fact.
  • Media metadata, not media files. You get the media type, cover image URL, dimensions, and alt text — not a downloadable video file.
  • Engagement counts are point-in-time, captured when the run happens.

Data Sources and Legality

All data returned by this Actor is publicly available information published on Threads. It returns only posts that account owners have chosen to make public — the same content visible to any member of the public viewing that profile. No private profiles, no logged-in-only content, no personal data behind an access control.

Your responsibility: You are responsible for complying with all applicable laws and regulations, and with the terms of service of any platform whose public data you collect. That includes GDPR, CCPA, and any local rules that apply to you when personal data is involved. Scraping publicly available data is generally lawful in many jurisdictions, but how you store, process, and use it is on you.

Practical guidance:

  • Use collected post data for legitimate purposes such as research, analytics, and competitive intelligence
  • Do not republish scraped content in ways that infringe the author's rights
  • Honor removal requests from individuals whose public posts you have stored
  • If in doubt about a specific use case, take legal advice — this section is information, not a legal guarantee

FAQ

Q: Can I search Threads for a keyword, hashtag, or topic? A: No. This Actor scrapes profile feeds only. You give it accounts; it gives you those accounts' posts. It cannot search Threads for mentions of a term.

Q: Can it scrape private profiles? A: No. Only publicly visible profiles can be retrieved. A private account returns This profile could not be retrieved. Check that the username is correct and the profile is public, then retry. You were not charged.

Q: I got "This profile could not be retrieved" for an account I know exists. Why? A: Profile lookups are occasionally flaky — the same handle can fail on one attempt and succeed on the next. The Actor already retries several times with backoff before giving up, so this is uncommon, but it happens. First check the spelling and that the profile is public; if both are fine, just run that handle again.

Q: Am I charged for profiles that fail or return nothing? A: No. You are charged only for posts actually delivered to your dataset. Every error row says so explicitly.

Q: What input formats does usernames accept? A: Bare handles (natgeo), @handles (@natgeo), and full profile URLs (https://www.threads.com/@natgeo, and threads.net URLs too). You can mix all three in one list.

Q: How many posts can I get from one profile? A: Up to 500 per profile per run, set via maxPostsPerProfile. The default is 50. Live checks have paginated well past 80 posts on a single profile without failures.

Q: How fresh is the data? A: It reflects the profile's live public feed at the moment of the run — in our checks, the newest post in a feed was less than a day old. Engagement counts are equally a snapshot of that moment.

Q: Does it download images or videos? A: It returns media metadata — type, cover image URL, dimensions, carousel item count, and alt text. It does not return downloadable video files.

Q: How do I tell post rows apart from error rows in my code? A: Post rows have no status field. Error rows always have status: "error". Check for the presence of status.

Q: Can I scrape several profiles in one run? A: Yes — that is what usernames is for. Note that maxPostsPerProfile applies to each profile separately, and you are billed per post returned across all of them.

Q: What formats can I export? A: Anything the Apify dataset supports — JSON, CSV, Excel, XML — via the console or the Apify API.

Q: Can I run this on a schedule? A: Yes. Use Apify's scheduling to run it daily or weekly and append to your own store, deduplicating on postId.


Contact & Support

Questions? Need help? We're here for you.

For questions, technical support, feature requests, or anything else about the Threads Posts Scraper, reach out:

Email: contact@endspec.net

Response Time: We respond within 24 hours on business days.

What to include in your message:

  • A description of the question or issue
  • Your Apify run ID, if applicable
  • The input you used
  • Any error message or status value you received

You can also report issues through the Issues tab on this Actor's Apify page. We're committed to helping you get the most out of this Actor — whether you're integrating it into a pipeline, troubleshooting a run, or figuring out whether it fits your use case.



Last updated: July 2026