# Meta Threads Scraper - Posts, Replies, Profiles, Keyword Search (`webdatalabs/meta-threads-scraper`) Actor

Extract public Meta Threads posts, replies, profiles and keyword search results. Verified keyword matching, typed status on every request, and honest nulls instead of fake zeros. No login, no cookies. Export CSV/JSON.

- **URL**: https://apify.com/webdatalabs/meta-threads-scraper.md
- **Developed by:** [WebDataLabs](https://apify.com/webdatalabs) (community)
- **Categories:** Social media, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 post scrapeds

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

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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, Replies, Profiles, Keyword Search

Extract public Meta Threads posts, replies, profiles and keyword search results. No login, no cookies, no API key.

### What you get

This Threads scraper returns **one flat record per public post**, with the engagement metrics Threads actually publishes — likes, replies, reposts, quotes, reshares and (on post detail) views. It runs logged out against public data only.

Three things it does that most Threads scrapers do not:

1. **It checks that search results contain your keyword.** Threads' own ranking returns posts that do not, which is the single most common complaint about keyword scraping on this platform. Every search row carries `keywordMatched` and `matchedField` so you can see which results really matched and where. On live runs a third of the results Threads returns for a keyword do not actually contain it — those are flagged, not silently mixed in.
2. **It never finishes with an unexplained empty dataset.** Every query produces a status row with `status`, `statusReason`, `requestedCount`, `returnedCount` and `capReason`. If a run returns little, the dataset itself tells you why.
3. **A missing number is `null`, never `0`.** Each metric has a companion `*_status` field — `available`, `not_public`, `not_returned_by_source` or `not_checked`. A `0` in this dataset means genuinely zero. This matters: a fake `0` silently corrupts any average, ranking or trend you build downstream.

### What data can it extract?

| Field | What it is |
|---|---|
| `text`, `language`, `postedAt` | Post body, detected language, publication time |
| `authorUsername`, `authorFullName`, `authorIsVerified`, `authorId` | Who posted it |
| `likeCount`, `replyCount`, `repostCount`, `quoteCount`, `reshareCount` | Engagement, each with a `*_status` companion |
| `viewCount` | Public view count — published by Threads for the root post in Post detail mode |
| `isReply`, `replyToUsername`, `quotedPostUrl` | Conversation context |
| `imageUrls`, `videoUrls`, `linkUrl`, `linkTitle` | Media, and link previews unwrapped from Meta's redirect |
| `keywordMatched`, `matchedField` | Search mode: whether the post really contains your keyword |
| `profileFollowerCount`, `profileBio`, `profileBioLinks` | Profile mode |
| `status`, `statusReason`, `returnedCount`, `capReason` | Why this query returned what it did |

The full field list is rendered from the dataset schema above the output table.

### Example output

A real record from a live run — `mode: search`, keyword `openai`:

```json
{
  "recordType": "post",
  "mode": "search",
  "query": "openai",
  "postId": "3949278582485051018",
  "shortcode": "DbOp_JUFGaK",
  "url": "https://www.threads.com/@aaronparnas/post/DbOp_JUFGaK",
  "postedAt": "2026-07-25T20:18:35.000Z",
  "authorUsername": "aaronparnas",
  "authorFullName": "Aaron Parnas",
  "authorIsVerified": true,
  "text": "OpenAI caught one of its AI agents leaving “notes” to future versions of itself on how to escape internal constraints.",
  "language": null,
  "imageUrls": [],
  "videoUrls": [],
  "likeCount": 19440,
  "likeCountStatus": "available",
  "replyCount": 565,
  "replyCountStatus": "available",
  "repostCount": 1217,
  "repostCountStatus": "available",
  "quoteCount": 142,
  "quoteCountStatus": "available",
  "viewCount": null,
  "viewCountStatus": "not_checked",
  "isReply": false,
  "keywordMatched": true,
  "matchedField": "caption",
  "status": "ok",
  "statusReason": null
}
```

### How to use it

Pick a mode, paste what you already have into **Queries**, run. Accounts work as `zuck`, `@zuck` or a full profile URL; posts work as a full link or the bare shortcode.

**Find posts about a topic**

```json
{ "mode": "search", "queries": ["openai", "climate tech"], "maxItemsPerQuery": 100 }
```

**Track what an account posts**

```json
{ "mode": "userPosts", "queries": ["@zuck", "mosseri"], "maxItemsPerQuery": 200 }
```

**Pull one post with its public replies**

```json
{ "mode": "postDetail", "queries": ["https://www.threads.com/@zuck/post/DakyAavlKLZ"], "includeReplyTree": true }
```

**Profile stats for a list of accounts**

```json
{ "mode": "profile", "queries": ["zuck", "mosseri", "meta"] }
```

### How much does it cost to scrape Threads?

Pay per result — you are charged for each post record delivered, and nothing else. Status rows that explain a failure are free, and a run that returns no posts costs effectively nothing. The current price is shown in the pricing box on this page.

### Use cases

- **Social listening** — run `mode: search` on your brand and category, then filter `keywordMatched = true` to drop the results Threads' ranking padded in.
- **Competitor tracking** — `mode: userPosts` on rival accounts, sorted by `likeCount`, to see which messages land.
- **Creator research** — `mode: profile` across a list of handles for follower counts and bio links, then `userPosts` on the ones worth watching.
- **Conversation analysis** — `mode: postDetail` with `includeReplyTree`, then group by `replyToUsername` to see who a thread pulls in.
- **Dataset building for AI** — one flat record per post, with `*_status` fields so you can exclude uncertain metrics instead of training on fabricated zeros.

### Frequently asked questions

#### Do I need a Threads or Instagram login?

No. This actor scrapes only what Threads shows a logged-out visitor. No account, no cookies and no API key are involved.

#### How many search results can I get per keyword?

Roughly 20 to 40 per keyword. Threads does not paginate keyword search for logged-out visitors — it reports no next page, and no cursor exists in its search API. This actor fetches **both** the top and the recent ranking and combines them, which measured **96% more unique posts** than the default ranking alone across four test keywords. Beyond that, no scraper can go deeper without an account. `capReason: "search_page_limit"` tells you when you have hit it. For depth, use `userPosts` — that mode paginates freely.

#### How many replies does Post detail return?

About 20, no matter how many the post has. Threads serves logged-out visitors only the first page of a conversation; a post with 839 replies still returns about 20. The true total is always reported in `replyCount`, so you can see the gap.

#### Why is `viewCount` empty on most rows?

Threads publishes a view count only for the root post of a permalink. In other modes it is not fetched at all, and the row says so with `viewCountStatus: "not_checked"` rather than putting a `0` there.

#### Does it match what I see in the Threads app?

Not exactly. The Threads web and app rankings differ, so the set of posts a keyword returns here will not be identical to the app's Recent tab. Everything returned is real, public and current — but it is the web result set.

#### Is it legal to scrape Threads?

This actor collects only public data that Threads serves to any logged-out visitor, and it does not log in or bypass authentication. It deliberately does **not** extract emails or phone numbers from bios. You are responsible for how you use the output — if you process personal data of people in the EU or UK you need a lawful basis under GDPR, and you should review Meta's terms for your use case. For anything commercially sensitive, take your own legal advice.

#### What happens when a run returns nothing?

You still get a row. Every query emits a status row naming the reason: `no_public_posts`, `profile_not_found`, `login_wall`, `rate_limited` or `invalid_input`, plus a plain-language `statusReason`. An invalid input is rejected before anything is charged.

#### Can it monitor Threads on a schedule?

Yes. Add the actor to a schedule in Apify Console and it will run at your chosen interval. Records carry `scrapedAt`, so you can diff runs to detect new posts or engagement changes.

### Related actors

- [Meta Ad Library Scraper](https://apify.com/webdatalabs/meta-ad-library-scraper) — Facebook and Instagram ads, days running and creative changes
- [Facebook Ad Scraper with AI Analysis](https://apify.com/webdatalabs/facebook-ad-intelligence-pro) — ad creative scored and summarised
- [Reddit Scraper Pro](https://apify.com/webdatalabs/reddit-scraper-pro) — posts and full comment threads
- [TikTok Shop Scraper](https://apify.com/webdatalabs/tiktok-shop-scraper) — products, sales and sellers
- [YouTube Shorts Scraper](https://apify.com/webdatalabs/youtube-shorts-scraper) — Shorts metadata and engagement
- [Google Trends Scraper](https://apify.com/webdatalabs/google-trends-reliable) — reliable interest-over-time data

### Support

Found a bug or need a field that is missing? Open an issue on the **Issues** tab of this actor and it will be picked up there.

# Actor input Schema

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

What to scrape. The Queries field below changes meaning to match: keywords for Search, @handles for the three profile modes, post links for Post detail.

## `queries` (type: `array`):

One entry per keyword, account or post. Accounts work as zuck, @zuck or https://www.threads.com/@zuck. Posts work as a full link or the bare shortcode. Hashtags work with or without the #.

## `maxItemsPerQuery` (type: `integer`):

Cap per entry above, not for the whole run. Search is capped by Threads itself at roughly 20 results per keyword when logged out - asking for more will not return more, and the run tells you so.

## `includeReplyTree` (type: `boolean`):

Post detail mode only. Off returns just the post you asked for. On also returns the replies Threads shows publicly underneath it, as ordinary post rows. Threads serves roughly 20 replies to logged-out visitors no matter how many the post has.

## `postedAfter` (type: `string`):

Optional client-side filter, YYYY-MM-DD. Threads has no server-side date filter when logged out, so this narrows the results already fetched - it cannot reach further back in time. Every row records which filters were applied.

## `postedBefore` (type: `string`):

Optional client-side filter, YYYY-MM-DD. Same limitation as Posted after.

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

Threads serves an empty page shell to datacenter IPs, so residential is the default. Change it only if you have your own residential or mobile pool.

## Actor input object example

```json
{
  "mode": "search",
  "queries": [
    "openai",
    "@zuck",
    "https://www.threads.com/@zuck/post/DakyAavlKLZ"
  ],
  "maxItemsPerQuery": 100,
  "includeReplyTree": false,
  "postedAfter": "2026-01-01",
  "postedBefore": "2026-12-31",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `engagement` (type: `string`):

No description

## `keywordAudit` (type: `string`):

No description

## `profiles` (type: `string`):

No description

## `runStatus` (type: `string`):

No description

## `full` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# 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 = {
    "mode": "search",
    "queries": [
        "openai"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("webdatalabs/meta-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 = {
    "mode": "search",
    "queries": ["openai"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("webdatalabs/meta-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 '{
  "mode": "search",
  "queries": [
    "openai"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call webdatalabs/meta-threads-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,webdatalabs/meta-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/S5fzj2MPHfhryCTNe/builds/erOm8W5ImjYcNhRlp/openapi.json
