# Threads Scraper: Keyword Search & Profiles (No Login) (`wheaten_steelpan/threads-scraper`) Actor

Scrape Threads posts by keyword or profile without a login. Keeps only posts that contain your keyword, in your language, above a like count. No duplicate rows, limits honored, text attachments and numeric user ids included. Pay per post; duplicates and failed pages are free.

- **URL**: https://apify.com/wheaten_steelpan/threads-scraper.md
- **Developed by:** [Vanja V](https://apify.com/wheaten_steelpan) (community)
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 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?

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: Keyword Search & Profiles (No Login)

Get Threads posts by keyword or from profiles, without a Threads account or cookies. You get posts that contain your keyword, in the language you choose, above a minimum like count, and each post appears once. Every row has the text, any text attachment, likes, replies, reposts, quotes, media links, the topic tag and the author's numeric user id.

**$2.00 per 1,000 posts** ($0.002 each). No start fee. Duplicates, posts the filters drop and failed pages cost nothing.

![Output rows: text, author, likes, replies, reposts, quotes, posted time, language, topic, media type and post link for each post](https://api.apify.com/v2/key-value-stores/genxtSjxhub827red/records/output-example.png)

### What it's for

- **Social listening.** Track what people post about your brand, product or topic, in one language, without the off-topic posts Threads search mixes in.
- **Find voices in a niche.** Sort by likes and replies to see who gets traction on a keyword.
- **Watch accounts.** Pull a profile's posts, newest first, with engagement counts.
- **Look up single posts.** Paste post links to get each post's text and current counts, optionally with the first replies.
- **Scheduled monitoring.** Turn on *Only posts not returned by an earlier run* and schedule the Actor: each run returns only the posts it hasn't returned before, and the rest cost nothing.
- **Give an AI agent Threads data**, through Apify's MCP server or the API (examples below).

### Example

Input:

```json
{
    "profiles": ["nasa"],
    "postedAfter": "2026-09-01"
}
```

One output row (shortened):

```json
{
    "id": "3989264246840470169",
    "code": "DdctrJMF6qZ",
    "url": "https://www.threads.com/@nasa/post/DdctrJMF6qZ",
    "text": "Take it in: the beauty of our nearest celestial neighbor! \n\nSept. 19 is International Observe the Moon Night and another reason to look up.",
    "textAttachment": null,
    "hashtags": [],
    "mentions": [],
    "links": [],
    "topic": "Space Threads",
    "language": "en",
    "likeCount": 3891,
    "replyCount": 110,
    "repostCount": 260,
    "quoteCount": 24,
    "reshareCount": 47,
    "postedAt": "2026-09-19T00:22:58.000Z",
    "isReply": false,
    "replyToUsername": null,
    "isPinned": false,
    "quotedPost": null,
    "repostedPost": null,
    "mediaType": "image",
    "linkPreview": null,
    "authorId": "28829152388",
    "authorUsername": "nasa",
    "authorFullName": "NASA",
    "authorIsVerified": true,
    "sourceType": "profile",
    "sourceInput": "nasa",
    "searchTab": null,
    "scrapedAt": "2026-10-17T16:24:11.424Z",
    "imageUrls": [
        "https://scontent-….cdninstagram.com/…"
    ],
    "videoUrls": []
}
```

### What you get

| Field | What it is |
|---|---|
| `id`, `code`, `url` | Post id, short code and link |
| `text`, `textAttachment` | Post text and the long-form text attached to it, if any |
| `hashtags`, `mentions`, `links`, `topic` | Hashtags, mentioned usernames, links and the topic tag |
| `language` | Language detected from the text (ISO 639-1, e.g. `en`) |
| `likeCount`, `replyCount`, `repostCount`, `quoteCount`, `reshareCount` | Engagement when scraped |
| `postedAt` | When it was posted (UTC) |
| `isReply`, `replyToUsername`, `isPinned` | Reply and pin details |
| `quotedPost`, `repostedPost` | The quoted or reposted post: id, code, url, username, text |
| `mediaType`, `imageUrls`, `videoUrls`, `linkPreview` | text, image, video or carousel; media links; link card |
| `authorId`, `authorUsername`, `authorFullName`, `authorIsVerified`, `authorProfilePicUrl` | The author; `authorId` stays the same when a username changes |
| `sourceType`, `sourceInput`, `searchTab`, `scrapedAt` | Which keyword or profile found the post, and when |

A `SUMMARY` record in the run's key-value store lists every keyword tab, profile and post link with its status (ok, incomplete, no results, no posts that passed the filters, no new posts, blocked, login-required, not found, not scraped), the posts found, duplicates and filtered, and the reason when something went wrong.

### Input

- **Search keywords**: one search per keyword and result tab. A keyword can be a word, a phrase, a hashtag or a brand name, in any language.
- **Result tabs**: Threads gives each keyword two result lists, and they rarely share posts. Both are searched by default.
- **Keep only posts that contain the keyword** (on by default): every word of the keyword must appear in the post text, text attachment, topic, quoted post or link title. Case and accents are ignored, and "espresso" also matches "espressos".
- **Profiles**: usernames or profile links (`zuck`, `@zuck`, `https://www.threads.com/@zuck`). Posts come newest first.
- **Post links**: links to single posts (`https://www.threads.com/@zuck/post/DeZtp-TkQHh`; threads.net and share links work too). Each returns that post, whatever the filters.
- **Include replies** (off by default): with post links, also returns the replies shown on each post's page and the author's own follow-ups, one row each. Their `sourceInput` is the post link, so you can group them by post.
- **Language**, **Minimum likes**, **Posted after** (a date or a time ago such as `7 days`): filters for both searches and profiles.
- **Only posts not returned by an earlier run**: for scheduled runs (see below).
- **Max posts per keyword tab or profile** (default 100) and **Max posts in total**.

### Only new posts on a schedule

With *Only posts not returned by an earlier run* on, the Actor remembers which posts it returned for each keyword tab and profile, in a key-value store named `threads-scraper-seen` in your Apify account. The next run skips any post an earlier run returned for one of its keywords or profiles, at no charge, and stops paging a profile when it reaches posts it returned before. The record keeps posts for 30 days. Delete the store to start over.

### Pricing

| Posts | Price |
|---|---|
| 100 | $0.20 |
| 1,000 | $2.00 |
| 10,000 | $20.00 |

- Apify's platform usage is included, and there's no start fee.
- You pay for rows you get. A post found by two keywords or both tabs is one row, charged once. Posts the filters drop, posts returned by an earlier run (with that option on), blocked pages, login-only profiles and posts not found cost nothing. Replies (with *Include replies*) are charged as posts.
- To cap what a run can cost, set a maximum cost when you start it (`maxTotalChargeUsd` in the API). The Actor stops once the cap is reached and lists what it didn't get to in `SUMMARY` as `not-scraped`, free of charge.

### Use it through the API

Run it and get the rows back in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/wheaten_steelpan~threads-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keywords": ["espresso", "cold brew"], "language": "en", "minLikes": 5}'
```

That call waits at most 5 minutes. For bigger runs, the Apify clients start the run and wait for it to finish.

JavaScript (`npm install apify-client`):

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('wheaten_steelpan/threads-scraper').call({
    profiles: ['zuck', 'mosseri'],
    postedAfter: '30 days',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const post of items) console.log(post.authorUsername, post.likeCount, post.text);
```

Python (`pip install apify-client`):

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("wheaten_steelpan/threads-scraper").call(run_input={
    "keywords": ["your brand"],
    "onlyNewSinceLastRun": True,
})
for post in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(post["url"], post["likeCount"], post["text"])
```

### Use it from an AI agent (MCP)

Add Apify's MCP server with this Actor as its tool, for example in Claude Desktop, Cursor or VS Code:

```json
{
  "mcpServers": {
    "threads": {
      "url": "https://mcp.apify.com?tools=wheaten_steelpan/threads-scraper"
    }
  }
}
```

Clients that support OAuth sign you in to Apify on first use. Other clients can send your token in a header: `"headers": { "Authorization": "Bearer <APIFY_TOKEN>" }`.

### Speed and limits

- The Actor works on eight keyword tabs or profiles at a time, and rows appear in the dataset as each page arrives.
- Typical run times on Apify in our tests: one keyword (both tabs) about 40 seconds; 20 keywords and 50 profiles (about 2,400 posts) about 4.5 minutes.
- **Logged out, Threads shows a limited number of posts per keyword**: about 15–35 per result tab, with no further pages. To get more posts on a topic, add related keywords and phrasings, use both tabs, or schedule runs with *Only posts not returned by an earlier run*. A keyword can end below your maximum.
- Profiles page back through older posts until your maximum or the date you set.
- **Logged out, a post's page shows only its first replies**, usually 3–12, so *Include replies* doesn't return every reply on busy posts.
- Some large accounts (news outlets, celebrities) are only shown to logged-in visitors, and a username that doesn't exist looks the same. Both show as `login-required` in `SUMMARY` and cost nothing. A post link to a deleted post or a wrong code shows as `not-found`, also free.
- Threads blocks some logged-out requests. The Actor retries each page on new IP addresses; a page that stays blocked shows as `blocked` and costs nothing.
- Language detection works from the post text. Posts too short to tell (a single word or emoji) are dropped when you set a language. Indonesian is reported as Malay (`ms`).
- Image, video and profile picture links come from Meta's CDN and expire after a while; download them soon if you need them.

### Notes

- This Actor reads public Threads pages and isn't affiliated with Meta or Threads.
- Posts contain personal data (usernames, names, profile pictures, what people wrote). You're responsible for using the data lawfully, including data-protection laws such as the GDPR where they apply.
- Found a problem or need a field? Open an issue in the Issues tab.

# Actor input Schema

## `keywords` (type: `array`):

One Threads search per keyword and result tab, e.g. espresso, #booktok or a brand name. Logged out, Threads shows about 15–35 posts per keyword and tab, so add several related keywords for more posts. Each post is one row.

## `searchTabs` (type: `array`):

Threads gives each keyword two result lists that rarely share posts. Both are searched by default. In tests the default list held mostly posts from the last day, and the recent list older posts.

## `requireKeywordInText` (type: `boolean`):

Threads search also returns posts that don't mention the keyword. When on, a post is kept only if every word of the keyword appears in its text, text attachment, topic, quoted post or link title (ignoring case and accents; "espresso" also matches "espressos"). Dropped posts are free.

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

Usernames or profile links, e.g. zuck, @zuck or https://www.threads.com/@zuck. Returns the profile's posts, newest first. A few large accounts (news outlets, celebrities) are only shown to logged-in visitors; they are reported as login-required and cost nothing.

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

Links to single posts, e.g. https://www.threads.com/@zuck/post/DeZtp-TkQHh (threads.net and share links work too). Returns that post as one row. The filters below don't apply to post links. Deleted posts and wrong links are reported as not-found and cost nothing.

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

Also return the replies shown on each post's page and the author's own follow-up posts, one row each (charged like any post). Logged out, Threads shows only the first replies, usually 3–12 per post.

## `language` (type: `string`):

Keep only posts written in this language, detected from the post text. Posts too short to tell (a single word or emoji) are dropped when a language is set. Dropped posts are free.

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

Drop posts with fewer likes. 0 keeps every post.

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

Drop posts older than this date, or than a time ago such as 7 days. For profiles it also stops paging once posts are older.

## `onlyNewSinceLastRun` (type: `boolean`):

For scheduled runs: skips posts an earlier run of this Actor already returned for any of this run's keywords or profiles. Skipped posts are free. The record is kept for 30 days in a key-value store named threads-scraper-seen in your account.

## `maxPostsPerInput` (type: `integer`):

Stop each keyword tab or profile after this many posts. Keyword searches end sooner (about 15–35 posts per tab logged out); profiles page back until this limit.

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

Stop the whole run after this many posts. Leave empty for no limit beyond the per-input one and your maximum charge.

## Actor input object example

```json
{
  "keywords": [
    "espresso"
  ],
  "searchTabs": [
    "default",
    "recent"
  ],
  "requireKeywordInText": true,
  "includeReplies": false,
  "language": "any",
  "minLikes": 0,
  "onlyNewSinceLastRun": false,
  "maxPostsPerInput": 100
}
```

# Actor output Schema

## `posts` (type: `string`):

One row per post, each saved once per run. Download as CSV, JSON or Excel from the Export button.

## `summary` (type: `string`):

What happened to each keyword tab, profile and post link: ok, incomplete, no results, no posts that passed the filters, no new posts since an earlier run, blocked, login-required, not found or not scraped, with counts of posts found, duplicates and filtered, and the reason.

# 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 = {
    "keywords": [
        "espresso"
    ]
};

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

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

```

## MCP server setup

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