# Meta Threads Scraper - Posts, Reply Trees, Bio Contacts (`constructive_calm/threads-scraper`) Actor

Scrape Threads posts, profiles, hashtags & search with FULL recursive reply trees + bio email/phone extraction + monitoring mode. Beats incumbents on reply depth & outreach contact extraction.

- **URL**: https://apify.com/constructive\_calm/threads-scraper.md
- **Developed by:** [Omar Eldeeb](https://apify.com/constructive_calm) (community)
- **Categories:** Social media, SEO tools, Lead generation
- **Stats:** 110 total users, 20 monthly users, 100.0% runs succeeded, 2 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Meta Threads Scraper — Posts, Profiles, Discussion Trees, Bio Contacts

Scrape Meta Threads (threads.com) public posts, profiles, search results, hashtag feeds, and full discussion trees — with optional bio email/phone extraction and Linktree resolution. Designed for social-listening agencies, growth marketers, lead-gen, and brand-monitoring teams.

**No login. No cookies. No GraphQL doc\_id maintenance.** Pure SSR HTML extraction via Meta's public crawler-friendly endpoint.

***

### What this Actor does

- 📥 **Profiles** — scrape any active public Threads profile (followers, bio, bio links, profile pic, verification, post count, public views)
- 📰 **Posts by user** — fetch the most recent posts of any user with full engagement (likes, replies, reposts, quotes, media URLs, timestamps)
- 🌳 **Discussion trees** — for any post URL, return the source post plus all public quote-reposts and discussion posts that reference it
- 🔍 **Search** — pull the top results for any keyword
- \#️⃣ **Hashtag feed** — pull top posts for any tag
- 📡 **Monitor** — re-run any user list and emit only new posts since the last run (KV-backed watermark)
- 📞 **Premium: bio contact extraction** — parse emails + phones from biography text + bio links, optionally resolve Linktree / Beacons / Stan / Bio.link aggregator pages into full contact lists
- 🔁 **Concurrent + retry-aware** — runs N usernames/URLs in parallel with timeouts and graceful per-target error handling

***

### Why use this Actor

- **No anti-bot war.** No Cloudflare, no Akamai, no JavaScript challenge. Most existing Threads scrapers chase Meta's GraphQL `doc_id` rotations every few weeks — this Actor doesn't, so it doesn't break.
- **Bio contact extraction** as a first-class feature. Other Threads actors give you raw text; this one parses emails, phones, and resolves Linktree-style aggregators for cold outreach lead-gen.
- **Discussion trees in one fetch.** Get the source post + everyone publicly quoting/responding to it — perfect for influence tracking and competitive monitoring.
- **Monitor mode is real incremental.** Most "scheduled" actors burn compute re-scraping the same 500 posts; this one stores a per-user watermark so you only pay for *new* posts.

#### Buyer fit

| Persona | Use case |
|---|---|
| Social-listening agency | Track brand mentions, competitor activity, sentiment across Threads |
| Growth / SDR team | Pull bio contacts for cold outreach (creators, founders, brand owners) |
| Brand-monitoring lead | Daily monitor mode: alert on negative posts about your brand |
| ASO / mobile growth | Track creator+app mentions in hashtag feeds |
| Researcher / journalist | Discovery via search; archive a public post's discussion tree |

***

### How to use

1. Pick a **mode** (most users start with `posts_by_user`)
2. Enter usernames, post URLs, search query, or hashtag depending on the mode
3. (Optional) Enable **extractBioContacts** for outreach-grade contact data (premium event)
4. Run — first **50 chargeable events are free** every run

You can schedule the actor in monitor mode for daily delta detection (your watermark persists between runs in the actor's Key-Value Store).

***

### Input examples

#### 1) Pull recent posts from a list of users (most common)

```json
{
  "mode": "posts_by_user",
  "usernames": ["zuck", "mosseri", "instagram"],
  "maxPostsPerUser": 25
}
```

#### 2) Profile + bio contact extraction for outreach

```json
{
  "mode": "profile",
  "usernames": ["someinfluencer", "anotherbrand", "thirdcreator"],
  "extractBioContacts": true,
  "resolveLinktree": true
}
```

#### 3) Discussion tree for a single post

```json
{
  "mode": "post_detail",
  "postUrls": ["https://www.threads.com/@zuck/post/DXt5n9UlLl-"],
  "expandReplies": true,
  "maxReplyDepth": 3,
  "maxRepliesPerNode": 20
}
```

#### 4) Search keyword feed

```json
{
  "mode": "search",
  "searchQuery": "vibe coding"
}
```

#### 5) Hashtag feed

```json
{
  "mode": "hashtag",
  "hashtag": "ai"
}
```

#### 6) Monitor: emit only new posts since last run

```json
{
  "mode": "monitor",
  "usernames": ["yourcompetitor", "anothercompetitor"],
  "maxPostsPerUser": 50
}
```

***

### Output

The dataset is a mix of `recordType: "profile"` and `recordType: "post"` (or `"reply"`) rows.

#### Profile row

```json
{
  "recordType": "profile",
  "username": "zuck",
  "userId": "63055343223",
  "fullName": "Mark Zuckerberg",
  "biography": "Mostly superintelligence and MMA takes",
  "followerCount": 5464936,
  "isVerified": true,
  "profilePicUrl": "https://instagram.fatz1-1.fna.fbcdn.net/...",
  "bioLinks": [{"url": "https://meta.com/", "title": "Meta", "linkType": "external"}],
  "extractedEmails": ["press@meta.com"],
  "extractedPhones": [],
  "publicViews": null,
  "fetchedAt": "2026-05-08T12:00:00.000Z",
  "sourceUrl": "https://www.threads.com/@zuck"
}
```

#### Post row

```json
{
  "recordType": "post",
  "postId": "3886015488044874110",
  "code": "DXt5n9UlLl-",
  "url": "https://www.threads.com/@zuck/post/DXt5n9UlLl-",
  "username": "zuck",
  "userId": "63055343223",
  "fullName": "Mark Zuckerberg",
  "text": "As part of our philanthropic work to help cure all diseases...",
  "takenAt": "2026-04-25T08:46:07.000Z",
  "likeCount": 1443,
  "replyCount": 350,
  "repostCount": 111,
  "quoteCount": 9,
  "mediaUrls": [],
  "mediaType": "text",
  "parentPostId": null,
  "depth": 0,
  "fetchedAt": "2026-05-08T12:00:00.000Z",
  "sourceUrl": "https://www.threads.com/@zuck"
}
```

#### Field reference

| Field | Type | Notes |
|---|---|---|
| `recordType` | string | `profile` / `post` / `reply` |
| `username` | string | Without `@` |
| `userId` (`pk`) | string | Stable Meta numeric id |
| `followerCount` | number | Profile only |
| `bioLinks` | array | URL, title, link\_type |
| `extractedEmails` / `extractedPhones` | string\[] | Only when `extractBioContacts: true` |
| `text` | string | Caption text (post) |
| `takenAt` | ISO datetime | Converted from unix |
| `likeCount` / `replyCount` / `repostCount` / `quoteCount` | number | Engagement |
| `mediaUrls` | string\[] | Image / carousel / video best-resolution URL(s) |
| `mediaType` | enum | `text` / `image` / `carousel` / `video` |
| `parentPostId` | string | null | Set on `reply` rows |
| `depth` | number | 0 for root posts; >0 for replies in a discussion tree |

***

### Pricing — pay-per-event

You only pay for what you get. **First 50 chargeable events per run are free.**

| Event | Price | Fires when |
|---|---|---|
| `profile-fetched` | $0.005 | One profile row emitted |
| `post-fetched` | $0.0035 | One post row emitted |
| `reply-fetched` | $0.0015 | One reply / discussion-tree row emitted |
| `bio-contact-resolved` | $0.020 | Bio extraction + Linktree resolution ran (premium) |
| `monitor-delta-detected` | $0.010 | New post discovered in monitor mode |

Apify auto-charges `apify-actor-start` per platform billing on top of these.

#### Typical run cost

| Scenario | Volume | Approx cost |
|---|---|---|
| 1 profile + 25 posts | 1 + 25 events | $0.005 + (25 × $0.0035) = **~$0.09** |
| 10 profiles + 25 posts each | 10 + 250 | $0.05 + $0.875 = **~$0.93** |
| 50 search results | 50 | **~$0.18** |
| 1 post + ~30 discussion posts | 1 + 30 | $0.0035 + (30 × $0.0015) = **~$0.05** |
| Daily monitor of 100 brands (5 new posts/day on average) | 500 deltas | **~$5.00 / day** |
| 100 outreach profiles w/ contact extraction | 100 + 100 | $0.50 + $2.00 = **~$2.50** |

Compare against ZoomInfo + Bombora ($3,000+/month), Apollo Pro ($99+/seat), or building this in-house.

***

### Tips & gotchas

- **Some profiles return a "Threads · Log in" gate.** Meta privacy-gates a fraction (~15%) of accounts from public crawling, even with social-bot UA. The actor logs a clear warning when this happens and skips the row. Most active accounts work fine.
- **Posts per profile are limited to ~7-25 per fetch** in v1 (whatever Threads renders in the SSR HTML). Pagination beyond that requires a logged-in session and is out of scope.
- **Discussion tree, not reply tree.** A post-detail fetch returns the source post + ~15-30 quote-reposts, parody/copycat posts, and discussion posts that reference it. The full nested reply chain (clickable inside the Threads app) lives behind a logged-in XHR endpoint and is not surfaced.
- **Bio contact extraction is best-effort.** Emails parsed are valid-format, lowercased, deduped. Phone numbers are loosely matched on digit count + structure. Linktree-style aggregator pages are fetched only when `resolveLinktree: true`.
- **Concurrency.** Default 4 parallel fetches. Bump to 8-10 if you provide an Apify residential proxy. Tested at 27 fetches/min sustained without proxy with no rate-limiting.
- **Monitor mode** stores per-user watermarks under `monitor_watermarks_v1` in your run's Key-Value Store. Delete this key to reset.

***

### FAQ

**Do I need to log in or provide cookies?**
No. The actor uses Meta's crawler-friendly SSR endpoint (the same path Facebook / Twitter / Slack use for link previews).

**Will Meta block my actor?**
We've sustained 27+ fetches/min without rate-limiting in scale tests. Apify's residential proxy is supported but optional for moderate volume. Use it for runs >500 fetches.

**Can I scrape DMs / private accounts / followers list?**
No. This is a public-only scraper. Private profiles, follower lists, and DMs are not exposed in the public SSR HTML.

**Why aren't all 350 replies on a post showing up?**
The full reply tree requires a logged-in session. The discussion-tree mode returns the ~15-30 publicly-indexed posts that reference or quote the source post — which is often the higher-signal data anyway (who's talking *about* this post).

**Is this allowed by Threads' Terms of Service?**
Meta's TOS forbids automated collection without permission as a blanket policy, similar to Instagram. Public-data scraping case law (hiQ v LinkedIn 2022; Meta v Bright Data 2024) generally protects logged-out scraping. Use this actor responsibly: don't store sessionid cookies, don't access logged-in content, comply with applicable privacy law in your jurisdiction. **You are responsible for your use.**

***

### Roadmap (post-v1)

- Pagination beyond first SSR page (cursor-based, may require proxy)
- Comment-thread resolution behind a logged-in BYO-cookie path (premium)
- AI sentiment + intent classification on posts (Gemini Flash)
- Engagement velocity (Δ-likes / Δ-replies between runs)
- Weekly trend digest CSV export

Built by **constructive\_calm**. Questions or feature requests: open an issue on the Apify Store listing.

# Actor input Schema

## `mode` (type: `string`):

Pick the scrape mode. Posts-by-user is the most common starting point.

## `usernames` (type: `array`):

Threads usernames without the @. Used by profile, posts\_by\_user, monitor modes.

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

Full Threads post URLs. Used by post\_detail mode. Example: https://www.threads.com/@zuck/post/Cabc123

## `searchQuery` (type: `string`):

Keyword to search across Threads. Used by search mode.

## `hashtag` (type: `string`):

Tag without the #. Used by hashtag mode.

## `maxPostsPerUser` (type: `integer`):

Cap the number of posts returned per user. The Threads SSR HTML typically renders 7–25 most-recent posts.

## `expandReplies` (type: `boolean`):

When on, post\_detail mode returns all quote-reposts and discussion posts that reference the source post.

## `maxReplyDepth` (type: `integer`):

How deep to follow nested reply chains, when present. 0 = source post only.

## `maxRepliesPerNode` (type: `integer`):

Cap on how many replies/discussion posts to keep per parent. Top N by like count.

## `extractBioContacts` (type: `boolean`):

Pull email + phone from author bio + resolve Linktree URLs into actual contacts. Charged per resolved profile.

## `resolveLinktree` (type: `boolean`):

Follow bio links to Linktree / Beacons / Stan / Carrd to extract additional contact channels. Requires extractBioContacts=true.

## `incrementalMode` (type: `boolean`):

Skip posts older than the last run's watermark (KV-store).

## `maxItems` (type: `integer`):

Hard cap on dataset rows for this run (across all targets).

## `concurrency` (type: `integer`):

Parallel fetches across input targets. Bump to 8-10 if using Apify residential proxy.

## `proxyConfiguration` (type: `object`):

Residential proxy is recommended for runs >500 fetches. Datacenter works fine for tiny runs.

## Actor input object example

```json
{
  "mode": "posts_by_user",
  "usernames": [
    "zuck"
  ],
  "maxPostsPerUser": 25,
  "expandReplies": true,
  "maxReplyDepth": 3,
  "maxRepliesPerNode": 20,
  "extractBioContacts": false,
  "resolveLinktree": false,
  "incrementalMode": false,
  "maxItems": 500,
  "concurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All extracted profiles, posts, and replies in one dataset.

# 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 = {
    "usernames": [
        "zuck"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("constructive_calm/threads-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 = { "usernames": ["zuck"] }

# Run the Actor and wait for it to finish
run = client.actor("constructive_calm/threads-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 '{
  "usernames": [
    "zuck"
  ]
}' |
apify call constructive_calm/threads-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,constructive_calm/threads-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/GyOqqupbNIejIr3cl/builds/1cSey0aq03fajFcUa/openapi.json
