# Threads Scraper: Keyword Search & Exact Match (`jungle_cleric/money-venture-threads-search`) Actor

Scrape public Threads posts by keyword or exact phrase. Apply date filters, remove duplicates across queries, and skip saved posts on repeat runs. Export text, authors, dates and available engagement counts to JSON, CSV or Excel. Up to five queries; partial top-search coverage.

- **URL**: https://apify.com/jungle_cleric/money-venture-threads-search.md
- **Developed by:** [Ying Qi Mah](https://apify.com/jungle_cleric) (community)
- **Categories:** Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 saved threads 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

Use this **Threads scraper** to collect public Threads posts by keyword or exact phrase. Search up to five queries, apply a publication-date filter, and export posts to JSON, CSV, or Excel. Each result includes its post URL, author, text, dates, available engagement counts, and matching queries.

Use it to review brand mentions, track public topic discussions, or prepare a dataset for analysis. Saved history can skip posts returned by earlier runs. The same workflow works in Apify Console and through the Threads scraper API.

Try a configured example: [export Shopify mentions to CSV](https://apify.com/jungle_cleric/money-venture-threads-search/examples/export-shopify-threads-mentions) or [monitor unique brand mentions](https://apify.com/jungle_cleric/money-venture-threads-search/examples/monitor-unique-threads-brand-mentions). Replace Shopify with your own brand before running. These examples create no schedule or alerts.

### Threads scraper features and output

| Need | Supported behavior |
| --- | --- |
| Keyword search | Up to five queries in one batch |
| Exact phrase search | Case-normalized phrase matching in post text |
| Date filtering | Keep returned posts at or after a UTC timestamp |
| Duplicate removal | Combine repeated post codes across queries |
| Repeat monitoring | Skip saved post codes for up to 30 days or 5,000 entries |
| Export | Download the saved dataset as JSON, CSV, or Excel |
| Data fields | Text, author, post URL, publication time, available counts, and query attribution |

Public source coverage is partial. Some queries return no posts even when another source can find matches. Use this tool for a bounded sample of public top-search results. It does not provide complete historical or recent search.

### Threads scraper pricing

Each run costs $0.01 plus $0.002 per saved post ($2 per 1,000 posts). A run with ten saved posts costs $0.03. A run with no saved posts costs $0.01. The run fee also applies to failed runs and jobs that exit with `history_busy`. Rejected posts and skipped duplicates do not create saved-post charges.

Memory is fixed at 1 GB. The maximum output is 50 posts, so the event charge is at most $0.11 per run at these settings. Set a spending limit to stop output sooner. The minimum allowed spending limit is $0.012. Platform usage is not added as a separate customer charge for standard runs.

### How to scrape Threads posts

1. Open the Actor in Apify Console and enter one to five search queries.
2. Select a text rule. Add a date cutoff or saved history if needed.
3. Set the result caps and spending limit, then start the run.
4. Review the run summary and download the retained posts.

Start with this configuration. Set `postedAfter` to the beginning of the time window you need.

```json
{
  "queries": ["Airbnb refund", "Shopify"],
  "matchMode": "all_terms",
  "postedAfter": "2026-10-18T00:00:00Z",
  "maxResults": 30,
  "maxResultsPerQuery": 20,
  "onlyNewPosts": true,
  "stateStoreName": "my-topic-monitor"
}
```

Use the query list in the input form. Each query needs 1–200 characters. The list accepts up to five queries. Repeated queries are searched once. The tool uses one browser for the batch. API clients can also use the older `query` field for one query. Do not supply both forms.

`maxResults` caps the saved rows across the whole run, from 1 to 50. `maxResultsPerQuery` caps matching source candidates before batch and history exclusions. It also accepts 1–50. These are caps, not guaranteed counts. Query order sets priority. The run summary names queries skipped because of a work or result limit.

### Threads keyword and exact-phrase filters

- `all_terms`, the default, requires every query word in the post text. It uses whole Unicode letter-and-number words. `refund` does not match `refunded`. It does not match only an author name or translate words.
- `exact_phrase` requires the same phrase and punctuation. It normalizes case, Unicode compatibility characters, and repeated whitespace. `Airbnb refund` matches `AIRBNB  refund`, but not `Airbnb, refund`.
- `source` keeps the source's relevance choices without a text filter.

Matching evidence appears in every row. A text match does not guarantee semantic relevance. For queries whose punctuation changes the meaning, use `exact_phrase`.

`postedAfter` is an optional inclusive publication-time cutoff in Coordinated Universal Time (UTC). Use a timestamp such as `2026-10-18T00:00:00Z`. Omit it to keep all returned dates. This filter does not recover posts absent from the source. Saved output is ordered by publication time, newest first. The source search still uses its top view.

### Remove duplicate Threads posts and skip prior results

Each post code appears once across the batch. `sourceQueries` lists the searches that returned that post. `matchedQueries` lists the supplied queries that pass the text rule. In `source` mode, it lists only the searches that returned the post. `query` and `match` preserve the first discovery query and its evidence.

Set `onlyNewPosts` and reuse a dedicated `stateStoreName` for repeat runs. History applies across queries that share the store. It retains up to 5,000 saved post codes for 30 days. Old or evicted codes can appear again. Use different stores for independent workflows. New means absent from saved history, not newly published.

A shared history guard permits one cloud job at a time per store. An overlapping job exits with `history_busy` before browser work. Retry it after the active job finishes. Cloud guards expire after 120 seconds if a job stops unexpectedly. The process safety limit is 85 seconds.

History is saved after each successful output write. Concurrent cloud jobs cannot update the same history while the guard is held. An interruption between output and history writes can still return a post again. The tool does not promise exactly-once delivery. A history-write error stops further output.

### Export Threads data to JSON, CSV, or Excel

Use the Results, Download CSV, or Download Excel links after the run. JSON, CSV, and Excel contain the same saved posts. No second extraction run is needed for another format.

Each record contains its post code and ID, URL, author username and display name, text, publication time, media-presence flag, available engagement counts, query attribution, matching evidence, and collection time. Quoted posts and thread replies are not added as extra search results. Missing optional source fields remain `null` and appear in `missingFields`.

This output example uses synthetic data:

```json
{
  "postCode": "Example123",
  "postId": "123456789",
  "url": "https://www.threads.com/@example/post/Example123",
  "username": "example",
  "authorName": "Example",
  "text": "Airbnb refund arrived.",
  "publishedAt": "2026-10-25T06:00:00.000Z",
  "hasMedia": false,
  "likeCount": 0,
  "replyCount": null,
  "repostCount": null,
  "quoteCount": 0,
  "query": "Airbnb refund",
  "sort": "top",
  "scrapedAt": "2026-10-25T06:45:00.000Z",
  "sourceQueries": ["Airbnb refund"],
  "matchedQueries": ["Airbnb refund"],
  "missingFields": ["replyCount", "repostCount"],
  "match": {
    "mode": "all_terms",
    "scope": "post_text",
    "terms": ["airbnb", "refund"],
    "matchedTerms": ["airbnb", "refund"],
    "exactPhrase": true
  }
}
```

Read `RUN_SUMMARY` for per-query candidates, excluded matches, source errors, skipped queries, history exclusions, and saved rows. A failed query does not discard records from successful queries. `partial_failure` identifies such runs. Output or history-write errors fail the run. The `coverage` field always says `partial`.

`source_empty` means the source returned no candidates. `no_matching_results` means candidates failed the filters. `no_new_matching_results` means matching candidates were in history. These are valid empty outcomes. They do not prove that no matching posts exist.

Filters and duplicate checks run before dataset writes and any dataset-item charges. Browser work, history storage, and empty runs still use platform resources. The run stops at its result cap, spending limit, or work deadline. No schedule is created.

### Threads keyword search API

Use the Apify REST API to start this Threads scraper from a script or an HTTP step in an automation workflow. Set your Apify API token in `APIFY_TOKEN`.

This request allows up to ten saved posts. At the configured prices, its maximum event charge is $0.03.

```sh
curl --request POST \
  'https://api.apify.com/v2/acts/jungle_cleric~money-venture-threads-search/runs?maxTotalChargeUsd=0.03' \
  --header "Authorization: Bearer $APIFY_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{"queries":["Shopify"],"matchMode":"all_terms","maxResults":10,"maxResultsPerQuery":10}'
```

The response contains `data.id` for the run and `data.defaultDatasetId` for its output. Check `GET /v2/actor-runs/{runId}` until the run finishes. For a successful run, download `GET /v2/datasets/{datasetId}/items`. Add `?format=csv` or `?format=xlsx` for another export format. Use the same authorization header. The run summary explains any skipped queries or partial output.

#### Use the Threads scraper from Python

This standard-library example starts a run and prints its ID. It does not wait for the dataset to finish.

```python
import json
import os
import urllib.request

url = "https://api.apify.com/v2/acts/jungle_cleric~money-venture-threads-search/runs?maxTotalChargeUsd=0.03"
payload = {"queries": ["Shopify"], "maxResults": 10, "maxResultsPerQuery": 10}
request = urllib.request.Request(
    url,
    data=json.dumps(payload).encode(),
    headers={
        "Authorization": "Bearer " + os.environ["APIFY_TOKEN"],
        "Content-Type": "application/json",
    },
    method="POST",
)
with urllib.request.urlopen(request) as response:
    print(json.load(response)["data"]["id"])
```

### Threads scraper FAQ

#### Does this scraper need a Threads login?

No Threads account or cookies are required. The scraper reads public top-search results. Public access can return fewer posts than another provider, or no posts for some queries.

#### Can I search Threads for an exact phrase?

Set `matchMode` to `exact_phrase`. The saved post text must contain the phrase with the same punctuation. Case and repeated whitespace are normalized. The filter applies to returned candidates; it does not recover posts missing from the public source.

#### How do I avoid duplicate Threads results?

Post codes are combined across queries before output. Enable `onlyNewPosts` and reuse a dedicated `stateStoreName` to skip posts saved by earlier runs. History limits and interrupted-write behavior are described above.

#### Can I monitor brand mentions on Threads?

Use brand names as queries and enable saved history for repeat runs. Review the post text and `matchedQueries` to see which words matched. An Apify Schedule can start recurring runs if you configure one. This Actor creates no schedule or alerts by itself. Literal matching does not classify sentiment or intent.

#### Is the Threads scraper API the official Threads API?

This is an Apify-hosted browser scraper. Its REST API starts collection runs and exports their datasets. It is not Meta's official Threads API.

#### Can AI agents use this Threads scraper?

The Actor is public and uses limited permissions. An AI client can use the [Apify MCP server](https://docs.apify.com/mcp) to discover and run Actors. Identify this Actor as `jungle_cleric/money-venture-threads-search`. Its input schema describes the query and filter controls. Its dataset schema describes the output fields. Account authorization and spending limits still apply.

#### Does it return every recent Threads post?

No. Results are a partial public top-search sample. `postedAfter` filters publication dates and saved rows are ordered newest first. Neither behavior makes source coverage complete. The run summary lists limits and query failures.

#### Where can I report a failed search?

Use the Actor's [Issues tab](https://apify.com/jungle_cleric/money-venture-threads-search/issues). Include the query, run ID, stop reason, and expected output so the problem can be checked.

### Local development

```sh
npm ci --ignore-scripts
npm test
APIFY_DISABLE_KEYRING=1 apify run
```

Create `storage/key_value_stores/default/INPUT.json` first. Install local Chrome or the matching Playwright browser. Local storage is excluded from Git. The local history guard uses a directory under `storage/history-locks`. If a local process stops without cleanup, stop other local jobs before removing its stale guard directory.

The collector loads public structured search data without account cookies or purchased proxies. It blocks images, media, fonts, and stylesheets. Browser source changes, regional differences, pagination limits, and production costs need continued checks.

# Actor input Schema

## `queries` (type: `array`):

Enter up to five queries. Repeated queries are searched once. Query order sets search priority when a work or result limit is reached.

## `matchMode` (type: `string`):

All terms requires every query word in the post text. Exact phrase requires the same phrase and punctuation. Both ignore case and repeated whitespace. Unicode compatibility characters are normalized. Word matching has no stemming or translation. Source mode keeps the source's relevance choices. Author names are not matched.

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

Optional inclusive lower bound, such as 2026-10-18T00:00:00Z. This filters returned posts. It does not enable complete recent search.

## `onlyNewPosts` (type: `boolean`):

History covers up to 5,000 saved posts for 30 days across queries. Overlapping jobs that use the same store exit with history_busy. An interrupted output write can still return a post again. New means absent from history, not recently published.

## `stateStoreName` (type: `string`):

Required when repeat filtering is enabled. Use a dedicated name with 3–64 lowercase letters, digits, or hyphens. Start with a letter and end with a letter or digit. The same store shares history across queries. Use different stores for separate workflows.

## `maxResults` (type: `integer`):

Total saved-post cap across all queries. Later queries can be skipped when this cap is reached. The summary names skipped queries. Public search can expose only a small batch.

## `maxResultsPerQuery` (type: `integer`):

Limits matching source candidates for each query before batch and history exclusions. The run can save fewer posts than this cap.

## `maxScrolls` (type: `integer`):

Additional browser scroll attempts after the initial batch. No attempt bypasses a login requirement.

## `sort` (type: `string`):

Only top search is available. Recent-view checks did not establish fresh results. Coverage remains partial.

## Actor input object example

```json
{
  "queries": [
    "Shopify"
  ],
  "matchMode": "all_terms",
  "onlyNewPosts": false,
  "maxResults": 10,
  "maxResultsPerQuery": 10,
  "maxScrolls": 1,
  "sort": "top"
}
```

# Actor output Schema

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

No description

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

No description

## `csv` (type: `string`):

No description

## `excel` (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 = {
    "queries": [
        "Shopify"
    ],
    "maxResults": 10,
    "maxResultsPerQuery": 10,
    "maxScrolls": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("jungle_cleric/money-venture-threads-search").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 = {
    "queries": ["Shopify"],
    "maxResults": 10,
    "maxResultsPerQuery": 10,
    "maxScrolls": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("jungle_cleric/money-venture-threads-search").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 '{
  "queries": [
    "Shopify"
  ],
  "maxResults": 10,
  "maxResultsPerQuery": 10,
  "maxScrolls": 1
}' |
apify call jungle_cleric/money-venture-threads-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jungle_cleric/money-venture-threads-search"
        }
    }
}
```

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/0PrqifuPZBCUcyVjo/builds/Efy13eBAOXLTkluRn/openapi.json
