# Threads Profile Scraper | Bios & Recent Posts (`keystonelabs/threads-profile-scraper`) Actor

Collect public Threads bios, displayed follower counts and recent visible posts from usernames or profile URLs. Export creator research to JSON or CSV. Logged-out visibility limits coverage; no full-history guarantee.

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

## Pricing

$4.00 / 1,000 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.

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

### Review public profiles from a handle list

Threads Profile Scraper: Collect public Threads bios, displayed follower counts and recent visible posts from usernames or profile URLs. Export creator research to JSON or CSV. Logged-out visibility limits coverage; no full-history guarantee.

### Start with a small example

1. Open Input, switch to JSON and paste the example below. Replace the example usernames with your own research targets when ready.
2. Check the live Pricing tab and set a run spending limit.
3. Run the Actor, then open the **Workflow** output view. Inspect `username`, `fullName`, `followerCount`, `biography`, `profileUrl`, `found`.
4. Export JSON for nested data or CSV for a spreadsheet. Review a small sample before increasing the scope.

```json
{
  "usernames": [
    "nasa"
  ],
  "maxItems": 1,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "outputMode": "profile"
}
```

At the rate checked September 9, 2026, 1 saved rows cost $0.004 in Actor event fees ($4.00 per 1,000 rows). Actual returned rows determine the event fee. Taxes and other account services may be separate.

### What can I use it for?

- **Lead enrichment.** Attach Threads user IDs, display names, verified status and recent activity to a list of handles you already have from Instagram, a CRM or a creator database.
- **Influencer and creator vetting.** Check recent activity and engagement (likes, replies, reposts per post) across hundreds of accounts in one run.
- **Brand and competitor monitoring.** Poll a fixed set of profiles on a schedule and diff the newest posts to catch announcements, campaigns and replies as they land.
- **Account discovery and validation.** Find out which of your Instagram handles actually exist on Threads; handles that do not resolve come back flagged rather than silently dropped.
- **Content research.** Pull the latest posts, link previews and media from the accounts that matter to you into a spreadsheet for review.

### What data does it return?

By default each profile produces one record with its posts nested under `posts`. Switch **Output mode** to `posts` for one flat row per post with the profile's key fields attached, which is easier to filter in CSV or Excel.

| Field | Example |
|---|---|
| `username`, `userId` | `nasa`, `28829152388` |
| `fullName` | `NASA` |
| `biography` | `Making the seemingly impossible, possible.` |
| `followerCount`, `followerCountText` | `12700000`, `12.7M` — the figure Threads displays, so rounded above 10,000 |
| `threadCount` | `131` — the account's total posts as displayed |
| `bioLinks` | Always `[]` — link details are not served to logged-out visitors |
| `isVerified`, `isPrivate` | `true`, `false` |
| `profilePicUrl`, `profilePicUrlHd` | CDN image URLs |
| `profileUrl` | `https://www.threads.com/@nasa` |
| `found` | `true` — `false` when the handle has no public Threads profile |
| `postCount`, `latestPostAt` | `7`, `2026-09-03T21:06:28.000Z` |
| `morePostsRequireLogin` | `true` when Threads shows a "log in to see more" wall |
| `posts[]` | Array of post records, newest first |
| `scrapedAt`, `error` | ISO timestamp, `null` unless the profile could not be fetched |

Each post record contains:

| Field | Example |
|---|---|
| `id`, `code`, `url` | `3976157978336241783`, `DcuJp1dkSh3`, `https://www.threads.com/@nasa/post/DcuJp1dkSh3` |
| `text` | The post's text, `null` for media-only posts |
| `postedAt`, `timestamp` | `2026-08-31T03:43:09.000Z`, `1788214989` |
| `likeCount`, `replyCount`, `repostCount`, `quoteCount`, `reshareCount` | `3154`, `61`, `118`, `22`, `20` |
| `mediaType` | `text`, `image`, `video` or `carousel` |
| `imageUrls`, `videoUrls` | Largest available image per slide; MP4 URL per video (the poster frame lands in `imageUrls`) |
| `linkPreview` | `{ url, title, description, displayUrl, imageUrl }` for posts that share a link |
| `isReply`, `replyToUsername` | `true`, `nasa` for a self-thread continuation |
| `quotedPost`, `repostedPost` | `{ id, code, url, text, authorUsername }` when the post quotes or reposts another |
| `isPinned`, `isEdited`, `isPaidPartnership` | booleans |
| `accessibilityCaption` | Meta's generated alt text for images |
| `authorUsername`, `authorId` | Author of the post, normally the profile owner |

In `posts` mode every row also carries `profileUsername`, `profileUserId`, `profileFullName`, `profileFollowerCount`, `profileIsVerified` and `profileUrl`.

#### Illustrative output

The example below explains the output shape; values are illustrative and are not a live result or a promise that every field is populated.

```json
{
  "username": "nasa",
  "userId": "28829152388",
  "fullName": "NASA",
  "biography": "Making the seemingly impossible, possible. ✨",
  "bioLinks": [],
  "followerCount": 12700000,
  "followerCountText": "12.7M",
  "threadCount": 131,
  "isVerified": true,
  "isPrivate": false,
  "profilePicUrl": "https://instagram.fagc3-1.fna.fbcdn.net/v/t51.82787-19/...",
  "profileUrl": "https://www.threads.com/@nasa",
  "found": true,
  "postCount": 7,
  "latestPostAt": "2026-09-03T21:06:28.000Z",
  "morePostsRequireLogin": true,
  "posts": [
    {
      "id": "3976157978336241783",
      "code": "DcuJp1dkSh3",
      "url": "https://www.threads.com/@nasa/post/DcuJp1dkSh3",
      "text": "Main character energy unlocked! NASA astronaut Jessica Meir and @europeanspaceagency astronaut Sophie Adenot will conduct a spacewalk Tuesday, Sept. 1 ...",
      "postedAt": "2026-08-31T03:43:09.000Z",
      "likeCount": 3154,
      "replyCount": 61,
      "repostCount": 118,
      "quoteCount": 22,
      "mediaType": "image",
      "imageUrls": ["https://instagram.fagc3-2.fna.fbcdn.net/v/t51.82787-15/...jpg"],
      "videoUrls": [],
      "linkPreview": null,
      "isReply": false,
      "authorUsername": "nasa"
    }
  ],
  "scrapedAt": "2026-09-04T21:12:40.512Z",
  "error": null
}
```

### How to use it

1. Paste the handles you want under **Threads usernames or profile URLs**, one per line. `nasa`, `@nasa` and `https://www.threads.com/@nasa` are all accepted, and duplicates are removed.
2. Pick an **Output mode**: one row per profile (default) or one row per post.
3. Click **Start**. The defaults are tuned for most jobs.
4. Download the results as JSON, CSV, Excel or XML from the **Output** tab, or read them from the dataset via the API.

Leave **Max concurrency** at its default; Threads is tolerant of steady sequential traffic but not of bursts. Residential proxies are enabled by default because Threads rate-limits datacenter IP ranges with HTTP 429 responses — leave that setting as it is unless you route traffic through your own residential IPs.

#### Input

```json
{
  "usernames": ["nasa", "zuck", "mkbhd"],
  "outputMode": "profile",
  "maxItems": 100,
  "maxConcurrency": 2
}
```

### How it works

Threads server-renders the logged-out profile page with the profile record and the visitor-visible posts embedded as JSON. The actor requests each profile page once with the same navigation headers a browser sends, parses the embedded JSON blocks and walks them for the profile and post records. Nothing is executed, no session is created and no internal API is called, so there are no tokens to expire and no signatures to break.

Handles that Threads redirects to its login page do not have a public Threads profile: either the account has never joined Threads or the name does not exist. These are returned as rows with `found: false` and an explanatory `error` so your input list and output line up one-to-one. Entries that cannot be a Threads handle at all — empty lines, names over 30 characters, stray punctuation — get the same treatment with `error` set to `Not a valid Threads username or profile URL`, so nothing you pasted disappears silently. Transient empty responses are retried automatically.

### Pricing

You pay per dataset row: one row per profile in `profile` mode, one row per post in `posts` mode. Rows for handles that do not resolve are charged like any other row, since knowing that an account is not on Threads is part of the answer. There are no subscriptions or minimums, and you can cap the maximum spend of any run before you start it.

### Scope and responsible use

This actor reads only what Threads shows to any logged-out visitor on a public profile page. It does not log in, does not accept or use cookies, session tokens or credentials, and does not attempt to bypass any access control, rate limit or bot-protection challenge. Private profiles return their public header (name, verified and private flags, profile picture) and no posts. Media URLs point at Meta's CDN and are signed, so they expire after a few days; copy the files if you need them to persist. You are responsible for using the results in accordance with the laws that apply to you and the terms of the platforms involved.

### Limitations

- **Posts per profile are capped by Threads, not by this actor.** A logged-out visitor sees roughly 4 to 10 of an account's most recent posts (self-replies inside a thread count toward that number), after which Threads shows a login wall. Pagination is not available without an account and this actor does not log in, so it cannot return an account's full post history. If you need archives, this is not the right tool.
- **Follower and thread counts are approximate.** Threads no longer renders the exact profile record for logged-out visitors; the counts come from the figures Threads itself displays in the page summary (`12.7M`, `1.5K`), so they are rounded above 10,000. Bio links are not served logged-out and come back empty. Accounts with no posts are still found, with `postCount: 0`.
- View counts are not exposed on public profile pages, so they are not returned.
- Post rows include a profile owner's self-replies and quotes but not replies written by other users; a separate post-detail page would be needed for full reply trees.
- Meta changes the embedded payload from time to time. The parser matches records by their shape rather than by fixed paths, which survives most changes, but a breaking change can reduce output until the actor is updated.

### Something missing?

Open an issue on this actor's **Issues** tab with the username and the field you expected. Reproducible misses are usually fixed within days.

### Before a larger run

Billing follows output rows: profile mode returns profile rows; posts mode returns individual post rows. These are paid examples, not a free-results offer. No source-site API key is needed for the example in Console; programmatic Apify API access still requires Apify authentication.

Five internal launch smoke rounds passed on September 6, 2026. This describes small-input test coverage, not customer reviews, a service-level guarantee or a large-batch benchmark. Website behavior and data availability can change.

### Interpret empty results and errors

Check the run status and log if no records are returned. Restrictive filters, inaccessible content or a changed source page can produce an empty result; empty output does not prove there are no matching records on the source.

Billing follows saved rows. Profile mode saves one row per processed profile; posts mode saves individual post rows. An unavailable-profile row can still incur a charge. Proxy selection and run duration can affect operating costs. Do not increase concurrency or enable a more expensive proxy merely to work around an unclear result.

For a reproducible problem, open this Actor’s Issues tab with a small public input, expected behavior and relevant error text. Remove tokens and confidential information. No response-time or uptime guarantee is offered.

### Use the result in an automation

In Make or n8n, use the Apify integration to run this Actor with the same JSON input, wait for completion, then retrieve its default dataset. Route failed runs and error rows to a review step before sending valid results to your spreadsheet or CRM. Scheduling does not make these Actors emit only new records: deduplicate downstream using the source URL or record ID.

For Node.js, install the official `apify-client` package and set your own `APIFY_TOKEN` environment variable. The following example starts a paid run with a small spending limit.

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const input = {
  "usernames": [
    "nasa"
  ],
  "maxItems": 1,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "outputMode": "profile"
};
const run = await client.actor('keystonelabs/threads-profile-scraper').call(input, {
  memory: 512, timeout: 180, maxTotalChargeUsd: 0.10
});
if (run.status !== 'SUCCEEDED') throw new Error('Run did not succeed: ' + run.id);
const { items } = await client.dataset(run.defaultDatasetId).listItems({ limit: 100 });
console.log(items);
```

The first 100 rows are retrieved in this example. Use the dataset API pagination for a larger result. Keep credentials out of shared inputs and source files.

# Actor input Schema

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

One handle or profile URL per line, e.g. `nasa`, `@nasa` or `https://www.threads.com/@nasa`. Each public profile becomes one result with its recent posts. Duplicates are removed automatically.

## `outputMode` (type: `string`):

`profile` returns one row per profile with its recent posts nested under `posts`. `posts` returns one flat row per post with the profile's key fields attached, which is easier to work with in CSV or Excel.

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

Stop after this many dataset rows. In `profile` mode this is the number of profiles; in `posts` mode it is the number of posts.

## `maxConcurrency` (type: `integer`):

How many profiles to fetch in parallel. Threads tolerates a few parallel requests but not a burst, so keep this low unless you route through Apify Proxy.

## `timeoutSecs` (type: `integer`):

Give up on a profile page after this many seconds.

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

Threads rate-limits datacenter IP ranges with HTTP 429, so residential proxies are on by default and are required for reliable runs. Only switch them off if you route traffic through your own residential IPs.

## Actor input object example

```json
{
  "usernames": [
    "nasa",
    "zuck",
    "mkbhd",
    "natgeo",
    "spotify"
  ],
  "outputMode": "profile",
  "maxItems": 20,
  "maxConcurrency": 2,
  "timeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

Dataset of scraped profiles. Each item carries the profile fields plus its visible posts with text, likes, replies, reposts, timestamp and media URLs.

## `workflowJson` (type: `string`):

Selected columns for this workflow. Inspect errors and missing values before using the data.

## `workflowCsv` (type: `string`):

Download the workflow columns as CSV. Use JSON for full nested arrays.

# 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": [
        "nasa",
        "zuck",
        "mkbhd",
        "natgeo",
        "spotify"
    ],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("keystonelabs/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": [
        "nasa",
        "zuck",
        "mkbhd",
        "natgeo",
        "spotify",
    ],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("keystonelabs/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": [
    "nasa",
    "zuck",
    "mkbhd",
    "natgeo",
    "spotify"
  ],
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call keystonelabs/threads-profile-scraper --silent --output-dataset

```

## MCP server setup

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