# Threads Scraper - Profiles, Posts, Likes & Follower Counts (`dami_studio/threads-scraper`) Actor

Threads scraper for public profiles and recent posts, no login. threads.com and threads.net links work, and so does a bare handle. Profile rows have the bio and followers; post rows have the text, likes, exact time, images, and video and reply flags. 30 posts per profile. $0.40 per 1,000 rows.

- **URL**: https://apify.com/dami_studio/threads-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Social media, Automation
- **Stats:** 13 total users, 5 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.38 / 1,000 threads profile or posts

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

**Threads scraper for handles, a row per profile plus their recent public posts**: bio,
follower count, post text, likes, the exact publish time, image links, and flags for video and
replies. Nothing to log into.

You have to know whose posts you want. There is no anonymous global search on Threads that holds up,
so this reads the handles you list and nothing wider. The `query` field filters those posts, it does
not search Threads, and 30 recent posts per profile is the most it reads.

| | |
|---|---|
| **Input** | Threads handles or profile URLs |
| **Output** | One row per profile (bio, followers) and one per post (text, likes, publish time, images, video and reply flags) |
| **Ceiling** | 30 posts per profile |
| **Account needed** | None |
| **Price** | $0.40 per 1,000 rows on the Free plan, a little less on paid plans. The free plan's $5 a month covers about 12,500 |

### 🔍 What Threads Scraper does

Threads lives on `threads.com` now. A `threads.net` link redirects across, so both forms work, and so
does a bare handle like `zuck`. Duplicates in your list are collapsed before anything is fetched,
whatever the capitalisation.

For each handle it reads the public profile, then the public feed of that account's recent posts. You
get one `profile` row and up to 30 `post` rows per handle, and you can turn the profile rows off if
all you want is the posts.

`query` is an optional case-insensitive text filter over the posts it collected, so you can run ten
accounts and keep only the ones that said "pricing". It narrows what you already asked for. It does
not reach any post from an account you did not list.

### 📋 What data you get from each Threads profile and post

| What you get | Field |
|---|---|
| Profile: handle, display name, id and link | `username`, `displayName`, `id`, `profileUrl` |
| Profile: bio, follower count and picture | `bio`, `followerCount`, `profilePicUrl` |
| Profile: verified badge (always empty, see below) | `isVerified` |
| Post: id, short code and link | `postId`, `postCode`, `postUrl` |
| Post: text and like count | `text`, `likeCount` |
| Post: exact publish time | `publishedAt` |
| Post: image links, and whether it has a video or is a reply | `imageUrls`, `hasVideo`, `isReply` |
| What the row is | `recordType` |

### ▶️ How to scrape Threads posts

1. Open [Threads Scraper](https://apify.com/dami_studio/threads-scraper) and click **Try for free**.
2. Put your handles into **Threads profiles**, one per line.
3. Set **Maximum recent posts per profile**. Ten is plenty for a first look.
4. Click **Start**.
5. Download the dataset as JSON, CSV or Excel, or read it from the Apify API.

Leave **Threads profiles** empty and you get a single labelled sample row, so you can see the shape
before committing a list. It is not charged.

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

**$0.40 per 1,000 rows** on the Free plan. Paid plans pay a little less: $0.395 on Starter, $0.39 on Scale and $0.385 on Business. On the free plan, the $5 Apify
gives you each month covers about 12,500 rows.

A profile row and a post row cost the same. Turning **Include profile rows** off is the cheapest way
to run a posts-only monitor, because a skipped profile row is a row you are not billed for. The
sample row is not charged, diagnostic rows are not charged, and a handle you listed twice only
produces one set of rows.

### 📥 What you give it

```json
{
  "profiles": ["zuck", "https://www.threads.com/@instagram"],
  "maxPostsPerProfile": 10,
  "includeProfile": true,
  "query": "AI"
}
```

| Field | Default | What it is |
|---|---|---|
| `profiles` | none | Handles or full profile URLs. `zuck`, `@zuck` and the full link all resolve the same. The box starts with `zuck` in it, which is a suggestion and not a default. |
| `maxPostsPerProfile` | `10` | Recent public posts to keep per handle, 1 to 30. |
| `includeProfile` | `true` | Keep one profile row per handle. Turn it off for a posts-only run. |
| `query` | none | Case-insensitive text filter over the posts collected from your handles. Not a Threads search. |
| `fallbackToResidential` | `false` | Off. When on, a request that fails is tried once more with the settings below. It does nothing unless you fill those in. |
| `proxyConfiguration` | `{"useApifyProxy": false}` | Optional and off. Your own servers are used exactly as given. |

### 📤 What you get back

Every row carries a `recordType`, so one pass over the dataset splits it. A real profile row and a
real post row, with the avatar URL and the post text cut short:

```json
{
  "ok": true,
  "recordType": "profile",
  "id": "63055343223",
  "username": "zuck",
  "displayName": "Mark Zuckerberg",
  "bio": "Mostly superintelligence and MMA takes. See the latest conversations with @zuck.",
  "followerCount": 5700000,
  "isVerified": null,
  "profilePicUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.82787-19/550174606_...",
  "profileUrl": "https://www.threads.com/@zuck"
}
{
  "ok": true,
  "recordType": "post",
  "id": "3981852126213720917",
  "postId": "3981852126213720917",
  "username": "zuck",
  "text": "Introducing @Muse, the personal agent that understands your goals and works 24/7 to get things done for you...",
  "likeCount": 3007,
  "publishedAt": "2026-09-08T18:56:24.000Z",
  "postCode": "DdCYWl7GktV",
  "postUrl": "https://www.threads.com/@zuck/post/DdCYWl7GktV",
  "imageUrls": [],
  "hasVideo": false,
  "isReply": false
}
```

| Field | How to read it |
|---|---|
| `followerCount` | A number, but only as precise as Threads prints it. It publishes `5.7M` for large accounts, so you get `5700000` and not the exact figure. |
| `isVerified` | Comes back `null`. Threads does not put the badge in the public profile data, and a guess would be worse than a blank. |
| `postId`, `postCode` | `postCode` is the short code in the URL. Either one works as a dedupe key across runs. |
| `likeCount` | `null` when Threads does not publish a count for that post. |
| `text` | The post text. Long posts are kept whole; the example above is cut short for this page only. |
| `imageUrls` | Up to five public image links. Empty for a text post. |
| `hasVideo`, `isReply` | Booleans. There is no video file, just the flag. |

### 🧾 Reading the output

| Row | How to spot it | Charged |
|---|---|---|
| A profile | `recordType: "profile"` | yes |
| A post | `recordType: "post"` | yes |
| The sample row | `_sample: true` | no |
| A diagnostic | `_diagnostic: true`, `recordType: "diagnostic"` and a `code` | no |

The sample row also carries `recordType: "profile"`, so filter on `_sample` and `_diagnostic` being
absent rather than on `recordType` alone. A diagnostic row names the handle it belongs to:

| `code` | What it means |
|---|---|
| `NOT_FOUND` | No public Threads profile for that handle. Check the spelling. |
| `NO_POSTS` | The profile resolved but no posts came back, or none matched your `query`. |
| `NO_RESULTS` | Nothing at all was saved for that handle. |
| `NOT_SERVED` | This request was not served this run. Re-run it later. Runs before 13 October 2026 wrote `BLOCKED` here. |
| `RATE_LIMITED` | Too many requests too quickly. Give it a few minutes. |
| `NETWORK` | Threads was unreachable or answered badly. Re-run it. |
| `TARGET_ERROR` | Threads answered with something the actor could not read, usually a page without the public profile data on it. |
| `BAD_INPUT` | An entry in `profiles` is not a Threads handle or profile link, so nothing was read. Fix or remove it and run again. |
| `UNEXPECTED_ERROR` | Something broke on our side. It is not charged. If nothing came back, the run ends as failed so we see it; send the run ID in the Issues tab. |
| `TIME_LIMIT` | The run got close to its time limit and stopped on its own. The profiles and posts delivered before that are complete and charged. Give the run more time, or list fewer profiles, to get the rest. |

The run ends as succeeded either way. If the time limit stopped it before any profile or post came back, you get a `TIME_LIMIT` row and an amber status message; otherwise you get what it delivered.

### 💡 What people use it for

- Watching what a set of accounts posted this week, on a schedule, with `postCode` as the dedupe key.
- Pulling the text of an account's recent posts into a summary without opening the
  app.
- Tracking like counts on a competitor's posts over time, by running the same handles daily and
  joining on `postId`.
- Filtering a list of industry accounts down to the posts that mention one word, using `query`, so a
  30-account sweep lands as a handful of rows.

The same accounts on Instagram, in three steps:

1. Run this on your handles with `includeProfile` on.
2. Copy the `username` column (most Threads accounts use their Instagram handle).
3. Paste it into
   [Instagram Profile Scraper](https://apify.com/dami_studio/instagram-profile-scraper) for exact
   follower counts and categories.

### 🚧 What it does not do

- **No global search.** You cannot ask for every post mentioning a word. You give it handles.
- **No hashtags, no follower lists, no following lists, no reply threads and no comments** under a
  post.
- **A bad handle can end the run.** One entry that is not a usable handle stops the batch before the
  good handles are written, and you get a single `BAD_INPUT` row instead of your data. Check the
  list before a long run.
- **`isVerified` is always `null`.**
- **`followerCount` is rounded** whenever Threads abbreviates it.
- **30 posts per profile is the hard ceiling**, and recent posts only. There is no way back through
  an account's history.
- **Public accounts only.** A profile that does not resolve publicly comes back as a diagnostic row.
- **Image links are Meta CDN URLs.** They work for a while and are not archival. Video is a flag, not
  a file.
- It is not an official Threads tool. Dami's Studio is independent and is not affiliated with or endorsed by Threads or Meta, or by any other company named on this page.

### 🧭 Which social scraper do you need?

| If you want | Use |
|---|---|
| Threads profiles and their recent posts | This one |
| The same accounts' Instagram profile and grid | [Instagram Scraper](https://apify.com/dami_studio/instagram-scraper) |
| Exact Instagram follower counts and categories | [Instagram Profile Scraper](https://apify.com/dami_studio/instagram-profile-scraper) |
| Profiles and posts from Bluesky | [Bluesky Scraper](https://apify.com/dami_studio/bluesky-scraper) |

### ❓ Questions people ask

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

No. Nothing to connect, no cookie, no token.

#### Can I search Threads for a keyword?

Not from here. `query` filters the posts from the accounts you listed. A real global search is not
something Threads exposes to a signed-out visitor.

#### Do threads.net links work?

Yes. They redirect to `threads.com` and resolve to the same account.

#### Why do the numbers differ from what I see in the app?

Threads prints big follower counts rounded, so a `5.7M` on the page becomes `5700000` here. If a post
seems missing, open the profile in a private browser window: what a logged-out visitor sees there is
what this actor reads.

#### Can I call it from code or connect it to an AI assistant?

The [API tab](https://apify.com/dami_studio/threads-scraper/api/python) has code for Python, JavaScript and the CLI.
MCP clients like Claude and ChatGPT use
`https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/threads-scraper`; coding assistants can load [our skill
file](https://api.apify.com/v2/key-value-stores/BTBHU3DdiuoF4JOsK/records/apify-facebook-meta-SKILL.md). All three
bill your Apify account at the same price.

#### Is scraping Threads legal?

These are public profiles and public posts. They are also personal data, which GDPR and similar laws
cover, so have a reason for collecting it. Apify's
[write-up on scraping and the law](https://blog.apify.com/is-web-scraping-legal/) is a good starting
point, and we are not lawyers.

### 🆘 If something breaks

Open the **Issues** tab on the actor page. Send the handle and the run ID. If you got a diagnostic
row, its `code` and `details` usually name the problem on their own.

# Actor input Schema

## `profiles` (type: `array`):

Public Threads handles or profile URLs. Leave empty for an uncharged sample row.

## `query` (type: `string`):

Optional case-insensitive text filter applied to posts from the supplied profiles. This is not a global Threads search.

## `maxPostsPerProfile` (type: `integer`):

Maximum genuine public posts saved for each supplied public profile.

## `includeProfile` (type: `boolean`):

Save one public profile row for every successfully resolved handle.

## `fallbackToResidential` (type: `boolean`):

Off by default. When on, a request that fails is tried once more with the settings below. It does nothing unless you fill those in.

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

Optional. Your own proxy servers are used exactly as given.

## Actor input object example

```json
{
  "profiles": [
    "zuck",
    "instagram"
  ],
  "maxPostsPerProfile": 10,
  "includeProfile": true,
  "fallbackToResidential": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (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 = {
    "profiles": [
        "zuck"
    ]
};

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

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

```

## MCP server setup

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