# Threads Scraper (`batscrape/threads-scraper`) Actor

Search Threads by keyword or pull any public account's latest posts. Export post text, author, likes, replies, reposts, quotes, shares, topic, mentions, links and media URLs as JSON. Date range filters, cross-query dedup, no login or cookies. Flat $1.49 per 1,000 posts.

- **URL**: https://apify.com/batscrape/threads-scraper.md
- **Developed by:** [BatScrape](https://apify.com/batscrape) (community)
- **Stats:** 5 total users, 4 monthly users, 99.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.49 / 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 Search & User Posts Scraper: Keyword Search and Account Posts with Author & Engagement Data 🧵

Search Threads by keyword, or pull the latest public posts of any account, and export unique posts as clean JSON: post text, author, likes, replies, reposts, quotes, shares, topic tag, mentions, links and media URLs, with transparent flat per-result pricing. Keyword search goes deep: the actor collects the top matching posts first, then keeps searching further back in time, day by day, until it reaches the limit you set. No Threads login, no cookies, no browser session to keep alive. Perfect for brand and mention monitoring, competitor account tracking, trend research, creator benchmarking and feeding public Threads posts into dashboards or LLM pipelines.

💰 Price: $0.0005 flat per run + $0.00149/result ($1.49 per 1,000 posts, no separate query fee) 🌏 Coverage: Threads keyword search going back day by day (up to one year), and any public account's latest posts 📊 Data: Post text, author, likes/replies/reposts/quotes/shares, topic, mentions, links, image & video URLs 🎯 Filters: Start date / end date for keyword search, per-keyword limit, overall item cap 🛡️ Flat Per-Result Pricing

### Quick Navigation

- [🧭 What Does This Actor Do?](#🧭-what-does-this-actor-do)
- [🧵 Features and Functionality](#🧵-features-and-functionality)
- [💰 Pricing: Transparent Event-Based Costs](#💰-pricing-transparent-event-based-costs)
- [🪡 Who Needs This?](#🪡-who-needs-this)
- [💬 Input Parameters](#💬-input-parameters)
- [💡 Input Strategy Guide](#💡-input-strategy-guide)
- [🔎 Example Requests](#🔎-example-requests)
- [📦 Output](#📦-output)
- [🧩 Custom Map Function](#🧩-custom-map-function)
- [🔧 Troubleshooting](#🔧-troubleshooting)
- [❔ FAQ](#❔-faq)
- [📞 Contact](#📞-contact)

### 🧭 What Does This Actor Do?

This actor collects public Threads posts in two ways, in the same run if you like: by keyword and by account. It pushes every unique post to your dataset as structured JSON, with no headless browser, no login and no cookies on your end. Give it keywords, usernames, or both; it searches each keyword back through time until one of your limits is reached, looks up each username once, removes posts that show up more than once, and respects your `maxItemsPerQuery` and `maxItems` limits and your pay-per-event budget as it goes.

**🔍 Keyword Search** — search as many keywords as you like in one run. For each keyword the actor collects the top matching posts first, then keeps searching further back in time, day by day, until it has `maxItemsPerQuery` posts (100 when left empty), reaches the start date, or stops finding new posts.

**👤 Account Posts** — pass usernames (with or without `@`) or profile URLs and get each account's most recent public posts, about 15-30 per account

**📊 Author & Engagement Included** — every post carries its author (username, verification, avatar), like / reply / repost / quote / share counts and media links at no extra charge.

### 🧵 Features and Functionality

| Feature                      | Description                                                                                 | Benefit                                                         |
| ---------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| 💰 Flat Per-Result Pricing   | One price per unique post delivered, no separate query fee                                  | Predictable cost, no surprise per-keyword or per-query fees     |
| 🔍 Deep Multi-Keyword Search | Search many keywords in one run; each one is searched back in time, day by day              | Up to hundreds of posts per keyword, not just one top list      |
| 👤 Account Posts             | Latest public posts of any account, by username or profile URL                              | Track brands, competitors and creators without a second tool    |
| 🧹 Cross-Query Dedup         | A post found more than once, by two keywords or by a keyword and a username, is stored once | Never pay twice for the same post                               |
| 🎯 Date Range & Limits       | `startDate` / `endDate` set the period, `maxItemsPerQuery` and `maxItems` set the volume    | Decide how far back and how many posts, per keyword and per run |
| 📊 Engagement & Media Data   | Likes, replies, reposts, quotes, shares, topic, mentions, links, image & video URLs         | Everything needed for analysis in one item                      |
| 🔧 Custom Map Function       | JavaScript function to reshape or enrich output on the fly                                  | Shape results to match your exact requirements                  |
| 💵 Budget-Aware              | Every result is checked against the run's charge budget before it's stored                  | A run stops cleanly instead of overspending                     |

### 💰 Pricing: Transparent Event-Based Costs

Pay-per-event pricing — no subscription, no hidden fees. A flat run fee, and one charge per unique post actually delivered.

#### Pricing Structure

| Event          | Price (USD) | Charged For                                 |
| -------------- | ----------- | ------------------------------------------- |
| 🚀 Actor Start | $0.0005     | Once per run, regardless of input           |
| 📊 Post        | $0.00149    | Each unique Threads post actually delivered |

#### 💵 Real-World Pricing Examples

| Use Case                   | Configuration                                                              | Total Cost | Breakdown                    |
| -------------------------- | -------------------------------------------------------------------------- | ---------- | ---------------------------- |
| One keyword, default limit | 1 keyword, `maxItemsPerQuery` left at its default of 100 (100 posts)       | $0.1495    | $0.0005 + (100 × $0.00149)   |
| Brand comparison           | 5 keywords, `maxItemsPerQuery: 200`, `maxItems: 1000` (1,000 unique posts) | $1.4905    | $0.0005 + (1,000 × $0.00149) |
| Deep single-keyword run    | 1 busy keyword, `maxItemsPerQuery: 0`, `maxItems: 0` (about 1,100 posts)   | $1.6395    | $0.0005 + (1,100 × $0.00149) |
| Competitor account watch   | 5 usernames, about 15 posts each (75 posts)                                | $0.1123    | $0.0005 + (75 × $0.00149)    |

Result counts vary from keyword to keyword and account to account — a rare keyword or a quiet account simply returns fewer unique posts (and costs less) than a popular one, and a keyword stops below its limit when there is nothing more to find; the rows above are illustrative at the volumes shown.

### 🪡 Who Needs This?

- **📱 Social media managers** — track what is being said about a brand or topic on Threads.
- **🏷️ Brand & PR teams** — monitor mentions and keep an eye on competitor accounts.
- **📈 Market & trend researchers** — sample public conversation around a keyword and a date range.
- **🤝 Creator & influencer agencies** — benchmark accounts by likes, replies and reposts.
- **📰 Journalists & analysts** — collect public posts from specific accounts.
- **🤖 Data teams** — feed Threads posts into dashboards or LLM pipelines.

### 💬 Input Parameters

| Field               | Description                                                                                                                                                                                                                                                                                                    | Default                                        |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| `keywords`          | Keywords to search for on Threads, one per line. For each keyword the actor collects the top matching posts first, then keeps searching further back in time, day by day, until it reaches one of your limits or runs out of posts. Results are deduplicated across keywords and usernames.                    | prefilled `["basketball"]`                     |
| `usernames`         | Threads usernames (with or without `@`) or profile URLs. Each one returns that account's most recent public posts (about 15-30).                                                                                                                                                                               | (none)                                         |
| `maxItemsPerQuery`  | Maximum posts to collect per keyword. The actor keeps searching further back in time, one day at a time, until it has this many posts, runs out of posts, or reaches the start date. Empty means `100`; `0` means no per-keyword limit (up to 5,000). Applies to keywords only, not to usernames. Minimum `0`. | prefilled `100`                                |
| `startDate`         | How far back to search: only posts published on or after this date (`YYYY-MM-DD`) are collected. When empty, the search goes back up to one year from the end date. Applies to keyword search only.                                                                                                            | (none)                                         |
| `endDate`           | Only posts published on or before this date (`YYYY-MM-DD`) are collected; the search starts here and works backwards in time. When empty, the search starts from today. Applies to keyword search only.                                                                                                        | (none)                                         |
| `maxItems`          | Overall cap on posts for the whole run, across all keywords and usernames combined. The run stops as soon as it is reached, even if a keyword has not reached its own limit yet. `0` or empty means unlimited. Minimum `0`.                                                                                    | prefilled `1000`                               |
| `customMapFunction` | `(object) => object` applied to every item. Must not be used for filtering.                                                                                                                                                                                                                                    | prefilled `(object) => { return {...object} }` |

### 💡 Input Strategy Guide

**Just want a quick sample?** Run a single keyword with `maxItems` set to 10. It is a cheap, fast check that the actor and your keyword return what you expect.

**Need a lot of posts on one topic?** Raise `maxItemsPerQuery` (or set it to `0` for no per-keyword limit) and raise `maxItems` to match, because the overall cap still applies and its prefilled value is 1,000. Use `startDate` to say how far back the search may go; without it the search may go back up to one year. A single keyword tops out at roughly 1,100-1,200 posts per run, so for more than that add related keywords (brand name, product names, common misspellings, campaign slogans) or split the period across several runs with different `startDate` / `endDate` values.

**Tracking several keywords fairly?** Keep `maxItemsPerQuery` at a moderate value such as 100 or 200. Every keyword gets its top posts before any keyword is searched further back, and the per-keyword limit keeps one busy keyword from using up the whole `maxItems` cap.

**Watching competitors or creators?** List their accounts in `usernames` and schedule the run. Each run returns only each account's most recent posts, so a regular schedule is how you build up a timeline over time.

**Mixing keywords and usernames in one run?** Go ahead. Dedup works across both, so a post found by a keyword and again on an account's profile is stored and charged once. Keep in mind that `startDate`, `endDate` and `maxItemsPerQuery` affect the keyword part only.

### 🔎 Example Requests

Go as deep as possible on one keyword since a given date, with no per-keyword limit and no overall cap:

```json
{
    "keywords": ["basketball"],
    "maxItemsPerQuery": 0,
    "startDate": "2026-08-01",
    "maxItems": 0
}
```

Search several brand keywords within a date range, up to 200 posts each (cross-keyword dedup applies automatically):

```json
{
    "keywords": ["nike", "adidas", "new balance"],
    "maxItemsPerQuery": 200,
    "startDate": "2026-09-01",
    "endDate": "2026-09-30",
    "maxItems": 600
}
```

Collect the latest posts of specific accounts, by username or profile URL:

```json
{
    "usernames": ["zuck", "@mosseri", "https://www.threads.com/@instagram"],
    "maxItems": 100
}
```

### 📦 Output

One dataset item per unique Threads post. A keyword with no matches, or an unknown or private account, simply contributes no items — it isn't stored as a placeholder row.

Captured from a real run on 2026-10-09 (`usernames: ["zuck"]`, trimmed for brevity — the media lists are cut to the first entries and signed CDN URLs are shortened with `…`):

```json
{
    "id": "3994109866205639942",
    "code": "Ddt7cL5EfUG",
    "url": "https://www.threads.com/@zuck/post/Ddt7cL5EfUG",
    "text": "Agrippa said it's time to get back to work 😎",
    "createTime": 1790355021,
    "createDate": "2026-09-25",
    "mediaType": "carousel",
    "isEdited": false,
    "isReply": false,
    "replyControl": "everyone",
    "mentions": [],
    "links": [],
    "authorMeta": {
        "id": "63055343223",
        "name": "zuck",
        "verified": true,
        "avatar": "https://scontent-ord5-2.cdninstagram.com/v/t51.82787-19/…",
        "isPrivate": false
    },
    "statistics": {
        "likeCount": 5075,
        "replyCount": 524,
        "repostCount": 266,
        "quoteCount": 48,
        "shareCount": 142
    },
    "mediaMeta": {
        "width": 2160,
        "height": 2700,
        "hasAudio": false,
        "images": [
            "https://scontent-ord5-1.cdninstagram.com/v/t51.82787-15/…",
            "https://scontent-ord5-1.cdninstagram.com/v/t51.82787-15/…"
        ],
        "videos": [
            "https://scontent-ord5-1.cdninstagram.com/o1/v/t16/f2/m84/….mp4?…"
        ]
    },
    "inputUsername": "zuck"
}
```

Fields worth knowing about:

- **`inputUsername` vs `searchKeyword` / `inputKeyword`** — these tell you which input produced the item. Posts found through `usernames` carry `inputUsername`; posts found through `keywords` carry `searchKeyword` and `inputKeyword` instead. A post is stored once, under whichever input found it first.
- **`authorMeta.name` and `authorMeta.nickname`** — `name` is the account's username. `nickname` is the display name and appears only on keyword-search results (and not on every one of them); it is never present on posts collected through `usernames`.
- **`topic`** — the Threads topic tag attached to the post. Threads has no hashtags; a post has at most one topic, and the field is left out when the post has none.
- **`mediaType`** — `text`, `image`, `video` or `carousel` (`unknown` for anything else).
- **`mediaMeta.images` / `mediaMeta.videos`** — direct media links. They are signed links that expire, so fetch the files soon after the run if you need them. For video posts, `images` holds the cover thumbnail.
- **Optional fields** — `replyToUsername` (only on replies), `topic`, `authorMeta.nickname` and `linkPreview` (`url`, `title`, `displayUrl`, `imageUrl`, only when the post has a link card) are left out of the item when they don't apply, rather than set to `null`.
- **`createTime` / `createDate`** — publish time as a Unix timestamp in seconds, and the same moment as a `YYYY-MM-DD` date (UTC).

### 🔧 Troubleshooting

#### ❓ Getting Few or No Results?

- ✅ Check your plan — runs on the free plan are limited to one request: the first page of results for one keyword or username. The log says so when the limit applies; a paid plan removes it
- ✅ Read the log — every keyword ends with a line such as `Collected 100 posts for "basketball". Stopped because the per-keyword limit of 100 was reached.`, which tells you exactly which limit applied
- ✅ Check `maxItemsPerQuery` — when left empty a keyword stops at 100 posts; raise it, or set it to `0` for no per-keyword limit
- ✅ Expect about 15-30 recent posts per account — that is the full depth available for usernames, not a partial result
- ✅ Expect less from rare keywords — the actor stops a keyword early when it keeps finding few new posts further back in time, and a busy keyword tops out at roughly 1,100-1,200 posts per run
- ✅ Check `maxItems` — the whole run stops once it is reached, even if a keyword has not reached its own limit yet
- ✅ Check the charge budget — a run that can't afford another result stops cleanly rather than erroring
- ✅ Check the username — an unknown or private account returns nothing; entries that aren't a valid username or Threads profile URL are skipped with a warning in the log
- ✅ Loosen or remove `startDate` / `endDate` — the search never goes back past the start date, so a narrow date range can leave a keyword with few matches

#### 💡 Want to Minimize Costs?

- ✅ Set `maxItems` to a hard cap matching your budget, and keep `maxItemsPerQuery` low when you only need the top posts of each keyword
- ✅ Batch related keywords and usernames into one run — cross-query dedup means overlaps are never charged twice
- ✅ Use `startDate` / `endDate` on scheduled keyword runs so each run covers a new period instead of re-collecting posts you already have

### ❔ FAQ

**Do I need a Threads or Instagram account or login to use this actor?**
No — it collects public data only, through a backend service; no login or cookies are required on your end.

**How many posts do I get per keyword or per username?**
Per keyword: up to `maxItemsPerQuery` posts, which is 100 when left empty and up to 5,000 when set to `0`. The actor collects the top matching posts first, then searches further back in time, day by day, until it reaches that limit, `maxItems`, the run's maximum charge or the start date (one year back from the end date when no start date is given), or stops finding new posts. It is a sample spread over time, not every post ever published: in practice a busy keyword yields roughly 20 posts per day searched and tops out at roughly 1,100-1,200 posts per run; in a real test on 2026-10-09 the last 14 days of one busy keyword gave 271 unique posts. Rare keywords return less. Per username: the most recent public posts, about 15-30 (15 in our real run), with no full account history.

**Can I search using hashtags?**
Threads uses topic tags rather than hashtags. Search by plain keyword, without `#`; each post's topic tag, when it has one, is returned in `topic`.

**What's included at no extra charge?**
The author block (username, verification, avatar), all engagement counts, topic, mentions, links and media URLs come with every post. Duplicates are removed for free, and unknown or private accounts cost nothing beyond the start fee.

**What is not included?**
Replies and comments under a post, view counts, follower counts and full account history are not collected. A collected post that is itself a reply is marked with `isReply`.

**Does this actor download images or videos?**
No — it returns each post's image and video URLs but does not download or store the files. The URLs are signed links that expire, so download promptly if you need the media.

### 📞 Contact

Reach out to the maintainers directly for support.

- ✉️ Email: batscrape@gmail.com
- 💬 Discord: discord.com/invite/ZRANXwhWU
- 🐦 X (Twitter): x.com/batscrape
- 🛒 Apify: apify.com/batscrape

# Actor input Schema

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

Keywords to search for on Threads, one per line. For each keyword the actor collects the top matching posts first, then keeps searching further back in time, day by day, until it reaches one of your limits or runs out of posts. Results are deduplicated across keywords and usernames, so you never pay twice for the same post.

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

Threads usernames (with or without @) or profile URLs. Each one returns that account's most recent public posts (about 15-30).

## `maxItemsPerQuery` (type: `integer`):

Maximum posts to collect per keyword. The actor keeps searching further back in time, one day at a time, until it has this many posts, runs out of posts, or reaches the start date. Empty means 100; 0 means no per-keyword limit (up to 5,000). Applies to keywords only, not to usernames.

## `startDate` (type: `string`):

Only posts published on or after this date are collected. The search works backwards in time from the end date towards this date and stops when it gets here; when empty, it can go back up to one year. A very active keyword typically reaches about two months back in one run. To go further back, run again with an earlier end date. Applies to keyword search only.

## `endDate` (type: `string`):

Only posts published on or before this date are collected; the search starts here and works backwards in time. When empty, the search starts from today. Applies to keyword search only.

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

Overall cap on posts for the whole run, across all keywords and usernames combined. The run stops as soon as it is reached, even if a keyword has not reached its own limit yet. 0 or empty means unlimited.

## `customMapFunction` (type: `string`):

A function that takes each output object and returns a transformed object. MUST NOT be used for filtering — accounts using it for filtering may be banned. Leave empty to store items unchanged.

## Actor input object example

```json
{
  "keywords": [
    "claude"
  ],
  "maxItemsPerQuery": 10,
  "maxItems": 10,
  "customMapFunction": "(object) => { return {...object} }"
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `engagement` (type: `string`):

No description

## `content` (type: `string`):

No description

## `authors` (type: `string`):

No description

## `media` (type: `string`):

No description

## `source` (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 = {
    "keywords": [
        "claude"
    ],
    "maxItemsPerQuery": 10,
    "maxItems": 10,
    "customMapFunction": (object) => { return {...object} }
};

// Run the Actor and wait for it to finish
const run = await client.actor("batscrape/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": ["claude"],
    "maxItemsPerQuery": 10,
    "maxItems": 10,
    "customMapFunction": "(object) => { return {...object} }",
}

# Run the Actor and wait for it to finish
run = client.actor("batscrape/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": [
    "claude"
  ],
  "maxItemsPerQuery": 10,
  "maxItems": 10,
  "customMapFunction": "(object) => { return {...object} }"
}' |
apify call batscrape/threads-scraper --silent --output-dataset

```

## MCP server setup

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