# Threads Profile Scraper: Followers, Bio, Posts (`automation_craft/threads-profile-scraper`) Actor

Scrape Threads profiles without a login: followers, bio, bio links, verified, profile tags and engagement from recent posts, plus the linked Instagram counts. Find profiles by a word, and get each profile's posts, replies and reposts as rows. JSON, CSV, Excel, API. Pay per profile.

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

## Pricing

from $0.49 / 1,000 profiles

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 Profile Scraper: Followers, Bio, Posts

**Scrape Threads profiles** (threads.com, Meta's text app) without a login: give usernames or profile URLs, or words to find profiles by, and get one row per profile with the follower count, bio, bio links, verified and private flags, profile picture, profile tags and engagement figures from its recent posts. Ask for more and get the profile's posts, the replies it wrote and the posts it reposted as rows of their own, and the follower and following counts of the Instagram account linked to it. Pay per profile, from $0.49 per 1,000.

Built for influencer and creator research, competitor and brand tracking, audience growth monitoring, lead research on public brand accounts, and AI data sets. Name a memory and every later run adds the follower change since the previous run to each profile and delivers only posts it has not delivered before. Rows carry what Threads shows publicly about a profile; use them under the rules that apply to you.

### Quick start

1. Open the Actor and paste Threads handles (`zuck`, `@mosseri`) or profile URLs (`https://www.threads.com/@nasa`, threads.net links work too) into **Usernames or profile URLs**, up to 1,000 a run. Or give **Find profiles by a word** (a brand, a topic, a name) and the profiles Threads' account search finds for it are read the same way.
2. Run. You get one `profile` row per profile. The Console form also asks for 5 posts per profile and the Instagram counts; through the API both are off unless you ask.
3. For posts, replies and reposts set **Posts per profile**, **Replies the profile wrote** and **Reposts by the profile** (each 0 when left out, up to 5,000), with date, likes, media and language filters if you like.
4. For a monitor: give a **Memory name** and schedule the run. Each run delivers every profile with its fresh counts and `followerCountChange`, and only the posts, replies and reposts it has not delivered before.

### What you get

One `profile` row per profile found, charged once per run, 27 fields. Fill rates measured over 340 real profiles read on 2026-10-09 (305 authors of 80 Threads search pages over 40 words, plus 36 large accounts); a run of 100 other profiles on the platform the same day gave name 95, bio 64, bio links 38, tags 22, verified 25 and an engagement sample 96 percent:

| Field | What it is | Filled |
|---|---|---|
| `userId`, `username`, `url` | the numeric Threads user id (as text), the handle and the profile page | 100% |
| `followerCount` | Threads followers at the time of reading, as a number | 100% (95% above 0; median 282) |
| `fullName` | the display name | 95.9% |
| `biography` | the bio as written | 63.5% |
| `bioLinks`, `bioLinkItems` | the links in the bio (`bioLinkItems` adds each link's title) | 31.2% |
| `profileTags`, `profileTagItems` | the topic tags on the profile (name and handle) | 25.3% |
| `verified`, `private`, `threadsBadge` | the verified badge, a private profile, the Threads badge | always set; 24.4% verified |
| `profilePic`, `profilePicHd` | the profile picture and its largest version (the URLs expire after some days) | 100% |
| `engagement` | from the profile's own recent posts on its page, pinned posts left out: `postsSampled`, `averageLikes`, `averageReplies`, `averageReposts`, `averageQuotes`, `engagementRatePercent` (their sum per post as a percent of followers), `latestPostAt`, `oldestSampledPostAt` | 95.9% of profiles have posts on the page (median 4 posts sampled); the rest have no posts |
| `instagramFollowerCount`, `instagramFollowingCount`, `instagramStatsStatus` | the follower and following counts of the Instagram account linked to the profile, when you switch **Instagram follower and following counts** on | 95 of 100 profiles of a platform run (the rest `not_available`) |
| `previousFollowerCount`, `followerCountChange`, `previousCheckedAt` | with a memory name: the followers at the previous run under that name and the change since | from the second run on |
| `sourceQuery` | the search word that found the profile, or null for a username you gave | when searched |

With **Posts per profile**, **Replies the profile wrote** or **Reposts by the profile**, `post` rows (charged once per run, and once per memory name across runs) with `code`, `url`, `postId`, `text`, `language`, `postedAt`, the author fields, `likeCount`, `replyCount`, `repostCount`, `quoteCount`, `mediaType`, `images`, `videos`, `carousel`, `linkAttachment`, `isReply`, `quotedPost` and `source`:

- `source: "profile"`: the profile's own posts, newest first (pinned posts first, as on the profile).
- `source: "profile_replies"`: a reply the profile wrote, with `repliedToPost` (code, URL, author, text and time of the post it answers; that post is context, not a row, and is never charged).
- `source: "profile_reposts"`: a post the profile reposted, as the original (its author, text and counts) with `repostedAt` and `repostedByUsername`.

Free rows next to them: one `coverage` row per profile (what each tab read, delivered and charged, and why it stopped), one per search word (`profilesFound`, `profilesAdded`, the handles), a `status` row for a handle with no public profile, a private profile, an unusable value or inputs a stopped run did not start, and one `summary` row with every counter and what was charged.

#### How deep it goes

Measured logged out on 2026-10-09: 396 profile page calls drew no refusal (10 got no answer, and a call with no answer is tried again on a fresh exit); a profile's posts come 11 a call with a cursor, read until Threads gives no next page, your cap or your date range; the replies and reposts tabs 25 a call (mosseri's replies 15 pages back to 2025-06-11 and still going; his reposts 28, back to 2023-07-04, to the end); a search word finds up to about 100 profiles in one call (95 for "nike", 100 for "coffee"), and Threads gives no further page of them.

### How much does it cost to scrape Threads profiles?

Pay per event. Prices per 1,000 events (USD):

| Event | Free plan | Bronze | Silver | Gold |
|---|---|---|---|---|
| Profile (one profile row) | 0.60 | 0.55 | 0.52 | 0.49 |
| Post (one post, reply or repost row) | 1.10 | 1.05 | 1.00 | 0.99 |
| Actor start (once per run, per GB of memory; runs use 256 MB, so one) | 0.006 per run | 0.006 per run | 0.006 per run | 0.006 per run |

Platinum and Diamond plans pay the Gold price. There is no discount on the Actor start event. The Instagram counts are included in the Profile price.

The Store pricing card shows these same prices per 1,000 events: "$0.49 / 1,000" on the Profile row means one profile costs 0.049 cents, and "$0.99 / 1,000" on the Post row means one post costs 0.099 cents.

Worked examples on the Free plan: one profile costs 0.006 + 0.0006 = $0.0066; 100 profiles cost 0.006 + 0.06 = $0.066; 10 profiles with 50 posts each cost 0.006 + 0.006 + 500 x 0.0011 = $0.562; a daily monitor of 100 profiles with no new posts costs $0.066 a run.

Free: a handle with no public profile, a profile that could not be read, a search word, filtered rows, a row already delivered in the run, posts a named memory knows, the post a reply answers, the coverage, status and summary rows, and every row of a run that cannot bill safely (a memory that could not be read or saved, a run the platform restarted).

#### The work allowance

Work that may end without a charge (handles with no profile, a search word that finds nobody, pages of posts that all fail your filters or that the memory already knows) is bounded per run: 40 seconds of such unpaid work, plus 8 seconds for each charged profile and 6 seconds for each charged post. When it is spent the run stops, says so in the summary (`stoppedBy: "work_allowance"`) and lists the inputs it did not start in a free status row. Calls that deliver charged rows never use it up.

### Input

| Field | Type | What it does |
|---|---|---|
| `usernames` | list | Threads handles (`zuck`, `@zuck`) or profile URLs (threads.com or threads.net), up to 1,000 a run. Capitals do not matter. |
| `searchProfiles` | list | Words or names to find profiles by (up to 20 a run); each word is one free search, and every profile it finds is read like a username. |
| `maxProfilesPerSearch` | integer | Profiles read per search word, in Threads' order (default 10, up to 100). |
| `includeInstagramStats` | boolean | Add the linked Instagram account's follower and following counts (one small extra call per profile, same price). Off by default. |
| `maxPostsPerProfile` | integer | The profile's own posts as rows, newest first (default 0, up to 5,000). |
| `maxRepliesPerProfile` | integer | Replies the profile wrote, newest first, each with the post it answers (default 0, up to 5,000). |
| `maxRepostsPerProfile` | integer | Posts the profile reposted, newest first (default 0, up to 5,000). |
| `postedAfter`, `postedBefore` | text | Keep posts, replies and reposts in a date range (UTC; a repost by the time it was reposted): `YYYY-MM-DD`, `YYYY-MM-DDTHH:MM`, `today`, `yesterday`, `7 days`, `6 hours`. A tab stops at its first page older than `postedAfter`. The profile row is always delivered. |
| `minLikes`, `onlyWithMedia`, `excludeReplies` | integer, boolean, boolean | Keep rows with at least this many likes, rows with media, or leave out replies inside the Posts tab. |
| `languages`, `dropUnknownLanguage` | list, boolean | Keep rows in these languages (en, es, ja); rows with no detected language are kept unless `dropUnknownLanguage` is on. |
| `maxItems` | integer | A cap of post, reply and repost rows for the whole run (profile rows are not counted; no cap when left out). |
| `memoryName`, `resetMemory` | text, boolean | A named memory this Actor keeps in your account: the follower change on each profile row, and posts, replies and reposts charged once across runs. Each run reads every tab you asked for again, newest first, to its cap, your date range or its end; rows the memory knows are free and the rows it has not delivered before are charged. Re-reading known rows is unpaid work within the work allowance, so keep the caps or a date range ("7 days") to what a monitor needs. |
| `proxyConfiguration` | object | Apify datacenter proxy. Threads answers it fully, so a residential or other Apify proxy group is not used (the run says so in its summary); your own proxy URLs are used as given. |

#### API examples

```bash
curl -X POST "https://api.apify.com/v2/acts/automation_craft~threads-profile-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"usernames": ["zuck", "mosseri", "nasa"], "includeInstagramStats": true}'
```

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation_craft/threads-profile-scraper').call({
    usernames: ['nasa'],
    maxPostsPerProfile: 50,
    maxRepliesPerProfile: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.find((x) => x.type === 'profile').followerCount, 'followers');
```

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("automation_craft/threads-profile-scraper").call(run_input={
    "searchProfiles": ["coffee roaster"],
    "maxProfilesPerSearch": 30,
    "memoryName": "coffee accounts",
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["type"] == "profile":
        print(item["username"], item["followerCount"], item["followerCountChange"])
```

### What this Actor does NOT do

- It does not read followers or following lists: they need a logged in Threads session, and this Actor reads only what Threads shows a visitor who is not logged in. It never logs in and never asks for your cookies or a Threads account.
- It does not give view counts on a profile's posts: Threads shows a view count on a post's own page only, and reading a page per post would cost more than a post row earns.
- It does not pull emails or phone numbers out of a bio: the bio is delivered as written.
- It does not read the posts, replies or reposts of a private profile: Threads closes them to a logged out visitor; the profile row (counts, bio) is delivered and marked private.
- It cannot tell a handle that never existed from a renamed one or an Instagram account that never joined Threads: Threads answers all three the same way, and the free status row says so.
- It does not search posts by keyword or hashtag, or read the replies under a post: the Threads Search Scraper and the Threads Replies Scraper of Automation Craft do (linked below).
- It does not use residential proxies: Threads answers the Apify datacenter pool, and a residential group in the input is replaced by it (the summary says so).

### FAQ

#### How do I scrape a Threads profile without logging in?

Paste the handle or the profile URL into `usernames` and run. The profile row comes from the public profile page, which Threads shows to anyone; this Actor never logs in, never asks for cookies and uses no Threads account.

#### How can I see how many followers a Threads account has?

Every profile row carries `followerCount` as a number, read at the time of the run. With a memory name, later runs add `previousFollowerCount` and `followerCountChange`, so a scheduled run tracks follower growth.

#### Can I scrape all posts from a Threads profile?

Set `maxPostsPerProfile` (up to 5,000): the posts come newest first, 11 a call, until the profile's first post, your cap or your date range. The replies the profile wrote and the posts it reposted come the same way with `maxRepliesPerProfile` and `maxRepostsPerProfile`.

#### Can I find Threads accounts by keyword?

Yes: give words in `searchProfiles`. Each word is one free search of Threads' accounts, and up to `maxProfilesPerSearch` profiles it finds (100 at most, in Threads' order) are read and charged like usernames, with the word in `sourceQuery`.

#### Why does this Actor run with limited permissions?

Least privilege: it only needs its own storages and the named memory store it creates itself, so it asks for nothing else in your account.

### More data tools by Automation Craft

- [Threads Scraper: Posts, Search, Tags, Replies](https://apify.com/automation_craft/threads-scraper)
- [Threads Search Scraper: Keywords, Hashtags, Dates](https://apify.com/automation_craft/threads-search-scraper)
- [Threads Replies Scraper: Comments of Any Post](https://apify.com/automation_craft/threads-replies-scraper)
- [TikTok Profile Scraper: Exact Stats, No Login](https://apify.com/automation_craft/tiktok-profile-scraper)
- [Telegram Channel Scraper: Posts, Views, Dates](https://apify.com/automation_craft/telegram-channel-scraper)
- [Substack Scraper: Posts, Notes and Profiles](https://apify.com/automation_craft/substack-scraper)

### Changelog

- 0.1 (2026-10-09): first release. Threads profiles by username or by search word without a login: followers, bio, links, tags, verified, private, engagement from recent posts, the linked Instagram counts as an option, the profile's posts, replies and reposts as rows, filters, a cross run memory with the follower change, free rows for misses.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation_craft/threads-profile-scraper/changelog.md

# Actor input Schema

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

Threads profiles to read, one per line: a handle (zuck or @zuck) or a profile URL (threads.com or threads.net). Capitals do not matter. Up to 1,000 per run. A handle with no public Threads profile (it does not exist, was renamed, or the account is on Instagram only) gets a free status row.

## `searchProfiles` (type: `array`):

Words or names to find profiles by, one per line (a brand, a topic, a person's name), as the Threads search for accounts finds them. Each word is one search, free; every profile it finds is read like a username and charged as a Profile. Up to 20 words per run.

## `maxProfilesPerSearch` (type: `integer`):

How many of the profiles a search word finds are read, in Threads' order. Default 10, at most 100 (Threads answers about 100 at most for a word, with no further page).

## `includeInstagramStats` (type: `boolean`):

Add the follower and following counts of the Instagram account linked to each Threads profile: one small extra call per profile, at the same Profile price (a run of many profiles takes longer). Off by default; with it on, instagramStatsStatus says ok, not_available or failed.

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

The profile's own posts to deliver, newest first (pinned posts first, as on the profile), each a Post row. Default 0 (the profile row only; the Console form starts at 5), at most 5,000.

## `maxRepliesPerProfile` (type: `integer`):

Replies this profile wrote to other posts (its Replies tab), newest first, each a Post row with the post it answers in repliedToPost (that post is context, not a row, and is never charged). Default 0, at most 5,000.

## `maxRepostsPerProfile` (type: `integer`):

Posts this profile reposted (its Reposts tab), newest first, each a Post row of the original post (its author, text and counts) with repostedAt and repostedByUsername. Default 0, at most 5,000.

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

Keep posts, replies and reposts from this moment on (UTC; a repost by the time it was reposted): YYYY-MM-DD, YYYY-MM-DDTHH:MM, today, yesterday, "7 days", "2 weeks" or "6 hours" (counted back from the run's start). The walk of a tab stops at the first page older than this. The profile row is always delivered.

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

Keep posts, replies and reposts up to this moment (UTC): YYYY-MM-DD (the end of that day), YYYY-MM-DDTHH:MM, today, yesterday or "N hours". Newer rows are read and left out free.

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

Keep only posts, replies and reposts with at least this many likes. Default 0.

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

Drop the rows of the Posts tab that are replies (a profile's own continuation of a thread). Off by default. It does not touch the Replies tab, which maxRepliesPerProfile asks for.

## `onlyWithMedia` (type: `boolean`):

Keep only posts, replies and reposts with an image, a video or a carousel. Off by default.

## `languages` (type: `array`):

Keep only posts, replies and reposts whose language is one of these codes: en, es, ja, pt. Threads itself labels only Chinese, Japanese and Korean posts; every other text is classified here (languageSource says which; texts under 80 characters stay unknown). Unknown rows are KEPT and marked languageUnknown true unless dropUnknownLanguage is on.

## `dropUnknownLanguage` (type: `boolean`):

With a languages list: drop rows that carry no language value instead of keeping them as unknown. Off by default so nothing is dropped silently.

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

The cap of post, reply and repost rows for the whole run, across every profile (profile rows are not counted). No cap when left out (the per profile numbers and your charge limit bound the run), at most 100,000.

## `memoryName` (type: `string`):

Give a name and later runs with the same name: (1) add previousFollowerCount, followerCountChange and previousCheckedAt to each profile row; (2) deliver and charge only posts, replies and reposts not delivered before under this name (each run reads the tabs again to their cap, date range or end; known rows are free and count as unpaid work within the work allowance). The profile row is delivered and charged on every run (fresh counts). The memory is a named key-value store this Actor creates in your account. Leave empty for a one off run.

## `resetMemory` (type: `boolean`):

Forget everything the named memory holds before this run: every post counts as new again (and is charged again) and no follower change is computed this run.

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

Apify datacenter proxy (one exit address at a time; a run moves to a fresh exit when Threads asks it to wait, up to 4 times). Threads answers it fully, so a residential or other Apify proxy group is not used and the run says so; your own proxy URLs are used as given.

## Actor input object example

```json
{
  "usernames": [
    "zuck",
    "mosseri"
  ],
  "includeInstagramStats": true,
  "maxPostsPerProfile": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (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 = {
    "usernames": [
        "zuck",
        "mosseri"
    ],
    "includeInstagramStats": true,
    "maxPostsPerProfile": 5,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/threads-profile-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",
        "mosseri",
    ],
    "includeInstagramStats": True,
    "maxPostsPerProfile": 5,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/threads-profile-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",
    "mosseri"
  ],
  "includeInstagramStats": true,
  "maxPostsPerProfile": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call automation_craft/threads-profile-scraper --silent --output-dataset

```

## MCP server setup

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