# Threads Scraper — Posts, Profiles, Replies & Hashtags (`acebuilds/threads-scraper`) Actor

Scrape Threads posts, profiles, reply threads, search results and hashtags. Browserless, no login, and you only pay for rows actually delivered.

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

## Pricing

$2.50 / 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.

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

## Threads Scraper — Posts, Profiles, Replies & Hashtags

Scrape **Threads** (threads.com) without a browser and without logging in.

Give it a keyword, a `#hashtag`, an `@username`, or any Threads URL. Get back flat rows
ready for a spreadsheet, a dashboard, or an LLM pipeline.

***

### Why this one

Threads scrapers break in two boring ways: they don't recognise the URL you gave them, and
they die the moment Meta reshuffles the page. This one is built against both.

| | This actor | Typical Threads scraper |
|---|---|---|
| **How it finds data** | Walks the page's own JSON for any `thread_items` node | Hard-coded path into a Relay wrapper Meta reshuffles at will |
| **URL shapes understood** | profile, `/replies`, `/media`, `/@user/post/CODE`, `/t/CODE`, `threads.net`, `?si=` tracking links | Often just the plain profile URL |
| **Stable numeric author ID** | ✅ `authorId` on every row | Usually username only — which breaks when someone renames |
| **One bad target** | Skipped, with the reason; the rest still run | Whole run fails |
| **What you pay for** | **Only rows actually delivered** | Often a start fee regardless of output |

**If a run returns nothing, it costs you nothing.** There is no actor-start charge.

***

### What you get

Real, unedited rows from this input:

```json
{ "queries": ["@zuck", "#buildinpublic"], "profileTabs": ["posts", "replies", "media"], "maxItems": 25 }
```

→ **25 rows** in one run: 1 profile + 24 unique posts, zero duplicates across the three tabs.

#### A post

```json
{
  "recordType": "post",
  "code": "DVrwsE5EdSz",
  "url": "https://www.threads.com/@zuck/post/DVrwsE5EdSz",
  "text": "Meanwhile at Meta HQ",
  "authorUsername": "zuck",
  "authorId": "63055343223",
  "authorFullName": "Mark Zuckerberg",
  "authorIsVerified": true,
  "publishedAt": "2026-03-10T00:26:46.000Z",
  "likeCount": 7581,
  "replyCount": 1192,
  "repostCount": 232,
  "quoteCount": 56,
  "isReply": false,
  "mediaType": "image",
  "mediaCount": 1,
  "media": [{ "type": "image", "url": "https://instagram.fcdg4-1.fna.fbcdn.net/v/t51.82787-15/...", "width": 1080, "height": 1350 }],
  "hashtags": [],
  "sourceType": "profile",
  "sourceUrl": "https://www.threads.com/@zuck"
}
```

`authorId` is the account's **stable numeric ID**. Usernames get changed; this doesn't.
Use it as your join key.

#### A profile row

```json
{
  "recordType": "profile",
  "authorUsername": "zuck",
  "authorId": "63055343223",
  "authorFullName": "Mark Zuckerberg",
  "authorIsVerified": true,
  "followerCount": 5721349,
  "biography": "Mostly superintelligence and MMA takes",
  "bioLinks": [],
  "url": "https://www.threads.com/@zuck"
}
```

***

### Input

| Field | Type | Notes |
|---|---|---|
| `queries` | array | **Required.** Keywords, `#hashtags`, `@usernames`, or Threads URLs — mixed freely. |
| `profileTabs` | array | Which profile tabs to pull: `posts`, `replies`, `media`. More tabs = more posts. |
| `expandThreads` | boolean | Also open every post that has replies and scrape the conversation. Highest yield, highest cost. |
| `maxItems` | integer | Hard cap on delivered rows — your spend ceiling. `0` = no limit. |
| `minLikes` / `minReplies` | integer | Drop low-engagement posts. Filtered posts are **not billed**. |
| `onlyVerified` | boolean | Verified authors only. |
| `excludeReplies` | boolean | Top-level posts only. |
| `mustHaveMedia` | boolean | Only posts carrying an image or video. |
| `postedAfter` / `postedBefore` | string | `YYYY-MM-DD` date window. |
| `includeProfileInfo` | boolean | Emit the extra profile row shown above. |
| `proxyConfiguration` | object | Residential strongly recommended. |

#### Every URL shape works

```
@zuck                                          https://www.threads.net/@zuck
https://www.threads.com/@zuck                  https://www.threads.com/@zuck/replies
https://www.threads.com/@zuck/media            https://www.threads.com/@zuck/post/CODE
https://www.threads.com/t/CODE                 https://www.threads.com/@zuck/post/CODE?si=xyz
#buildinpublic                                 ai agents
```

***

### How much data you actually get — read this

Threads shows a **limited number of posts to logged-out visitors**, and this actor does not
log in. Measured yield per page fetch:

| Input | Posts per fetch |
|---|---|
| A post URL (post + its reply thread) | **~31** |
| Keyword search | **~20** |
| Hashtag | **~18** |
| Profile `/replies` tab | ~8 |
| Profile `/media` tab | ~6 |
| Profile main tab | **~4** |

**So a profile returns its most recent posts, not its full history.** Scraping all three
profile tabs is the way to widen that — measured **~18 unique posts for `@zuck` across
three tabs versus 4 from the main tab alone** — and `expandThreads` multiplies it further
by pulling each post's replies.

If you need a complete profile archive going back months, this actor will not give you
that, and no amount of retrying will change it. That is a Threads limit, not a bug, and
it is stated here rather than discovered after you have paid.

***

### Cost

You are billed **per delivered row**, so the row count is the price. Rows removed by your
filters, duplicates, and failed pages are all free.

| Job | Rows | Cost |
|---|---|---|
| One profile, all three tabs | ~18 | ~$0.05 |
| One keyword search | ~20 | ~$0.06 |
| One post + full reply thread | ~31 | ~$0.09 |
| 10 hashtags | ~180 | ~$0.54 |

Set `maxItems` to cap any run. It is a hard ceiling.

***

### Honest limitations

- **No full profile history.** See the table above. Deeper paging sits behind Threads'
  internal API and is deliberately not attempted.
- **`language` is almost always empty.** Threads does not populate `detected_language` for
  logged-out requests, so there is deliberately **no language filter** in the input — a
  filter that silently keeps everything is worse than no filter at all.
- **Private and non-existent accounts return nothing.** You get an explicit
  `does not exist or has no public content` message, and you are not charged.
- **Threads rate-limits.** Each retry rotates to a fresh proxy session *and* re-establishes
  cookies, which is what actually clears a block — a fresh IP alone gets challenged again.
  If everything is blocked the run fails and bills nothing.
- **A post with no replies yields one row.** `expandThreads` only helps where conversation exists.

***

### Use it for

- **Brand & competitor monitoring** — watch a hashtag or keyword on a schedule.
- **Creator research** — recent posts, engagement counts, and verified status per account.
- **Conversation mining** — pull a viral post's full reply thread for sentiment or support signals.
- **LLM pipelines** — flat rows, no HTML, stable numeric IDs to join on.

***

### Scheduling

Point the Apify Scheduler at this actor with a handful of keywords and `postedAfter` set to
yesterday to build your own Threads history over time. Because you only pay per delivered
row and duplicates are removed within each run, a daily watch stays cheap.

# Actor input Schema

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

One or more of: a keyword (`ai agents`), a `#hashtag`, an `@username`, or any Threads URL — profile (`https://www.threads.com/@zuck`), a tab (`/@zuck/replies`, `/@zuck/media`), a post (`/@zuck/post/CODE`) or a share link (`/t/CODE`). `threads.net` and `threads.com` both work, and tracking parameters like `?si=` are ignored.

## `profileTabs` (type: `array`):

Which tabs to pull when the input is a profile. Threads serves only a handful of posts per tab to logged-out visitors, so scraping several tabs is how you get a fuller picture — measured ~4 posts from `posts` alone vs ~18 across all three. Duplicates are removed and never billed twice.

## `expandThreads` (type: `boolean`):

After collecting posts, also open each post that has replies and scrape the conversation under it (measured ~31 posts per thread). This is the highest-yield option in the actor — and the most expensive, since every reply is a billable row. Use `maxItems` with it.

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

Hard ceiling on delivered rows. You are billed per row, so this is your cost cap. `0` = no limit.

## `minLikes` (type: `integer`):

Skip posts below this like count. Filtered posts are never billed.

## `minReplies` (type: `integer`):

Skip posts below this reply count.

## `onlyVerified` (type: `boolean`):

Keep only posts written by verified accounts.

## `excludeReplies` (type: `boolean`):

Keep only top-level posts, dropping anything that is a reply to someone else.

## `mustHaveMedia` (type: `boolean`):

Keep only posts that carry an image or video.

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

Keep only posts published on or after this date, e.g. `2026-01-31`.

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

Keep only posts published on or before this date, e.g. `2026-08-31`.

## `includeProfileInfo` (type: `boolean`):

For profile inputs, emit one extra row with the account's bio, bio links, follower count and verification status.

## `maxRetries` (type: `integer`):

Threads rate-limits aggressively. Each retry rotates to a fresh proxy session and re-establishes cookies, which is what actually clears a block.

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

Residential proxies are strongly recommended — Meta challenges datacenter ranges. Falls back automatically if residential is not on your plan.

## Actor input object example

```json
{
  "queries": [
    "ai agents",
    "@zuck"
  ],
  "profileTabs": [
    "posts",
    "replies"
  ],
  "expandThreads": false,
  "maxItems": 0,
  "minLikes": 0,
  "minReplies": 0,
  "onlyVerified": false,
  "excludeReplies": false,
  "mustHaveMedia": false,
  "postedAfter": "",
  "postedBefore": "",
  "includeProfileInfo": true,
  "maxRetries": 4,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `posts` (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 = {
    "queries": [
        "ai agents",
        "@zuck"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("acebuilds/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 = { "queries": [
        "ai agents",
        "@zuck",
    ] }

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

```

## MCP server setup

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