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

Turn public Threads accounts into rows with exact followers, bio, links and verified badge, plus each recent post's likes, replies, reposts and quotes. Paste usernames, profile links or post links; empty input runs a two-profile sample. $2.50 per 1,000 rows.

- **URL**: https://apify.com/automatic/threads-profile-scraper.md
- **Developed by:** [Philip Kirkbride](https://apify.com/automatic) (community)
- **Categories:** Social media
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 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?

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 & Posts Scraper

Turn any public Threads account into clean, structured rows. Paste usernames or profile links and get each profile's name, bio, bio links, exact follower count, verified badge and profile picture, followed by the latest posts shown on the profile: text, post time, likes, replies, reposts, quotes, images and videos. Individual post links work too.

No Threads account is needed. Profiles and posts that cannot be read are listed in the run summary and are not charged.

### Use cases

- **Influencer research**: exact follower counts, bios and links for a list of creators, one row per account.
- **Brand and competitor monitoring**: schedule the same list and chart `followerCount` and each new post's `likeCount`, `replyCount` and `repostCount` over time.
- **Engagement tracking**: the latest posts with likes, replies, reposts and quotes, re-run on a schedule to watch them grow.
- **Lead lists**: names, bio links and verified badges for a set of accounts, joinable to your own records through `userId` or `username`.
- **Content research**: the latest posts, images and videos from accounts in a niche, ready to sort by engagement.
- **Campaign reporting**: put the campaign's post links in `postUrls` and keep one row per post with its current counts.

### Your first run

You do not have to fill the form to see the output. With no input the Actor reads the same two profiles the Console prefills (`nasa`, `netflix`) with three posts each, so a complete sample arrives in seconds. The run summary carries `usedExample: true` and the run status says it was the example run, so it cannot be mistaken for your own data. Replace the prefilled values to collect your own accounts.

### Bulk input

`profiles` and `postUrls` accept a list. In the Console, click **Bulk edit** in either field and paste one username, profile link or post link per line. Usernames (`zuck`), `@` handles (`@zuck`), `threads.com` and `threads.net` links, and links copied from the app with extra parameters all work; duplicates are read once. Lists of a few hundred accounts are fine: allowance is `maxItems` rows in total, up to 100,000.

### Worked costs

| What you collect | Rows | What you pay |
| --- | ---: | ---: |
| The first-run sample (2 profiles, 3 posts each) | 8 | $0.025 |
| 100 accounts, profile rows only (`includePosts: false`) | 100 | $0.255 |
| 100 accounts with about 5 posts each | ~600 | $1.505 |
| 1,000 post links | 1,000 | $2.505 |

One $0.005 run start plus $0.0025 per delivered row. Failed profiles and posts are not charged. A profile takes about a second, so long lists finish well inside the default one-hour timeout.

### Quick Start

```json
{
  "profiles": ["zuck", "nasa", "https://www.threads.com/@natgeo"],
  "postUrls": ["https://www.threads.com/@mosseri/post/DeZvGAMmu4D"],
  "maxPostsPerProfile": 25,
  "maxItems": 1000
}
```

Each profile becomes one profile row, followed by one row per recent post. Each post link becomes one post row.

### Input

| Input | Type | Default | Meaning |
| --- | --- | --- | --- |
| `profiles` | array | the two sample profiles | Threads usernames (`zuck` or `@zuck`) or profile links (`https://www.threads.com/@zuck`, `threads.net` links work too). Repeated profiles are read once. If you leave it empty, the Actor collects the same two profiles the Console prefills (`nasa`, `netflix`) and marks the run summary `usedExample: true`, so a first run still returns a sample. |
| `postUrls` | array | `[]` | Individual post links (`https://www.threads.com/@user/post/<code>`). A post already returned from its profile is not repeated. |
| `includePosts` | boolean | `true` | Add the latest posts shown on each profile. Turn off for profile rows only. |
| `maxPostsPerProfile` | integer | `25` | Cap on post rows per profile, 1 to 50. |
| `maxItems` | integer | `1000` | Total cap on rows (profiles plus posts) for the whole run, up to 100,000. |

Give at least one profile or post link, or leave both fields empty for the sample.

### Example queries

- `{"profiles": ["netflix", "nba", "espn"]}`: three brand accounts and their latest posts.
- `{"profiles": ["mosseri", "zuck"], "includePosts": false}`: profile rows only, for a long list of accounts.
- `{"postUrls": ["https://www.threads.com/@zuck/post/DeZtp-TkQHh"]}`: one post with its current counts.
- `{"profiles": ["natgeo"], "maxPostsPerProfile": 5}`: one profile and at most five posts.

### What it returns

Every row has a `kind`: `profile` or `post`. All rows share the same columns, so profiles and posts export into one sheet; columns that do not apply to a row are `null` (or an empty list).

Profile rows:

| Field | Meaning |
| --- | --- |
| `username`, `url` | The username and the profile link. |
| `fullName` | The display name. |
| `biography` | The bio text. |
| `bioLinks` | Links listed on the profile. |
| `followerCount` | Exact follower count (not rounded). |
| `isVerified` | Whether the account has the verified badge. |
| `isPrivate` | Whether the profile is marked private. |
| `profilePicUrl` | The largest available profile picture. |
| `userId` | The account's numeric ID. |
| `postsReturned` | How many post rows follow for this profile. |

Post rows:

| Field | Meaning |
| --- | --- |
| `url`, `code`, `postId` | The post link, its short code and its numeric ID. |
| `username`, `fullName`, `isVerified`, `userId` | The post's author. |
| `text` | The post text. |
| `createdAt` | When it was posted (UTC, ISO 8601). |
| `likeCount`, `replyCount`, `repostCount`, `quoteCount` | Engagement counts. `likeCount` is `null` when the author hides it. |
| `mediaType` | `text`, `image`, `video` or `carousel`. |
| `images`, `videos` | Image and video links, one per slide for carousels. Videos also carry their cover image. |
| `linkUrl` | The web link a post previews, if any. |
| `quotedPostUrl` | The post this post quotes, if any. |
| `isPinned` | Pinned to the top of the profile. |
| `isReply` | Part of a thread the author continued, or a reply. |
| `isRepost` | Shown on the profile but written by another account. |
| `profileUsername` | The profile the post was found on (`null` for rows from post links). |

Every row also carries `source` (`profile` or `post`), `input` (the value you entered), `scrapedAt` and `error` (always `null` in the dataset).

The run summary (`OUTPUT`) reports `sourcesRequested`, `usedExample` (true only for the bare-start sample), `items`, `profiles`, `posts`, `errors` and the list at `sourceErrors`.

### Output example

```jsonl
{"kind": "profile", "username": "nasa", "url": "https://www.threads.com/@nasa", "isVerified": true, "userId": "28829152388", "fullName": "NASA", "biography": "Making the seemingly impossible, possible. ✨", "bioLinks": [], "followerCount": 12785923, "isPrivate": false, "profilePicUrl": "https://scontent-hel3-1.cdninstagram.com/v/t51.82787-19/669747085_18089786557960389_922072622069351626_n.jpg", "postsReturned": 5, "postId": null, "code": null, "text": null, "createdAt": null, "likeCount": null, "replyCount": null, "repostCount": null, "quoteCount": null, "mediaType": null, "images": [], "videos": [], "linkUrl": null, "quotedPostUrl": null, "isReply": null, "isPinned": null, "isRepost": null, "profileUsername": "nasa", "source": "profile", "input": "nasa", "scrapedAt": "2026-10-20T01:10:00Z", "error": null}
{"kind": "post", "username": "nasa", "url": "https://www.threads.com/@nasa/post/Decc755lYhC", "isVerified": true, "userId": "28829152388", "fullName": "NASA", "biography": null, "bioLinks": [], "followerCount": null, "isPrivate": null, "profilePicUrl": null, "postsReturned": null, "postId": "4007205030373722178", "code": "Decc755lYhC", "text": "Spotted: views of a \"space jellyfish\" following today's launch of NASA’s SpaceX 35th commercial resupply mission to the International Space Station.", "createdAt": "2026-10-13T18:28:06Z", "likeCount": 41103, "replyCount": 592, "repostCount": 4378, "quoteCount": 282, "mediaType": "carousel", "images": ["https://scontent-hel3-1.cdninstagram.com/v/t51.82787-15/847998870_18123835888960389_7278135639222434691_n.jpg", "https://scontent-hel3-1.cdninstagram.com/v/t51.82787-15/846964916_18123835993960389_2780682481226156847_n.jpg", "https://scontent-hel3-1.cdninstagram.com/v/t51.82787-15/849508659_18123836011960389_1894048710237085880_n.jpg", "https://scontent-hel3-1.cdninstagram.com/v/t51.82787-15/845941405_18123836032960389_8401705155110598071_n.jpg"], "videos": [], "linkUrl": null, "quotedPostUrl": null, "isReply": false, "isPinned": false, "isRepost": false, "profileUsername": "nasa", "source": "profile", "input": "nasa", "scrapedAt": "2026-10-20T01:10:00Z", "error": null}
```

Image links are shortened here; real rows carry the full links.

### Failed profiles and posts

A profile that does not exist, is not publicly visible, or cannot be read is listed in the run summary (`OUTPUT.sourceErrors`) with a short code such as `NOT_FOUND`, `RATE_LIMITED` or `LOGIN_WALL`, and the run goes on with the rest. These entries are not dataset rows and are not charged. A run in which nothing at all could be read fails with a clear message.

### Limits and caveats

- Threads shows visitors only the first page of a profile, usually 4 to 10 recent posts; older history, replies tabs, follower lists and search are not included.
- Counts are read at run time and change quickly on popular posts.
- Some well-known handles have no public Threads profile under that name (for example `vogue` or `bbcnews`); they are reported as `NOT_FOUND`. Private or restricted profiles may likewise show no posts or be reported as `NOT_FOUND`.
- Media links are the ones Threads serves and expire after some days; download what you need soon after the run.
- Automated access is subject to Threads' terms; use this actor only where you have the necessary permissions and a lawful basis.

### FAQ

**Do I need a Threads or Instagram account?** No. Only public information is collected.

**What happens if I just press Start?** The Actor collects the sample profiles `nasa` and `netflix` (three posts each) and marks the run summary `usedExample: true`. It is a real run and is charged normally (about $0.025), so check the rows before adding your own list.

**Can I mix usernames, profile links and post links?** Yes. Put usernames and profile links in `profiles`, post links in `postUrls`. Duplicates are returned once.

**How many posts will I get per profile?** The posts on the first page of the profile, usually 4 to 10, capped by `maxPostsPerProfile`. Setting the cap above about 10 rarely adds more.

**Is the follower count exact?** Yes, `followerCount` is the exact number, not a rounded label like "5.7M".

### Price

**$0.0025 per result** (each profile row and each post row), plus **$0.005 per run start**. The run-start event is charged per GB of memory; the default 512 MB run is one start event. The start is charged even if the run saves no results. Profiles and posts that fail are not charged. Platform usage is included in these prices.

At $2.50 per 1,000 rows this matches the largest Threads scraper's per-row price ($2.50 per 1,000 on the Free plan) with a **$0.005 start instead of $0.02**, and it is half the platform's own Meta Threads Profile Scraper ($5.00 per 1,000 profiles). Post rows and profile rows cost the same here, so a mixed run of profiles and their posts stays at $2.50 per 1,000.

# Actor input Schema

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

Threads usernames (zuck or @zuck) or profile links (https://www.threads.com/@zuck). Each profile becomes one profile row, followed by its latest posts when posts are included.

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

Individual Threads post links (https://www.threads.com/@user/post/<code>). Each link becomes one post row.

## `includePosts` (type: `boolean`):

Add a row for each recent post shown on every profile. Turn off to collect profile rows only.

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

Cap on post rows per profile, 1 to 50. Threads shows visitors only the first page of a profile, usually 4 to 10 recent posts; a higher cap rarely adds more.

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

Total cap on dataset rows (profiles plus posts) across the whole run.

## Actor input object example

```json
{
  "profiles": [
    "zuck",
    "nasa"
  ],
  "postUrls": [],
  "includePosts": true,
  "maxPostsPerProfile": 25,
  "maxItems": 1000
}
```

# Actor output Schema

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

No description

## `summary` (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",
        "nasa"
    ],
    "postUrls": [],
    "includePosts": true,
    "maxPostsPerProfile": 25,
    "maxItems": 1000
};

// Run the Actor and wait for it to finish
const run = await client.actor("automatic/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 = {
    "profiles": [
        "zuck",
        "nasa",
    ],
    "postUrls": [],
    "includePosts": True,
    "maxPostsPerProfile": 25,
    "maxItems": 1000,
}

# Run the Actor and wait for it to finish
run = client.actor("automatic/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 '{
  "profiles": [
    "zuck",
    "nasa"
  ],
  "postUrls": [],
  "includePosts": true,
  "maxPostsPerProfile": 25,
  "maxItems": 1000
}' |
apify call automatic/threads-profile-scraper --silent --output-dataset

```

## MCP server setup

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