# Threads Scraper — Posts, Profiles, Replies & Search (`northbound-data/threads-scraper`) Actor

Extract public Threads (Meta) data — posts, replies, profiles, keyword search — as clean structured JSON. No login or cookies. Recommended posts are filtered out, so replies are actually replies. Structure-adaptive parser that survives Meta's changes.

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

## Pricing

from $3.50 / 1,000 results

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

## Threads Scraper — Posts, Profiles, Replies & Keyword Search

Extract public **Threads (Meta)** data as clean structured JSON: posts, replies, profiles and keyword search results. **No login. No cookies. No account of yours is ever used.**

Give it a post URL, a profile URL, a `@username` or a keyword — mix them freely in one run — and get back a flat, ready-to-use dataset.

***

### Why this one

Most Threads scrapers break the moment Meta ships a change, because they hard-code GraphQL `doc_id` values or fixed JSON paths. This one identifies data by **structural signature**, not by path — so a Meta refactor degrades it instead of killing it.

| | This Actor | Typical alternative |
|---|---|---|
| Measured failure rate | **0%** across 24 tasks — 8 profiles, 10 posts, 6 keyword searches, **647 records** delivered, 72 fetches with **zero** render fallbacks | 13% – 17% on the two largest Threads Actors |
| Recommended posts mixed into replies | **Never** — filtered out explicitly | Common, and it silently pollutes your dataset |
| Breaks when Meta rotates `doc_id` | No — nothing is hard-coded | Yes |
| Login / cookies required | **No** | Varies |

#### Replies are actually replies

Threads serves *recommended posts* in the same payload as the real replies to a post. Naive scrapers hand you both, mixed together, with no way to tell them apart — you think you have 30 replies to your post, and 10 of them are strangers talking about something else.

This Actor separates them and returns only genuine replies, each carrying `root_post_author` and `replied_to_username` so you can verify the thread they belong to.

***

### Honest limitation — please read before you buy

**Meta caps logged-out access to Threads.** Measured, as of August 2026:

| Surface | What you get without logging in |
|---|---|
| Replies to a post | ~20 (Threads then shows *"Log in to see more replies."*) |
| Keyword search | ~20 per surface — but this Actor merges all 5 public surfaces for ~50 (see below) |
| Posts on a profile | ~5 per tab — but this Actor merges all 4 public tabs for 16-22 (see below) |

**Replies cannot exceed this** — it is enforced by Meta, not by this Actor.

**Profiles and search are the exceptions**, because Meta caps each *entry point*, not the total.

**Search is the exception.** Threads exposes five separate public search surfaces (`default`, `recent`, `top`, `users`, `tags`), each capped at ~20 but each returning a *different* result set. This Actor queries all five and merges them deduplicated, which measured **47 posts for `ai agents` and 54 for `climate tech`** — about 2.4–2.7x what a single-surface scraper returns. Every record carries a `search_surface` field. Set `searchDepth: "standard"` for the faster single-surface behaviour.

**Profiles work the same way.** A Threads profile has four public tabs — `threads`, `replies`, `media`, `reposts` — each capped at ~5 but each holding different posts. This Actor queries all four and merges them: measured **16 posts for `@nasa` and 22 for `@mkbhd`**, against 5-6 from the main tab alone (2.7-4.4x). Every record carries a `profile_tab` field. Set `profileDepth: "standard"` to query only the main tab.

Neither of these is a bypass — every surface and tab is the same public endpoint the Threads website itself uses, read logged-out. Anything promising you "all 800 replies" without an account is either using logged-in accounts (against Threads' terms, and those accounts get banned) or it is not telling you the truth.

This Actor is built for **per-target lookup at volume**: point it at 1,000 posts and get the top ~20 replies for each, reliably. It is not a bulk archive crawler. If you need full reply archives, this is not the right tool and you should not buy it.

When the cap is reached the Actor stops immediately instead of burning your compute on scrolls that return nothing.

***

### Dead handles do not count against us — and we tell you which ones they were

Feed in a list of 40 brand accounts and some of them will not exist on Threads. In our own 40-account test, six did not: `tesla`, `spacex`, `apple`, `stripe`, `vercel`, `anthropicai`.

Most scrapers count those as failures, which inflates the failure rate you see and tells you nothing about which inputs were bad. This Actor separates them:

- `failedTasks` — runs that actually went wrong
- `notFoundTargets` + `notFoundList` — targets that simply do not exist, listed by name so you can clean your input
- `failureRate` — computed over real attempts only, excluding non-existent targets

You get an honest reliability number *and* a list of the handles worth removing from your source data.

***

### Why residential proxy is the default

Meta serves **empty shell pages to datacenter IPs**. When that happens this Actor falls back to full browser rendering, which still returns correct data but is dramatically slower and more expensive.

Measured on the Apify platform, same target, same day (2026-08-19):

| Proxy | Mode used | Time per profile | Compute cost per run |
|---|---|---|---|
| Datacenter (Apify default) | browser rendering | **150 s** | $0.127 |
| Residential | direct fetch | **3.3 s** | $0.0086 |

That is 45x faster and 15x cheaper, for a residential transfer cost of roughly 7% of what the returned records are worth. So `RESIDENTIAL` ships as the default proxy group.

You can override it, but if you switch to datacenter expect slow runs — the Actor will still work, it just has to render every page.

***

### Input

```json
{
  "startUrls": [
    { "url": "https://www.threads.com/@natgeo/post/Db5pTtZFAmB" },
    { "url": "https://www.threads.com/@nasa" }
  ],
  "usernames": ["mkbhd"],
  "searchQueries": ["ai agents", "climate tech"],
  "includeReplies": true,
  "maxRepliesPerPost": 25,
  "maxItems": 1000
}
```

| Field | Type | Notes |
|---|---|---|
| `startUrls` | array | Post URLs, profile URLs or search URLs. Detected automatically. |
| `usernames` | array | Bare usernames, with or without `@`. |
| `searchQueries` | array | Keyword searches. |
| `includeReplies` | boolean | Default `true`. Set `false` for posts only. |
| `maxRepliesPerPost` | integer | Default `25`. Values above ~20 will not return more — see the limitation above. Set to `20` for the fastest path (~2 s/post, no page rendering). |
| `maxItems` | integer | Hard cap on billed records. Your safety net against surprise charges. |
| `proxyConfiguration` | object | Residential proxies recommended for larger runs. |

### Output

One flat record per post or reply:

```json
{
  "id": "3961381054679086896",
  "code": "Db5pxS4G_cw",
  "url": "https://www.threads.com/@catacolypse_/post/Db5pxS4G_cw",
  "is_reply": true,
  "replied_to_username": "natgeo",
  "root_post_author": "natgeo",
  "text": "Ohmygosh, yes!! We just had this visitor yesterday!",
  "posted_at": "2026-08-11T13:04:02.000Z",
  "like_count": 101,
  "reply_count": 2,
  "repost_count": 0,
  "quote_count": 0,
  "author": {
    "id": "63294227753",
    "username": "catacolypse_",
    "full_name": "Cathie English",
    "is_verified": false,
    "profile_pic_url": "https://…"
  },
  "media": { "images": [], "videos": [], "alt_text": null },
  "query_type": "post",
  "query_value": "https://www.threads.com/@natgeo/post/Db5pTtZFAmB",
  "source_url": "https://www.threads.com/@natgeo/post/Db5pTtZFAmB",
  "scraped_at": "2026-08-12T13:56:56.667Z"
}
```

Every run also writes a `RUN_SUMMARY` key with `tasks`, `itemsPushed`, `failedTasks` and `failureRate`, so you can monitor reliability yourself instead of taking our word for it.

### What it's used for

- **Brand & product monitoring** — track what people reply to your announcements
- **Competitor watch** — follow competitor accounts and the sentiment underneath their posts
- **Creator research** — find accounts and posts that perform in a topic
- **AI agents & RAG** — clean JSON in, no HTML parsing, ready to embed

### Pricing

Pay per result. You are charged only for records actually delivered to your dataset, and duplicates within a run are never billed twice. `maxItems` caps your spend.

### Legal & scope

Reads **public** Threads pages only. No login, no cookies, no CAPTCHA solving, no access-control circumvention. Threads publishes no `robots.txt` restriction for these paths. You are responsible for how you use the data, including GDPR/CCPA obligations where personal data is involved.

# Actor input Schema

## `startUrls` (type: `array`):

Profile URLs (https://www.threads.com/@nasa), post URLs (https://www.threads.com/@nasa/post/XXXX) or search URLs. Mix them freely.

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

Threads usernames without the @ (e.g. nasa). Returns the profile and its latest posts.

## `searchQueries` (type: `array`):

Keyword searches on Threads. Each keyword returns the public search result page.

## `includeReplies` (type: `boolean`):

Include replies to posts, not just the posts themselves.

## `maxRepliesPerPost` (type: `integer`):

Upper bound on replies returned per post. NOTE: Meta caps logged-out access at roughly 21 replies per post, so values above ~25 will not return more. This is a Threads limitation, not a limitation of this Actor — no logged-out tool can exceed it.

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

Hard cap on total records pushed to the dataset. Protects you from unexpected charges. The default of 100 keeps a first trial run cheap; raise it for production runs.

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

Proxy settings. RESIDENTIAL is the default and strongly recommended: Meta serves empty shell pages to datacenter IPs, which forces this Actor into slow browser rendering. Measured on 2026-08-19: datacenter took 150s per profile, residential took 3.3s — 45x faster and 15x cheaper in compute. Residential transfer is billed by Apify at ~7% of what these results are worth.

## `searchDepth` (type: `string`):

How many of Threads' public search surfaces to query per keyword. "deep" queries all five (default, recent, top, users, tags) and merges the results, which returns roughly 2.4-2.7x more unique posts than a single surface. "standard" queries only the default surface - faster and cheaper, about 20 posts.

## `profileDepth` (type: `string`):

How many of a profile's public tabs to query. "deep" queries all four (threads, replies, media, reposts) and merges them, which measured 2.7-4.4x more posts than the main tab alone. "standard" queries only the main tab - faster and cheaper.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.threads.com/@natgeo/post/Db5pTtZFAmB"
    }
  ],
  "usernames": [],
  "searchQueries": [],
  "includeReplies": true,
  "maxRepliesPerPost": 25,
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "searchDepth": "deep",
  "profileDepth": "deep"
}
```

# Actor output Schema

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

One record per post or reply, with author, text, timestamp and engagement counts. Replies carry root\_post\_author so you can verify which thread they belong to.

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

Reliability report for this run: tasks attempted, items delivered, failed tasks and the resulting failureRate. Use it to monitor the Actor yourself instead of taking our word for it.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.threads.com/@natgeo/post/Db5pTtZFAmB"
        }
    ],
    "usernames": [],
    "searchQueries": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("northbound-data/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 = {
    "startUrls": [{ "url": "https://www.threads.com/@natgeo/post/Db5pTtZFAmB" }],
    "usernames": [],
    "searchQueries": [],
}

# Run the Actor and wait for it to finish
run = client.actor("northbound-data/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 '{
  "startUrls": [
    {
      "url": "https://www.threads.com/@natgeo/post/Db5pTtZFAmB"
    }
  ],
  "usernames": [],
  "searchQueries": []
}' |
apify call northbound-data/threads-scraper --silent --output-dataset

```

## MCP server setup

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