# Threads Scraper: Profiles, Posts, Search, Replies (`sourabhbgp/threads-scraper`) Actor

Scrape Meta Threads profiles, posts, keyword search results, and post replies in one actor. Paste a username, a profile URL, a post URL, or a keyword and the mode is detected for you. No login, no OAuth, no app review.

- **URL**: https://apify.com/sourabhbgp/threads-scraper.md
- **Developed by:** [Sourabh Kumar](https://apify.com/sourabhbgp) (community)
- **Categories:** Social media, AI, Agents
- **Stats:** 161 total users, 51 monthly users, 100.0% runs succeeded, 2 bookmarks
- **User rating**: No ratings yet

## Pricing

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

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: profiles, posts, keyword search and replies, no login

Scrape Meta Threads profiles, posts, search results, and replies, in one actor, with one input field, no login, no app review. Mode is auto-detected from whatever you paste.

**$2 per 1,000 results.** No per-run fee, no monthly minimum, no subscription.

Works for any public account on threads.com worldwide. Posts in any language come back unchanged: the actor doesn't translate or strip non-Latin scripts.

### Why this scraper, vs. Meta's official API or other actors

Meta's Threads API is real and free. It is also capped at **500 keyword search queries per rolling 7-day window**, requires OAuth + app review, and only exposes most analytics on the developer's *own* posts. That's enough for first-party publishing. It's not enough for monitoring competitors, scraping a competitor's profile, or backfilling historical posts.

The Apify alternatives split this into pieces. Profile here. Post-only there. Replies somewhere else. You stitch four scrapers together to do one job.

| Concern | Meta's official API | Most Apify Threads scrapers | This actor |
| --- | --- | --- | --- |
| App registration / OAuth | Required | None | None |
| Search query cap | 500 per 7 days | None | None |
| Modes you get | Publishing + own analytics | One per actor (profile *or* posts *or* search) | All four: profile, posts, search, replies |
| Replies extraction | Only on your own posts | Often missing | Yes, on any public post |
| Per-run start fee | n/a | Common ($0.005 to $0.02) | None |
| Per 1,000 results | Free, but capped | $2.50 to $20 | **$2.00** |

### What data can you extract?

<table>
<tr><td>👤 Username, full name, bio</td><td>✅ Verified flag, follower count</td><td>🖼️ Profile picture (HD)</td><td>🔗 Bio links</td></tr>
<tr><td>📝 Post text + post code</td><td>❤️ Like, repost, quote, reply count</td><td>↗️ Share count (reshare_count)</td><td>👁️ View count (replies mode)</td></tr>
<tr><td>📷 Image URLs (single + carousel)</td><td>🎬 Video URLs (direct MP4)</td><td>📌 Pinned status</td><td>✏️ Edited flag</td></tr>
<tr><td>🏷️ Topic tag</td><td>@️⃣ Mentions array</td><td>🌐 URLs array</td><td>🤝 Paid-partnership flag</td></tr>
<tr><td>🔗 Link preview card</td><td>↩️ Reply linkage (who a post replies to)</td><td>🆔 Stable user ID</td><td>📐 Media dimensions</td></tr>
</table>

The actor extracts everything Threads serves to a logged-out browser. It does not extract likers lists, follower lists, or per-post analytics. Those live behind login.

#### Profile mode

A single record per username, with the user's most recent posts nested inside `posts[]` (measured 15 to 24 across accounts). Each nested post carries the full set of engagement fields plus hashtags, mentions, URLs, paid-partnership flag, and accessibility caption.

#### Posts mode

The same fetch as profile mode, but emits one flat record per post. Use this when you want a tabular dataset of posts without the profile wrapper.

#### Search mode

One record per matching post, with the same full field set as posts mode: engagement counts, media, link preview, hashtags, mentions and reply linkage. No second run needed to get engagement on search results.

#### Replies mode

One record for the parent post (the only place `viewCount` is populated) plus one record per reply, each carrying the full post field set. A single post returned 43 replies in testing; depth varies by post.

### How to scrape Threads: step by step

1. [Create a free Apify account](https://console.apify.com/sign-up). Takes 30 seconds, no card needed.
2. Open [Threads Scraper](https://console.apify.com/actors/P5oZe4b6Lg8LpvDlN?addFromActorId=P5oZe4b6Lg8LpvDlN) in the Apify Console.
3. Paste any combination of `@usernames`, profile URLs, post URLs, or search keywords into the **URLs** field.
4. Click **Start**. A profile or search run usually finishes in 5 to 10 seconds. A 50-result batch takes under a minute.
5. Export as JSON, CSV, or Excel, or fetch via the Apify API.

### How much does Threads Scraper cost?

Pay-per-result pricing.

- **Per 1,000 results: $2.00**
- Free-plan yield (Apify's $5/month credits): roughly **2,500 results**.
- Starter-plan yield ($29/month): about **14,500 results per month**.
- No per-run fee. No subscription lock-in.

Threads is HTTP-only, with no headless browser, so compute on top of the result fee is negligible.

### Input

Paste anything. The actor figures out the mode.

```json
{
    "urls": [
        "@zuck",
        "https://www.threads.com/@nike",
        "https://www.threads.com/@nike/post/DV_RsjcEdYz",
        "AI startups",
        "#threads"
    ],
    "maxResults": 50
}
```

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `urls` | string\[] | `["https://www.threads.com/@zuck"]` | Mix of profile URLs, `@usernames`, post URLs, profile `/replies` and `/media` tabs, or search keywords. Mode is auto-detected per entry. |
| `mode` | enum | (auto) | Override auto-detection. Values: `profile`, `posts`, `search`, `replies`. |
| `maxResults` | integer | `50` | Total result cap across all entries. `0` = unlimited. |

#### Auto-detection rules

- `https://threads.com/@zuck` → profile
- `@zuck` (leading `@` required, ASCII handle ≤30 chars) → profile
- `https://threads.com/@zuck/replies` or `/media` → that tab's posts
- `https://threads.com/@zuck/post/<code>` → replies
- `AI startups` (multi-word, or word-with-spaces) → search
- `#ai` (leading `#`) → search
- Empty input → defaults to `@zuck` so the daily Apify health check passes

Share-link tracking parameters (`?ref=share`, `?fbclid=...`, `?utm_source=...`) and URL fragments (`#section`) are stripped before parsing, so social-media share links work as-is.

### Recipes (ready-to-paste)

#### Get a Threads profile and its latest posts

```json
{ "urls": ["@zuck"], "maxResults": 1 }
```

Returns one profile record with `posts[]` containing the user's most recent posts (15 to 24 in testing).

#### Pull engagement on a specific Threads post

```json
{ "urls": ["https://www.threads.com/@nike/post/DV_RsjcEdYz"] }
```

Returns the parent post with `viewCount`, `likeCount`, share/repost/quote counts, plus the visible reply tree.

#### Search Threads by hashtag or keyword

```json
{ "urls": ["#ai", "AI startups", "Apify"], "maxResults": 100 }
```

Each entry runs as a separate search and contributes up to its share of `maxResults`.

#### Bulk-scrape multiple profiles in one run

```json
{ "urls": ["@zuck", "@instagram", "@nike", "@apple"], "maxResults": 0 }
```

`maxResults: 0` removes the cap. Apify's run-level memory limit still applies.

#### Force posts-only output (skip the profile wrapper)

```json
{ "urls": ["@nike"], "mode": "posts", "maxResults": 20 }
```

### Output

Real values pulled from a live run. Fields shown as `null` are genuinely null in that mode: they aren't omitted to save space.

#### Profile record (auto-detected from `@username` or profile URL)

```json
{
    "type": "profile",
    "username": "nike",
    "fullName": "Nike",
    "bio": "Just Do It.",
    "followerCount": 14942853,
    "isVerified": true,
    "profilePicUrl": "https://instagram.fpat2-2.fna.fbcdn.net/v/...",
    "posts": [
        {
            "postCode": "DW6egjCEZ2N",
            "text": "8 Cities. 8 Stories.\n\nDrawn from deep-rooted history...",
            "likeCount": 102,
            "repostCount": 10,
            "quoteCount": 1,
            "replyCount": 8,
            "shareCount": null,
            "viewCount": null,
            "postedAt": "2026-04-09T14:07:54.000Z",
            "imageUrls": ["https://instagram.fpat2-2.fna.fbcdn.net/v/..."],
            "postUrl": "https://www.threads.com/@nike/post/DW6egjCEZ2N",
            "hashtags": [],
            "mentions": [],
            "urls": ["http://nike.com/"],
            "isPaidPartnership": false,
            "isEdited": false,
            "isPinned": true,
            "hasAudio": false,
            "accessibilityCaption": "May be an image of batting and text..."
        }
    ],
    "scrapedAt": "2026-04-28T08:48:00.000Z",
    "url": "https://www.threads.com/@nike"
}
```

#### Post record (`mode: "posts"`)

Same shape as a single entry inside `profile.posts[]`, plus `type: "post"`, `username`, and `scrapedAt` at the top level.

#### Search record (`mode: "search"` or auto-detected)

```json
{
    "type": "search",
    "query": "AI startups",
    "username": "yujjin_solo",
    "postCode": "DbS_fXBDRRu",
    "text": "Building an AI startup and currently looking for a technical co-founde",
    "likeCount": 60,
    "postUrl": "https://www.threads.com/@yujjin_solo/post/DbS_fXBDRRu",
    "scrapedAt": "2026-08-18T10:23:06.170Z",
    "repostCount": 0,
    "quoteCount": 0,
    "replyCount": 41,
    "shareCount": 2,
    "viewCount": null,
    "postedAt": "2026-07-27T12:43:27.000Z",
    "imageUrls": [],
    "hashtags": [],
    "mentions": [],
    "urls": [],
    "isPaidPartnership": false,
    "isEdited": false,
    "isPinned": false,
    "hasAudio": false,
    "accessibilityCaption": null,
    "videoUrls": [],
    "linkPreview": null,
    "isReply": false,
    "replyToUsername": null,
    "rootPostUsername": null,
    "topicTag": "startup",
    "mediaWidth": 612,
    "mediaHeight": 612
}
```

Search records carry the same field set as posts mode, so there is no need to re-run a result to get its engagement. `viewCount` is the one exception: it is only available when you scrape a post URL directly.

#### Reply record (auto-detected from `/post/<code>` URL)

```json
{
    "type": "reply",
    "parentPostCode": "DV_RsjcEdYz",
    "parentUsername": "nike",
    "isParent": true,
    "username": "nike",
    "text": "Mute the gallery. Introducing Nike Powerbeats Pro 2...",
    "likeCount": 169,
    "viewCount": 18047,
    "postedAt": "2026-03-17T10:00:00.000Z",
    "postUrl": "https://www.threads.com/@nike/post/DV_RsjcEdYz",
    "scrapedAt": "2026-04-28T08:48:00.000Z"
}
```

The parent record (`isParent: true`) carries `viewCount` for video posts. Reply records (`isParent: false`) have `viewCount: null` because Threads only exposes view counts on the parent.

### Field availability by mode

| Field | profile | posts | search | reply (parent) | reply (child) |
| --- | :-: | :-: | :-: | :-: | :-: |
| `username` | ✅ | ✅ | ✅ | ✅ | ✅ |
| `text` | nested | ✅ | ✅ | ✅ | ✅ |
| `likeCount` | nested | ✅ | ✅ | ✅ | ✅ |
| `repostCount` / `quoteCount` / `replyCount` | nested | ✅ | ✅ | ✅ | ✅ |
| `shareCount` | nested | ✅ | ✅ | ✅ | ✅ |
| `viewCount` | — | — | — | ✅ | — |
| `hashtags` / `mentions` / `urls` | nested | ✅ | ✅ | ✅ | ✅ |
| `linkPreview` | nested | ✅ | ✅ | ✅ | ✅ |
| `topicTag` | nested | ✅ | ✅ | ✅ | ✅ |
| `isReply` / `replyToUsername` / `rootPostUsername` | nested | ✅ | ✅ | ✅ | ✅ |
| `isPaidPartnership` / `isEdited` / `isPinned` / `hasAudio` | nested | ✅ | ✅ | ✅ | ✅ |
| `accessibilityCaption` | nested | ✅ | ✅ | ✅ | ✅ |
| `imageUrls` / `videoUrls` | nested | ✅ | ✅ | ✅ | ✅ |
| `mediaWidth` / `mediaHeight` | nested | ✅ | ✅ | ✅ | ✅ |
| `followerCount` / `bio` / `profilePicUrl` | ✅ | — | — | — | — |
| `userId` / `isPrivate` / `bioLinks` / `profileTags` | ✅ | — | — | — | — |

"nested" means the field appears inside the profile record's `posts[]` array, not at the top level.

**A note on `hashtags`.** Threads has largely moved away from inline `#hashtags`: across 114 posts from six large accounts, not one caption contained a `#`. Threads groups posts under a single topic tag instead, which is what `topicTag` gives you. An empty `hashtags` array is almost always the truth about the post, not a gap in the data.

### FAQ

#### How much does Threads Scraper cost?

Threads Scraper uses pay-per-result pricing. You pay **$2 for 1,000 results**. The Apify Free plan gives you $5 in usage credits a month, enough for around 2,500 results. If you run regularly, the $29/month Starter plan covers about 14,500 results.

No subscription lock-in. Pause whenever.

#### Is it legal to scrape Threads?

Scraping public data is generally allowed in the US and most of the EU, as long as you don't collect personal data covered by GDPR or CCPA without a lawful basis. This actor only touches publicly accessible pages: anything you'd see in a browser without logging in. How you use the output is on you.

Apify's full breakdown: [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/).

#### Can I integrate Threads Scraper with other tools?

Push results into **Make, Zapier, Slack, Airbyte, GitHub, Google Sheets, Google Drive**, and more. Apify treats every actor as a webhook source, so anything that consumes webhooks or pulls from an API works.

Full list: [Apify integrations](https://docs.apify.com/platform/integrations).

#### Can I use Threads Scraper with the Apify API?

Yes. Every run is available via the Apify REST API:

```bash
curl -X POST "https://api.apify.com/v2/acts/sourabhbgp~threads-scraper/runs?token=APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["@zuck"], "maxResults": 1}'
```

Docs: [Apify API reference](https://docs.apify.com/api/v2).

#### Can I use Threads Scraper through an MCP Server?

Yes. Apify ships an MCP server that exposes every actor as a tool, so Claude Desktop, Cursor, and any other MCP-capable client can call Threads Scraper directly. Setup: [Apify MCP docs](https://docs.apify.com/platform/integrations/mcp).

### Your feedback

Bug, missing field, or odd behavior? Drop a note in the [Issues tab](https://console.apify.com/actors/P5oZe4b6Lg8LpvDlN/issues). Reports go to a human and fixes usually ship the same week.

# Actor input Schema

## `urls` (type: `array`):

Paste any mix of Threads URLs, @usernames, or search keywords. The mode is detected for each entry. Accepted: @instagram, https://www.threads.com/@zuck, a post URL, a profile /replies or /media tab, or a plain keyword like AI startups. Note that a username needs its leading @, otherwise it is treated as a search keyword.

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

Leave empty to detect the mode from each entry, which is what most runs want. Set it to force one mode for every entry, and entries that do not fit it are skipped.

## `maxResults` (type: `integer`):

Total rows to return across every entry, and the number you are charged for. Set 0 for no cap. Threads serves a limited slice per profile without login, so a large number here will often return fewer rows, and the run reports how many it actually delivered.

## Actor input object example

```json
{
  "urls": [
    "https://www.threads.com/@zuck"
  ],
  "maxResults": 50
}
```

# Actor output Schema

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

All rows from the run. Post, search and reply rows share the same field set: text, like/repost/quote/reply/share counts, posted time, image and video URLs, link preview card, hashtags, mentions, links, topic tag, reply linkage (isReply, replyToUsername, rootPostUsername), media dimensions, and pinned/edited/paid-partnership flags. Profile rows add follower count, bio, bio links, profile tags, stable user ID and private flag. `viewCount` is populated only on the parent post when you scrape a post URL.

## `resultsCsv` (type: `string`):

The same rows exported as CSV, ready for Excel or Google Sheets. Array fields such as imageUrls and videoUrls are expanded into numbered columns, so read the file by column header rather than by position.

# 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 = {
    "urls": [
        "https://www.threads.com/@zuck"
    ]
};

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

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

```

## MCP server setup

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