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

Public Threads (threads.com) profiles with followers and bio, their latest posts, and single post pages with visible replies: text, likes, replies, reposts, quotes, media. No login, no browser. $1.00 per 1,000 posts.

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

## Pricing

from $1.00 / 1,000 posts

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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: Profiles, Posts & Replies

Read public **Threads** (threads.com) profiles, their latest posts, and single post pages with their visible
replies. Plain HTTP, no login, no API key, no browser.

Each run gives you three kinds of row in one dataset:

- one **profile** row per profile: username, user id, full name, bio, bio links, follower count, verified and
  private flags, avatar and how many posts the page carried
- one **post** row per post: text, creation time, likes, reply / repost / quote / reshare counts, image and video
  URLs, link preview, author, quoted post
- one **reply** row per visible reply of a post link, with the code of the post it was rendered under

Rows are pushed one at a time as soon as they are parsed, so a run that hits its time limit still delivers
everything it collected.

### Input

```json
{
  "usernames": ["zuck", "nasa"],
  "postUrls": ["https://www.threads.com/@zuck/post/DeZtp-TkQHh"],
  "maxPostsPerProfile": 15,
  "includeReplies": true,
  "maxRepliesPerPost": 20,
  "maxItems": 200,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

`usernames` accepts `zuck`, `@zuck`, `https://www.threads.com/@zuck` and `https://www.threads.net/@zuck`.
`postUrls` accepts `https://www.threads.com/@user/post/<code>` and the `.net` form.

An entry in either list that cannot be read (a typo or another site's link) does not fail the run: it
becomes its own `error` row with your entry in `inputValue` and the reason in `error`, it is never charged, and the
remaining entries are scraped as usual. Blank lines are skipped. Only a run where every entry is unreadable fails, with a message naming
the rejected entries.

Give `usernames`, `postUrls`, or both. If you leave both out, the actor reads the profile `zuck` so that a
one-click run still returns data. Both fields are optional in the form on purpose: neither carries a default, so a
run that only sets `postUrls` never also scrapes a profile.

`maxPostsPerProfile` (0 to 50, 0 means the profile row only), `includeReplies`, `maxRepliesPerPost` (0 to 100) and
`maxItems` (1 to 10000, the cap on charged rows) bound the size of a run. The default run of the two prefilled
profiles delivers 2 profile rows and 30 post rows for about $0.034.

### Output

One real profile row, trimmed here to the fields you use most (avatar and profile picture URLs are long CDN
links):

```json
{
  "rowType": "profile",
  "username": "zuck",
  "userId": "63055343223",
  "fullName": "Mark Zuckerberg",
  "bio": "Mostly superintelligence and MMA takes",
  "bioLinks": [],
  "followers": 5750904,
  "verified": true,
  "isPrivate": false,
  "profileUrl": "https://www.threads.com/@zuck",
  "postsInPage": 33,
  "status": "ok",
  "error": null,
  "inputValue": "zuck",
  "scrapedAt": "2026-10-29T12:00:00.000Z"
}
```

One real post row from that profile:

```json
{
  "rowType": "post",
  "postCode": "DeZtp-TkQHh",
  "postId": "4006434140014576097",
  "postUrl": "https://www.threads.com/@zuck/post/DeZtp-TkQHh",
  "authorUsername": "zuck",
  "authorId": "63055343223",
  "authorVerified": true,
  "text": "Introducing Muse Wonder, our new family of frontier models with state of the art performance for personal and professional agentic work.",
  "createdAt": "2026-10-12T16:56:29.000Z",
  "likes": 2141,
  "replies": 615,
  "reposts": 170,
  "quotes": 62,
  "reshares": 148,
  "views": null,
  "isReply": false,
  "parentPostCode": null,
  "quotedPostCode": null,
  "imageUrls": ["https://scontent-iad6-1.cdninstagram.com/..."],
  "videoUrls": [],
  "linkUrl": null,
  "isPinned": false,
  "sourceProfile": "zuck",
  "sourcePostUrl": null,
  "status": "ok",
  "error": null,
  "scrapedAt": "2026-10-29T12:00:00.000Z"
}
```

A reply row from the same run has `"rowType": "reply"`, its own `postCode`, `parentPostCode` set to the post it
was rendered under, and `sourcePostUrl` set to the post link you asked for.

Every field is described in the dataset schema. `status` is `ok`, `not_found_or_login_wall`, `error` or
`skipped`; only `ok` rows are charged.

### How this compares

Free-plan list prices checked on 2026-10-29. Competitor platform usage is billed separately: see their listings.

| Actor | Price | Notes |
| --- | --- | --- |
| futurizerush/meta-threads-scraper | $2.50 per 1,000 results plus $0.02 per run start | 1,946 users in 30 days |
| apify/threads-profile-api-scraper | $5.00 per 1,000 profiles | 673 users in 30 days |
| **this actor** | $1.00 per 1,000 posts, $2.00 per 1,000 profiles, no start fee | profiles, posts and visible replies in one dataset |

### Pricing

Pay per event:

- **$0.002 per profile row** (`profile`)
- **$0.001 per post or reply row** (`post`)

There is no start fee. You are charged only after a row has been pushed, and never for error rows, missing
profiles, login walls or an empty result. Apify platform usage (compute and proxy) is billed on top.

### Limits

- About the 15 latest threads of a profile: threads.com serves one profile page with no deeper pagination, so a
  profile gives the threads in that page (each thread can hold several posts) and nothing older.
- No search, hashtag or home feed pages; profiles and single post links only.
- Public accounts only. A private or missing profile, or a login wall, is reported as
  `not_found_or_login_wall` and is not charged.
- Only the replies threads.com renders on the post page are available. There is no way to page through further
  replies.
- View counts are published for some posts only; `views` stays `null` when Threads does not print one.
- One request per profile and one per post link, retried up to 3 times with a fresh proxy session.

### Responsible use

Threads' robots.txt says automated collection needs Meta's permission. This actor reads public pages only, holds
no login and no cookies, and returns what any visitor sees. You are responsible for how you use the data:
comply with Meta's terms and with privacy law in your jurisdiction, and do not use it for mass profiling or
harassment.

# Actor input Schema

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

Public Threads profiles to read. Any of these forms works: zuck, @zuck, https://www.threads.com/@zuck, https://www.threads.net/@zuck. Each profile gives one profile row plus its latest posts. Give usernames, postUrls, or both.

## `postUrls` (type: `array`):

Threads post links such as https://www.threads.com/@zuck/post/DeZtp-TkQHh (.net links work too). Each link gives that post plus, when Include replies is on, the replies the page shows. Leave empty to scrape only the profiles above.

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

How many of a profile's latest posts to deliver (0 to 50). 0 delivers the profile row only. threads.com serves about 15 threads per profile page with no deeper pagination, so asking for more than that does not fetch older posts.

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

For each post link, also deliver the replies that the post page shows. Replies are charged as post rows. Only the replies threads.com renders are available; there are no further reply pages.

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

How many visible replies to keep for each post link (0 to 100). 0 turns replies off without changing Include replies.

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

Hard cap on charged rows in one run: profile rows plus post and reply rows (1 to 10000). The run stops cleanly when the cap is reached.

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

Apify datacenter proxy is the default and works for these pages. Direct requests (turn the proxy off) also returned data when this actor was built. A proxy failure fails the request and never falls back to a direct request.

## Actor input object example

```json
{
  "usernames": [
    "zuck",
    "@nasa",
    "https://www.threads.com/@zuck"
  ],
  "postUrls": [
    "https://www.threads.com/@zuck/post/DeZtp-TkQHh"
  ],
  "maxPostsPerProfile": 15,
  "includeReplies": true,
  "maxRepliesPerPost": 20,
  "maxItems": 200,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `rowType` (type: `string`):

profile for a profile row, post for a post row, reply for a visible reply row

## `username` (type: `string`):

Threads username without the @, e.g. zuck (profile rows)

## `userId` (type: `string`):

Threads user id (pk) of the profile, e.g. 63055343223 (profile rows)

## `fullName` (type: `string`):

Display name, e.g. Mark Zuckerberg (profile rows)

## `bio` (type: `string`):

Profile bio text as threads.com shows it (profile rows)

## `bioLinks` (type: `string`):

URLs from the profile's bio links, empty when the profile has none (profile rows)

## `followers` (type: `string`):

Follower count as an integer (profile rows)

## `verified` (type: `string`):

True when the profile carries Threads' verified badge (profile rows)

## `isPrivate` (type: `string`):

True when Threads marks the profile as private (profile rows)

## `avatarUrl` (type: `string`):

Largest profile picture URL Threads lists for the profile (profile rows)

## `profileUrl` (type: `string`):

https://www.threads.com/@username (profile rows)

## `postsInPage` (type: `string`):

How many unique posts the profile page carried before maxPostsPerProfile was applied (profile rows)

## `postCode` (type: `string`):

Short post code from the URL, e.g. DeZtp-TkQHh (post and reply rows)

## `postId` (type: `string`):

Threads media id (pk) of the post (post and reply rows)

## `postUrl` (type: `string`):

https://www.threads.com/@author/post/<code> (post and reply rows)

## `authorUsername` (type: `string`):

Username of the post's author without the @ (post and reply rows)

## `authorId` (type: `string`):

Threads user id (pk) of the post's author (post and reply rows)

## `authorVerified` (type: `string`):

True when the author carries Threads' verified badge (post and reply rows)

## `text` (type: `string`):

Post text as plain text, line breaks and emoji kept (post and reply rows)

## `createdAt` (type: `string`):

Publication time built from taken_at, ISO 8601 UTC (post and reply rows)

## `likes` (type: `string`):

Like count the page shows (post and reply rows)

## `replies` (type: `string`):

Direct reply count the page shows (post and reply rows)

## `reposts` (type: `string`):

Repost count the page shows (post and reply rows)

## `quotes` (type: `string`):

Quote count the page shows (post and reply rows)

## `reshares` (type: `string`):

Reshare count the page shows, null when it does not publish one (post and reply rows)

## `views` (type: `string`):

View count from the post page's view-count block, null when Threads does not publish one (post and reply rows)

## `isReply` (type: `string`):

True when Threads marks the post as a reply (post and reply rows)

## `parentPostCode` (type: `string`):

Code of the post this one was rendered under: the linked post for a first-level reply, the previous post in the same reply chain for a deeper reply. Null when the page does not show it (post and reply rows)

## `quotedPostCode` (type: `string`):

Code of the quoted post, null when the post quotes nothing (post and reply rows)

## `imageUrls` (type: `string`):

Best image URL of the post's photo, or one per carousel item (post and reply rows)

## `videoUrls` (type: `string`):

Video URL of the post, or one per carousel video item (post and reply rows)

## `linkUrl` (type: `string`):

Destination of the post's link preview, with the l.threads.com redirect unwrapped (post and reply rows)

## `isPinned` (type: `string`):

True when Threads marks the post as pinned to the profile or to its parent post (post and reply rows)

## `sourceProfile` (type: `string`):

Username whose page produced this row (profile and post rows)

## `sourcePostUrl` (type: `string`):

Post URL the input asked for, for rows from postUrls (post and reply rows)

## `status` (type: `string`):

ok, not_found_or_login_wall, error or skipped. Only ok rows are charged

## `error` (type: `string`):

Why the row carries no data, null on ok rows

## `inputValue` (type: `string`):

The username or link the buyer supplied for this row (profile rows)

## `scrapedAt` (type: `string`):

When this row was scraped, ISO 8601 UTC

# 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 = {
    "usernames": [
        "zuck",
        "nasa"
    ],
    "maxPostsPerProfile": 15,
    "includeReplies": true,
    "maxRepliesPerPost": 20,
    "maxItems": 200,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("subimpact/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 = {
    "usernames": [
        "zuck",
        "nasa",
    ],
    "maxPostsPerProfile": 15,
    "includeReplies": True,
    "maxRepliesPerPost": 20,
    "maxItems": 200,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("subimpact/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 '{
  "usernames": [
    "zuck",
    "nasa"
  ],
  "maxPostsPerProfile": 15,
  "includeReplies": true,
  "maxRepliesPerPost": 20,
  "maxItems": 200,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call subimpact/threads-scraper --silent --output-dataset

```

## MCP server setup

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