# Threads Search Scraper: Keywords, Hashtags, Dates (`automation_craft/threads-search-scraper`) Actor

Search Threads without a login: keyword and tag searches, top or recent posts, or one page per date window down to one hour for depth. One row per post with text, author, time, likes, replies, reposts, media, language and whether the text contains your words. JSON, CSV, API. Pay per post.

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

## Pricing

from $0.45 / 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.
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 Search Scraper: Keywords, Hashtags, Dates

**Scrape Threads search results** (threads.com, Meta's text app) without a login: keyword and tag searches, the top posts or the newest posts, or one page per date window down to one hour for depth. One row per post with the text, the author, the time, likes, replies, reposts, quotes, media, language, the post's topic tag and whether its text contains your search words. Pay per post, from $0.45 per 1,000.

Built for social listening and brand monitoring, campaign and hashtag tracking, research and AI data sets, and scheduled keyword monitors: name a memory and every later run delivers and charges only the posts it has not delivered before.

### Quick start

1. Open the Actor and type one or more words into **Search words** (coffee, running shoes, ai agents), or topic tags into **Tags** (#sourdough).
2. Pick the **Search depth**: the top posts page (the default), the recent page, both, or **Date windows** with a **Window length** of 24, 12, 6, 3 or 1 hour and a date range (the last 7 days when you give none).
3. Set the caps: posts per search (100 when left out; the Console form starts at 20) and posts per run (default 1,000).
4. Run. Rows land in the dataset as JSON, CSV, Excel or through the API; a free coverage row per search (and per window) says what was read, what passed your filters and why it stopped.
5. For a monitor: give a **Memory name** and schedule the run; each post comes once.

### What you get

One `post` row per Threads post a search found, charged once per run. Fill rates measured on 1,181 distinct posts delivered by 26 runs of the real actor's test matrix of 2026-10-07 (build 0.1.1; the later audit fixes changed no row field) (1,092 from keyword searches with the search words filter on or off, 89 from tag searches; top and recent pages, hour and day windows; `platform-results/fill-rates-r1.json`):

| Field | What it is | Filled |
|---|---|---|
| `postId`, `code`, `url` | the numeric id, the short code and the post's page | 100% |
| `text` | the full text of the post | 99.9% (a media only post has none; with the search words filter on, every keyword row has text) |
| `keywordInText` | keyword searches: `true` when the post's own text contains every search word, `false` when Threads answered it as a related post without them; `null` on tag searches | 100% of keyword rows |
| `postedAt`, `postedAtUnix` | when it was published (UTC) | 100% |
| `authorUsername`, `authorId`, `authorVerified`, `authorProfilePic`, `authorUrl` | the author | 100% |
| `authorFullName` | the author's display name | 92.2% |
| `likeCount`, `replyCount`, `repostCount`, `quoteCount` | engagement at the time of reading | 99.9% |
| `language`, `languageTag`, `languageSource`, `languageUnknown` | the language as an ISO code: Threads' own label when it has one (Chinese, Japanese and Korean posts), else a statistical classification of texts of 80 characters or more (`languageSource: "text"`); unknown and kept when neither gives one | 63.5% (6.2% Threads' own label, 57.3% classified from the text); the other 36.5% are short texts, kept with `languageUnknown: true` |
| `topicTag` | the post's own Threads topic tag (one per post at most) | 24.0% |
| `mediaType`, `hasMedia`, `imageUrl`, `images`, `videos`, `carousel` | text, image, video or carousel, with the media URLs (they expire after some days) | 57.8% carry media: an image on 57.6%, a carousel on 31.8%, a video on 9.7% |
| `linkAttachment` | the link card (url, title, image) | 1.7% |
| `isReply`, `replyToUsername`, `replyToUserId`, `rootPostAuthorUsername`, `parentPostCode` | reply context (search pages carry replies too) | 29.6% of rows are replies |
| `isQuote`, `quotedPost`, `repostedPost` | the quoted or reposted post (code, url, author, text, time) | when present |
| `source`, `sourceQuery`, `sourceWindow` | `search` or `tag`, the word or tag that found the post, and `top`, `recent` or the UTC window (start/end) | 100% |
| `viewCount` | always null here: Threads shows no view count in search answers without a login | 0% |

Free rows next to the posts: one `coverage` row per search (pages read, posts read, filtered with the reason, repeated, known to the memory, delivered, charged, the stop reason, `allPagesRead`, the windows read, failed and not read; a search is never reported complete: `complete` is always false), one `coverage` row per date window read (`window`, `status`, `postsRead`, `postsMatched`, `postsDelivered`, `pageCharged`), a `status` row for an unusable value or for searches a stopped run did not start, and one `summary` row with every counter and what was charged.

#### How deep it goes

Threads answers one page per search to a visitor without a login (about 20 to 30 posts on the top page, 10 to 20 on the recent page); in our 252 measured calls it never answered a second page. Depth comes from date windows: the same search limited to a window answers a fresh page for that window, and Threads holds the window's end to the second. Measured on 2026-10-07 for one full day of each word:

| Word | One day window | Four 6 hour windows | 24 one hour windows |
|---|---|---|---|
| nike | 19 posts | 77 distinct | 501 distinct |
| sourdough | 21 posts | 96 distinct | 563 distinct |
| apify (a rare word) | 5 posts | 4 distinct | 3 posts in two of the hours |

Windows shorter than one hour are not honoured by Threads, so one hour is the finest window offered. The window door reaches back at least 700 days. A window page is a sample: the same window read twice shared about 15 percent of its posts, so no search here is complete and none is reported as complete (`complete` is always false; `allPagesRead` says whether every planned page or window was read); the coverage rows say exactly what was read.

Window pages also carry related posts: on the measured windows 62 to 66 percent of the posts did not contain the search word (shoes and running for "nike", bread for "sourdough"), against 23 to 67 percent on the top pages of eight words. That is why the **search words filter** is on by default: a post whose own text does not contain every search word (at the start of a word, any case, any order: "nike" matches Nike, #nike and Nikes) is left out and not charged. Switch it off to keep every post Threads answers; each row still says `keywordInText` true or false. The rule is plain on purpose: lower case only, no accent folding (cafe does not match café), no stemming; an emoji only search word matches nothing, so switch the filter off for one.

### How much does it cost to scrape Threads search results?

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

| Event | Free plan | Bronze | Silver | Gold |
|---|---|---|---|---|
| Post (one row) | 0.49 | 0.48 | 0.47 | 0.45 |
| Search page (one page with at least one post that passed your filters) | 2.00 | 2.00 | 2.00 | 2.00 |
| Actor start (per run) | 0.006 per run, no discount | 0.006 | 0.006 | 0.006 |

Platinum and Diamond plans pay the Gold price. The Store pricing card shows these same prices per 1,000 events: "$0.49 / 1,000" on the Post row means one post costs $0.00049, and "$2.00 / 1,000" on the Search page row means one search page costs $0.002, a fifth of a cent.

Free: posts without your search words (with the filter on), posts outside your dates or languages, posts already delivered in the run or known to your memory, an empty page, a page whose posts all failed your filters, a word Threads has no posts for, refused calls, and every coverage, status and summary row.

Worked examples on the Free plan:

- One word, the top page, 20 posts delivered: 0.006 + 0.002 + 20 x 0.00049 = $0.0178.
- One busy word, one day in 1 hour windows, about 24 pages and 200 posts delivered: 0.006 + 0.048 + 0.098 = about $0.15.
- A daily monitor of 5 words under one memory that finds 10 new posts in total: 0.006 + 5 x 0.002 + 10 x 0.00049 = about $0.021.

How much one run reads: Threads lets one exit make about 40 to 70 search calls before it asks for a pause of 30 minutes and more; a run moves to a fresh exit up to 4 times, so one run reads at most about 200 to 300 search pages and then stops, naming every window it did not read. Split larger jobs (100 words over many hour windows) over several runs.

Work that earns nothing is bounded: a run may work 40 seconds plus 6 seconds per post and 8 seconds per search page it charged; past that, searches not yet read are skipped free with a status row. A search with no posts, a list of words Threads hides, or windows whose posts all fail your filters therefore cost you only the start fee.

### Input

| Field | What it does | Default |
|---|---|---|
| `searchQueries` | words or phrases, one per line, up to 100; a pasted Threads search URL (threads.com/search?q=...) is read as its word, or as a tag for a tag search; a profile or post address, and a mention (@name: Threads' search without a login answers none, so search the name without the @), are refused free | prefill `coffee` |
| `tags` | topic tags, with or without #, up to 100 | none |
| `searchMode` | `top`, `recent`, `top_and_recent` or `windows` | `top` |
| `windowHours` | for `windows`: `24`, `12`, `6`, `3` or `1` hours per window, on a UTC grid from midnight; at most 744 windows per search | `24` |
| `postedAfter`, `postedBefore` | YYYY-MM-DD, YYYY-MM-DDTHH:MM (UTC), today, yesterday, "7 days", "2 weeks", "1 month" (whole UTC days counted back from the end day, so "7 days" with no end reads today and the seven days before it), "6 hours" (counted back from the run's start) | for `windows`: today and the six days before it; no limit otherwise |
| `requireKeywordInText` | keep only posts whose text contains every search word (keyword searches) | on |
| `languages`, `dropUnknownLanguage` | ISO codes to keep (en, es, ja); unknown kept unless dropped | none, off |
| `minLikes`, `excludeReplies`, `onlyWithMedia` | engagement and type filters | 0, off, off |
| `maxPostsPerQuery`, `maxPosts` | caps per search and per run | 100 (the Console form starts at 20), 1,000 |
| `memoryName`, `resetMemory` | pay once per post across runs | none |
| `proxyConfiguration` | Apify datacenter proxy (one exit at a time; a run moves to a fresh exit when Threads asks it to wait, up to 4 times) | datacenter |

Alias keys are read too: `queries`, `keywords`, `searchTerms`, `hashtags`, `maxItems`, `maxResults`, `startDate`, `endDate`, `strictKeywordMatch`.

#### API examples

```bash
curl -X POST "https://api.apify.com/v2/acts/automation_craft~threads-search-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"searchQueries":["coffee"],"searchMode":"windows","windowHours":"1","postedAfter":"12 hours","maxPostsPerQuery":500}'
```

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation_craft/threads-search-scraper').call({ tags: ['sourdough'], searchMode: 'windows', postedAfter: '3 days' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((x) => x.type === 'post').length);
```

```python
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("automation_craft/threads-search-scraper").call(run_input={"searchQueries": ["ai agents"], "languages": ["en"]})
posts = [x for x in client.dataset(run["defaultDatasetId"]).iterate_items() if x["type"] == "post"]
```

### What this Actor does NOT do

- It does not log in and uses no account, no cookies of yours, no account pool and no third party data service: it reads the search threads.com shows a visitor, keeping only the anonymous cookies the site sets during a run.
- It does not page a search past the one page Threads answered in our measurements; depth comes from the recent page and from date windows, and no search or window is reported as complete.
- It does not filter by author: Threads answers its author filter for some accounts only (several large accounts gave no post for any word in our tests), so it is not offered.
- It does not deliver view counts: search answers carry none without a login.
- It does not search mentions (@name): Threads' search without a login answers none (0 posts for "@nike" where "nike" read 22 in the same run); a mention is refused free and the name without the @ finds the posts that carry it.
- It does not filter by a minimum number of replies (minimum likes, media and replies on or off are offered).
- It does not search for profiles or accounts by keyword, and does not read profiles or the replies under a post: a username or a post URL in the input makes the run stop at once, free, with a row that says so.
- It does not expand your words into plurals or variants: list each wording you want as its own search word (the search words filter already matches a word at the start of a longer one).
- It does not label short texts: Threads' own label is used when present, texts of 80 characters or more are classified statistically (the source is on every row), and the rest is marked unknown and kept unless you ask to drop it.
- It does not use residential proxies by default.

### FAQ

#### How do I search Threads posts by keyword?

Put the words into `searchQueries`, one search per line. Each search reads the top posts page Threads shows for it (or the recent page, or one page per date window), keeps the posts whose text contains every word (switch `requireKeywordInText` off to keep all) and gives one row per post with a coverage row per search.

#### Can I search Threads posts between two dates?

Yes. Give `postedAfter` and `postedBefore` (a day, a day and time in UTC, or "7 days", "6 hours") and choose `searchMode: "windows"`: the Actor reads one page per window of `windowHours` across the range, newest first, and filters every row to the range again (Threads' window start is loose). Shorter windows find more posts; one hour is the finest Threads honours.

#### Can I scrape Threads posts by hashtag or topic tag?

Yes. Put the tags into `tags` (with or without #). Tags are read on Threads' tag surface with the same modes, date windows included. Tag pages are not filtered by the search words; each row carries the post's own `topicTag` and the tag you searched in `sourceQuery`.

#### Why are some posts missing my keyword, and how do I keep them?

Threads answers related posts too: on date window pages most posts in our measurements did not contain the word. With `requireKeywordInText` on (the default) those posts are left out and not charged; switch it off to keep them, and use the `keywordInText` column to tell them apart.

#### Why do some rows have no language?

In our measurements Threads labelled only Chinese, Japanese and Korean posts (on 0 of 104 posts of five English top pages). Other posts are classified from their text when it is at least 80 characters long (`languageSource: "text"`, a statistical classification); shorter texts are `languageUnknown: true` and kept, unless `dropUnknownLanguage` is on. With a languages list the run's summary row counts the kept and the dropped unknown posts; every row carries `languageUnknown`.

#### 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

- [Meta Ad Library Scraper - All Placements, Filters](https://apify.com/automation_craft/meta-ads-library-scraper)
- [Facebook Ads Library Scraper - Page Ads, No Login](https://apify.com/automation_craft/facebook-ads-library-scraper)
- [Instagram Ads Library Scraper - Creatives, Video](https://apify.com/automation_craft/instagram-ads-library-scraper)
- [TikTok Profile Scraper: Exact Stats, No Login](https://apify.com/automation_craft/tiktok-profile-scraper)
- [Google Trends Scraper - Compare and Trending Now](https://apify.com/automation_craft/google-trends-scraper)
- [Google News Scraper: Search, Topics, Decoded URLs](https://apify.com/automation_craft/google-news-scraper)

### Changelog

- 0.1 (2026-10-07): first release.

# Changelog

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

# Actor input Schema

## `searchQueries` (type: `array`):

Keyword searches, one per line, as typed into the Threads search box: coffee, running shoes, ai agents. A pasted Threads search URL (threads.com/search?q=...) is read as its word, or as a tag for a tag search; a profile or post address and a mention (@name, which Threads' search without a login never answers) are refused, free. Each search reads the top posts page (about 20 to 30 posts), the recent page, or one page per date window (searchMode). Up to 100 per run.

## `tags` (type: `array`):

Threads tags (topics), one per line, with or without the #: sourdough, #marathontraining. Read on the tag surface of the search, the same way as a search word (top, recent or date windows). Up to 100 per run.

## `searchMode` (type: `string`):

top: the top posts page of each search (one page). recent: the newest posts page. top_and_recent: both. windows: one top page per date window of windowHours between postedAfter and postedBefore (the last 7 days when no dates are given), newest window first; the deep mode. Each page that holds at least one post passing your filters is one search page event; an empty page is free.

## `windowHours` (type: `string`):

For the windows mode: the length of each date window, on a fixed UTC grid from midnight. 24 reads one page per day; 1 reads one page per hour, the deepest Threads answers (measured on one day of a busy word: 19 posts from the day window, about 500 distinct posts from 24 hour windows). Shorter windows are not honoured by Threads. At most 744 windows per search. Default 24.

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

Keep posts from this moment on (UTC): YYYY-MM-DD, YYYY-MM-DDTHH:MM, today, yesterday, "7 days", "2 weeks", "1 month" (whole UTC days counted back from the end day) or "6 hours" (counted back from the run's start). With the windows mode this is where the first window starts.

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

Keep posts up to this moment (UTC): YYYY-MM-DD (the end of that day), YYYY-MM-DDTHH:MM, today, yesterday or "N hours". Default: the run's start.

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

Threads also answers related posts that do not contain the search word (more than half of the posts on date window pages in our measurements). On: keep only posts whose own text contains every search word, at the start of a word, in any order and any case (nike matches Nike, #nike and Nikes); the others are left out and never charged. Off: keep every post Threads answers; each row still says keywordInText true or false. Keyword searches only (tag pages are kept as Threads files them). On by default.

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

Keep only posts whose language is one of these codes: en, es, ja, pt. Threads itself labels only Chinese, Japanese and Korean posts; every other post is classified from its text here (languageSource says which; texts under 80 characters stay unknown). Unknown posts are KEPT and marked languageUnknown true unless dropUnknownLanguage is on. The run's summary row counts the kept and the dropped unknown posts.

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

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

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

Keep only posts with at least this many likes. Default 0.

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

Drop posts that are replies to another post (search pages carry some). Off by default.

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

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

## `maxPostsPerQuery` (type: `integer`):

Posts to keep per search word or tag across its pages and windows; the search stops when it is reached. Default 100 when left out (the Console form starts at 20), at most 5,000.

## `maxPosts` (type: `integer`):

The cap of post rows for the whole run, across every search and tag. Default 1,000, at most 100,000.

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

Give a name and every later run with the same name delivers and charges only posts it has not delivered before: the way to run a scheduled keyword monitor. 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 every post the named memory holds before this run, so every post counts as new again (and is charged again).

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

Apify datacenter proxy by default (one exit address at a time; a run moves to a fresh exit when Threads asks it to wait, up to 4 times). Residential proxy is not needed; use it only if your runs see refusals.

## Actor input object example

```json
{
  "searchQueries": [
    "coffee"
  ],
  "searchMode": "top",
  "requireKeywordInText": true,
  "maxPostsPerQuery": 20,
  "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 = {
    "searchQueries": [
        "coffee"
    ],
    "searchMode": "top",
    "requireKeywordInText": true,
    "maxPostsPerQuery": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/threads-search-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 = {
    "searchQueries": ["coffee"],
    "searchMode": "top",
    "requireKeywordInText": True,
    "maxPostsPerQuery": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/threads-search-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 '{
  "searchQueries": [
    "coffee"
  ],
  "searchMode": "top",
  "requireKeywordInText": true,
  "maxPostsPerQuery": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call automation_craft/threads-search-scraper --silent --output-dataset

```

## MCP server setup

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