# Threads Search Post Scraper With Engagement Analytics (`scrapio/threads-search-post-scraper`) Actor

🧵 Threads Search Post Scraper finds and extracts Threads posts & replies by keywords, hashtags, or profiles. 🔎 Captures text, author, timestamps, likes, reposts, media & links. 📊 Export CSV/JSON. 🚀 Perfect for social listening, brand monitoring & competitor analysis.

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

## Pricing

from $3.99 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### Threads Search Scraper — Extract Posts, Analytics and User Profiles

Threads Search Post Scraper With Engagement Analytics searches Threads for a keyword, hashtag, or phrase and returns every matching post as typed JSON — not just the first result — complete with author profiles, flattened like/reply/repost/quote/reshare counts, and derived `engagementRate`, `engagementVelocity`, and `percentileRank` scores computed locally with no extra requests. It also accepts a direct post URL or numeric post ID for a single-thread lookup. Unlike scraping frameworks that return raw HTML, it returns typed JSON — ready for your model, your database, or your pipeline without any parsing. This guide covers every input and output field, plus three concrete ways teams deploy it: enrichment pipelines, scheduled monitoring, and bulk dataset builds.

### 🧭 What Does Threads Search Post Scraper With Engagement Analytics Do?

This Actor runs real, paginated Threads searches — a search phrase returns every matching post found across the search's result pages, up to your cap, instead of stopping at the first match. It also accepts a full post URL (threads.com or threads.net) or a numeric post ID for a direct single-thread fetch, mixed freely with search phrases in the same run. No Threads account, login, or API key is required — everything is read from Threads' public pages.

- Real multi-result keyword/hashtag search, paginated automatically
- Direct post lookup by URL or numeric post ID
- Author profile data embedded on every post (`user` object)
- Flattened engagement counts: likes, replies, reposts, quotes, reshares, and a summed `totalEngagement`
- Derived `engagementRate`, `engagementVelocity`, and `percentileRank` — computed locally, zero extra requests
- Mentions, hashtags, and links parsed out of caption text into flat arrays
- Quote-post and repost detection with resolved source-post URLs
- Client-side filtering by author, date range, minimum engagement, and excluded keywords

### ⚡ Features & Capabilities

Capabilities split into three areas: what gets scraped, how results are filtered and enriched, and how this Actor stacks up against other Threads scrapers.

#### Core features

- **Real multi-result search** — a search-phrase target paginates the actual Threads search connection instead of returning a single post
- **Direct post/ID lookup** — `post_url`-shaped targets or a 10+ digit numeric ID fetch that thread's SSR page directly
- **Engagement analytics** — `engagementRate` (`totalEngagement` ÷ `like_count`), `engagementVelocity` (`totalEngagement` per hour since `taken_at`), and `percentileRank` (0–100 rank within that target's own result batch)
- **Entity extraction** — `mentions`, `hashtags`, `urls` arrays parsed from each post's text fragments (with a regex hashtag fallback)
- **Quote/repost resolution** — `is_quote_post` / `quoted_post_url` and `is_repost` / `reposted_post_url`
- **Search-result filters** — `fromUsername`, `afterDate`/`beforeDate`, `minEngagement`, `excludeKeywords`, applied client-side to already-fetched results
- **Tiered proxy escalation** — direct → SHADER (datacenter) → RESIDENTIAL, with automatic retry on blocks

#### How Threads Search Post Scraper With Engagement Analytics compares to other Threads scrapers

Checked on the Apify Store, 2026-08-04:

| Feature | This Actor | burbn/threads-search-scraper | logical\_scrapers/threads-post-scraper |
| --- | --- | --- | --- |
| Multi-result keyword search | ✅ paginated | ✅ paginated | ❌ URL-only input, no search |
| Direct post URL/ID lookup | ✅ | not documented | ✅ `startUrls` only |
| Derived engagement analytics (rate/velocity/percentile) | ✅ | not documented | not documented |
| Minimum-engagement / exclude-keyword filters | ✅ | not documented (from/date filters only) | not documented |
| Mentions/hashtags/links parsed to flat arrays | ✅ | not documented | not documented |
| Tiered proxy escalation described | ✅ | not documented | default Apify Proxy input only |

If your use case is feeding structured data to an LLM, the output format row is the decision-maker — HTML parsing inside an agent loop is a reliability failure mode, not a feature. Every field above is already a typed JSON primitive or array, so none of these three require a post-processing pass before the data is model-ready.

#### When another tool might suit you better

If you need a full nested reply/comment tree for a thread rather than the root post itself, a dedicated Threads replies scraper is a better fit — this Actor's direct-lookup mode fetches a single thread's SSR page, not a recursive comment crawl. If your workload is exclusively single-post lookups by URL at very high volume with no search step at all, `logical_scrapers/threads-post-scraper`'s narrower URL-only input may be a simpler fit for that one job.

#### Threads Search Post Scraper With Engagement Analytics within the Scrapio data stack

This is currently Scrapio's only Threads Actor — it covers keyword search, direct post lookup, and engagement analytics in one run rather than splitting them across separate Actors. For the same post-plus-engagement-analytics pattern on another platform, pair it with [LinkedIn Profile Posts Scraper With Engagement Analytics](../LinkedIn-Profile-Post-Scraper) or [Facebook Group Posts & Details Scraper](../Facebook-Group-Posts-And-Details-Scraper) elsewhere in the Scrapio catalog.

### Why do developers and data teams scrape Threads?

#### 🏢 Brand and social media teams

Feed `searchQueries` with your brand name, product names, or campaign hashtags, add `minEngagement` to cut noise, and get back `post_url`, `user.username`, `captionText`, `totalEngagement`, and `percentileRank` for every mention. Sort the batch by `percentileRank` to surface the handful of posts actually worth a reply or a repost, instead of scrolling a live search page by hand.

#### 📊 AI training data and RAG indexing

`captionText` is the high-information text field for RAG indexing — it's already flattened out of Threads' nested `caption` object, so it drops straight into an embedding pipeline with no parsing step. For training data, `like_count`, `direct_reply_count`, `repost_count`, `quote_count`, `reshare_count`, and the derived `engagementRate`/`engagementVelocity`/`percentileRank` are consistently typed numeric fields across every row, useful as engagement-conditioning signals for a model rather than raw counts alone.

#### 📱 Competitive and market intelligence

Track a competitor's own posts (via `fromUsername`) or a market-wide keyword/hashtag alongside it, and watch `totalEngagement` and `engagementVelocity` per post to see not just how much engagement a post got but how fast it accumulated — a launch post with a high velocity in its first hour reads differently than one that crept up over a week.

#### 🔬 Research and academic use

Public-data-only Threads datasets for social or discourse research: `hashtags` and `mentions` arrays make network- and topic-mapping straightforward without re-parsing raw text, and `taken_at` timestamps support time-series analysis of a search topic. Scope is limited to what Threads exposes on its public pages.

#### 🎥 Product and SaaS development

Build a Threads monitoring dashboard, sentiment tool, or engagement-benchmarking product on top of scheduled runs — `percentileRank` alone gives you a ready-made "top performing post" signal without building your own ranking logic.

### 🍚 Input Parameters

All parameters are optional — there is no required field in the schema. Read directly from `.actor/actor.json`:

| Parameter | Required | Type | Constraints | Description |
| --- | --- | --- | --- | --- |
| `searchQueries` | No | array | `stringList` editor | One per line: a search phrase (recommended — runs a real multi-result Threads search), a full post URL (threads.com or threads.net), or a numeric post ID (10+ digits). |
| `urls` | No | array | `stringList` editor | Legacy alias for `searchQueries`, kept so inputs built for the base Threads Search Post Scraper keep working unchanged. If both fields are filled, entries from both are combined and de-duplicated. |
| `maxItems` | No | integer | minimum `0`, prefill `20` | Cap on how many rows to save across the whole run. Leave empty or `0` for no limit. |
| `sortOrder` | No | string | enum `top` / `recent`, default `top` | Only affects search-phrase targets. `top` is Threads' default relevance ranking; `recent` returns the newest matching posts first. |
| `fromUsername` | No | string | text field | Only keep search results posted by this exact username (without `@`). Leave blank to include every author. |
| `afterDate` | No | string | date picker, absolute or relative | Only keep search results posted after this date/time. |
| `beforeDate` | No | string | date picker, absolute or relative | Only keep search results posted before this date/time. |
| `minEngagement` | No | integer | minimum `0`, prefill `0` | Drop search results whose likes + replies + reposts + quotes + reshares add up to less than this number. `0` = no minimum. |
| `excludeKeywords` | No | array | `stringList` editor | Drop any search result whose caption contains one of these words/phrases (case-insensitive). One per line. |
| `includeAnalytics` | No | boolean | default `true` | When on, every row also gets `engagementRate`, `engagementVelocity`, and `percentileRank`. Turn off for the leaner base-compatible field set only. |
| `proxyConfiguration` | No | object | proxy editor, prefill `{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}` | Routes requests through Apify Proxy. ⚠️ Threads serves an empty page to direct, un-proxied traffic from most cloud environments, so a proxy is strongly recommended for reliable results. |

Note the naming collision: the **input** field `urls` (legacy search targets) and the **output** field `urls` (links parsed out of a post's caption) are unrelated — same name, different meaning, on opposite sides of the run.

Sample input:

```json
{
  "searchQueries": ["AI startups", "https://www.threads.com/@zuck/post/AbCdEfGhIjK"],
  "sortOrder": "recent",
  "minEngagement": 25,
  "excludeKeywords": ["giveaway"],
  "maxItems": 30,
  "includeAnalytics": true,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

#### Supported URL types and input formats

Each line in `searchQueries` (or the legacy `urls`) is classified automatically:

- **Search phrase** — anything that isn't a URL or a bare number, e.g. `AI startups`. Recommended: this is the only mode that runs a real multi-result search.
- **Post URL** — must resolve to a `threads.com` or `threads.net` host, e.g. `https://www.threads.com/@zuck/post/AbCdEfGhIjK`. Any other host raises a validation error.
- **Numeric post ID** — a string of 10 or more digits, e.g. `3487216590412789`, matched with a `\d{10,}` pattern and converted internally to the post's short code.

### 📦 Output Format

Every matched or fetched post is pushed to the dataset as one JSON row, in the order it was found. There is no separate output type for analytics or author data — both are fields on the same post row.

#### Output for posts

Full row shape (values illustrative):

```json
{
  "post_url": "https://www.threads.com/@zuck/post/AbCdEfGhIjK",
  "pk": 3487216590412789,
  "id": "3487216590412789_7841022",
  "code": "AbCdEfGhIjK",
  "user": {
    "id": "7841022",
    "pk": 7841022,
    "username": "zuck",
    "full_name": "Mark Zuckerberg",
    "profile_pic_url": "https://scontent.cdninstagram.com/...",
    "is_verified": true,
    "friendship_status": { "muting": false, "following": false, "followed_by": false, "outgoing_request": null, "blocking": null },
    "text_app_last_visited_time": 0,
    "transparency_label": null,
    "transparency_product": null,
    "transparency_product_enabled": false,
    "text_post_app_is_private": false,
    "has_onboarded_to_text_post_app": true
  },
  "caption": { "text": "Excited to share our latest AI research... #ai" },
  "captionText": "Excited to share our latest AI research... #ai",
  "caption_is_edited": false,
  "caption_add_on": null,
  "media_type": 1,
  "image_versions2": { "candidates": [{ "url": "https://scontent.cdninstagram.com/..." }] },
  "video_versions": null,
  "carousel_media": null,
  "has_audio": false,
  "audio": null,
  "accessibility_caption": null,
  "usertags": null,
  "original_height": 1080,
  "original_width": 1080,
  "transcription_data": null,
  "media_overlay_info": null,
  "like_count": 1520,
  "direct_reply_count": 87,
  "repost_count": 42,
  "quote_count": 12,
  "reshare_count": 5,
  "totalEngagement": 1666,
  "engagementRate": 1.0961,
  "engagementVelocity": 138.8,
  "percentileRank": 92.5,
  "mentions": [],
  "hashtags": ["ai"],
  "urls": [],
  "is_quote_post": false,
  "quoted_post_url": null,
  "is_repost": false,
  "reposted_post_url": null,
  "taken_at": 1769875200,
  "scrapedAt": "2026-08-04T09:15:22.104Z",
  "is_paid_partnership": false,
  "has_liked": false,
  "like_and_view_counts_disabled": false,
  "canonical_url": null,
  "giphy_media_info": null,
  "metaPlace": null,
  "meta_place": null,
  "gen_ai_detection_method": { "detection_method": "NONE" },
  "sharing_friction_info": { "should_have_sharing_friction": false, "sharing_friction_payload": null },
  "organic_tracking_token": null,
  "logging_info_token": null,
  "__token": null,
  "text_post_app_info": { "id": "...", "direct_reply_count": 87, "repost_count": 42, "quote_count": 12, "reshare_count": 5, "share_info": { "reposted_post": null, "quoted_post": null }, "text_fragments": { "fragments": [] } }
}
```

Every key above is written by the row-building code (`src/extract_posts.py` + `src/enrich.py`). Grouped reference:

| Group | Fields |
| --- | --- |
| Identity | `post_url`, `pk`, `id`, `code` |
| Author | `user` (nested object — see below) |
| Content | `caption`, `captionText`, `caption_is_edited`, `caption_add_on` |
| Media | `media_type`, `image_versions2`, `video_versions`, `carousel_media`, `has_audio`, `audio`, `accessibility_caption`, `usertags`, `original_height`, `original_width`, `transcription_data`, `media_overlay_info` |
| Engagement counts | `like_count`, `direct_reply_count`, `repost_count`, `quote_count`, `reshare_count`, `totalEngagement` |
| Engagement analytics (this Actor's own feature, gated by `includeAnalytics`) | `engagementRate`, `engagementVelocity`, `percentileRank` |
| Extracted entities | `mentions`, `hashtags`, `urls` |
| Quote/repost | `is_quote_post`, `quoted_post_url`, `is_repost`, `reposted_post_url` |
| Timestamps | `taken_at`, `scrapedAt` |
| Raw Threads metadata, preserved unmodified | `text_post_app_info`, `is_paid_partnership`, `has_liked`, `like_and_view_counts_disabled`, `canonical_url`, `giphy_media_info`, `metaPlace`, `meta_place`, `gen_ai_detection_method`, `sharing_friction_info`, `organic_tracking_token`, `logging_info_token`, `__token` |

The default dataset view surfaces a 22-column subset of the above (`post_url`, `pk`, `user`, `captionText`, `like_count`, `direct_reply_count`, `repost_count`, `quote_count`, `reshare_count`, `totalEngagement`, `engagementRate`, `engagementVelocity`, `percentileRank`, `hashtags`, `mentions`, `urls`, `is_quote_post`, `quoted_post_url`, `is_repost`, `reposted_post_url`, `taken_at`, `scrapedAt`) for readability — every field in the table above is still present in the raw dataset item.

#### Output for engagement analytics

The same row's analytics fields, computed by `src/enrich.py`, in isolation:

```json
{
  "totalEngagement": 1666,
  "engagementRate": 1.0961,
  "engagementVelocity": 138.8,
  "percentileRank": 92.5
}
```

- `totalEngagement` — `like_count + direct_reply_count + repost_count + quote_count + reshare_count`.
- `engagementRate` — `totalEngagement ÷ like_count`. `null` when `like_count` is `0` (undefined ratio, never reported as a fake `0`).
- `engagementVelocity` — `totalEngagement` per hour since `taken_at`, floored at a 1-minute divisor so a just-posted item doesn't return an inflated number. `null` when `taken_at` is missing.
- `percentileRank` — 0–100 rank of this post's `totalEngagement` within its own target's result batch (average-rank method for ties). Only present when `includeAnalytics` is `true`; not comparable across different search queries or targets in the same run, since each target's batch is ranked independently.

#### Output for author profiles

The `user` object embedded on every row:

```json
{
  "id": "7841022",
  "pk": 7841022,
  "username": "zuck",
  "full_name": "Mark Zuckerberg",
  "profile_pic_url": "https://scontent.cdninstagram.com/...",
  "is_verified": true,
  "friendship_status": { "muting": false, "following": false, "followed_by": false, "outgoing_request": null, "blocking": null },
  "text_app_last_visited_time": 0,
  "transparency_label": null,
  "transparency_product": null,
  "transparency_product_enabled": false,
  "text_post_app_is_private": false,
  "has_onboarded_to_text_post_app": true
}
```

#### Schema stability and export options

Field names stay stable across runs — `src/extract_posts.py` walks Threads' SSR JSON with a fixed field order and preserves any unrecognized keys rather than dropping them, so a front-end rename upstream adds a field instead of breaking one your pipeline already depends on. Datasets export from the Apify Console or API to JSON, CSV, Excel, XML, RSS, or HTML table — standard Apify dataset export formats, not a feature specific to this Actor.

### 💡 Threads Search Post Scraper With Engagement Analytics Strategy Guide

#### 🎯 Strategy 1: Real-time enrichment pipeline

Trigger a run whenever a new lead, mention, or campaign hashtag needs checking: pass it as a `searchQueries` entry, run the Actor, then read `totalEngagement`, `engagementRate`, `engagementVelocity`, and `percentileRank` off each returned row keyed by `post_url`. Append those fields to the matching record in your CRM, spreadsheet, or database — the row is already typed JSON, so no parsing step sits between the Actor and your destination table.

#### 🎯 Strategy 2: Scheduled monitoring and alerting

Use an Apify Schedule to re-run the same `searchQueries` (brand name, hashtag, or competitor handle) every few hours. Diff each run's dataset against the previous one on `post_url` to find new posts, and alert on the ones where `percentileRank` or `engagementVelocity` crosses a threshold you set — an Apify webhook on the run-succeeded event can push that alert to Slack or an HTTP endpoint without polling.

#### 🎯 Strategy 3: Bulk dataset build

For a research or training corpus, list your search phrases or hashtags one per line in `searchQueries` and set `maxItems` per run. Targets inside one run are processed one at a time, not in parallel — for a large batch, split queries across several concurrent Actor runs (launched via the Apify API) rather than listing hundreds of queries in a single run. Aggregate each run's dataset export to CSV or a database.

#### Strategy comparison at a glance

| Strategy | Best for | Run pattern | Output format |
| --- | --- | --- | --- |
| Real-time enrichment | Lead/mention enrichment | Triggered run, small target list | JSON row appended to an external system |
| Scheduled monitoring | Ongoing brand/competitor tracking | Apify Schedule, recurring run + webhook | Dataset diffed run-over-run |
| Bulk dataset build | Research/training corpora | Multiple concurrent runs, smaller lists each | Dataset export to CSV/JSON |

### 🌴 Related Threads Scrapers & Tools

| Scraper | What it extracts |
| --- | --- |
| LinkedIn Profile Posts Scraper With Engagement Analytics | LinkedIn profile posts with the same engagement-analytics pattern, on LinkedIn |
| Facebook Group Posts & Details Scraper | Facebook group posts and group metadata |
| X (Twitter) Tweets & Profiles Scraper | Tweets and profile data, for cross-platform post monitoring |
| TikTok Data Scraper: Comments, Replies & AI Sentiment | TikTok comments and replies with sentiment scoring |
| Reddit Subreddit Members Scraper With User Profiles | Subreddit member lists and user profile cards, for adjacent community research |

This is currently the only Threads Actor in the Scrapio catalog — it covers keyword search, direct post lookup, and engagement analytics together rather than splitting them across separate listings.

### How to integrate Threads Search Post Scraper With Engagement Analytics with your stack

Threads Search Post Scraper With Engagement Analytics works with any language or tool that can make an HTTP request — it runs on Apify, so the Apify API and the official `apify-client` libraries are the fastest path in.

#### Python

```python
from apify_client import ApifyClient
import csv

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run_input = {
    "searchQueries": ["AI startups", "SaaS launch"],
    "sortOrder": "recent",
    "minEngagement": 10,
    "maxItems": 50,
    "includeAnalytics": True,
    "proxyConfiguration": {"useApifyProxy": True},
}

run = client.actor("<YOUR_USERNAME>/threads-search-post-scraper-with-engagement-analytics").call(run_input=run_input)

rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())

fields = ["post_url", "captionText", "totalEngagement", "engagementRate", "percentileRank"]
with open("threads_posts.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=fields)
    writer.writeheader()
    for row in rows:
        writer.writerow({k: row.get(k) for k in fields})
```

#### Node.js

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });

const input = {
    searchQueries: ['AI startups', 'SaaS launch'],
    sortOrder: 'recent',
    minEngagement: 10,
    maxItems: 50,
    includeAnalytics: true,
};

const run = await client.actor('<YOUR_USERNAME>/threads-search-post-scraper-with-engagement-analytics').call(input);
const { items } = await client.dataset(run.defaultDatasetId).listItems();

for (const post of items) {
    console.log(post.post_url, post.totalEngagement, post.engagementRate);
}
```

#### Async and scheduled pipelines

For fire-and-forget large jobs, start the run via the API and let it finish asynchronously rather than blocking on `.call()`; attach an Apify webhook on the run-succeeded event to push a notification or trigger a downstream job once the dataset is ready. For recurring jobs, an Apify Schedule re-runs the same input on a cron-style interval without any code on your side.

### 🎯 Who Needs Threads Search Post Scraper With Engagement Analytics? (Use Cases & Industries)

#### 🏢 Brand and social media managers

Search your brand name or campaign hashtag, sort the results by `percentileRank`, and reply to or amplify the posts already outperforming the rest of the batch — instead of manually scrolling Threads' own search results.

#### 📊 AI/ML and data teams

Pull `captionText`, `hashtags`, and the engagement fields as consistently typed inputs for RAG indexing or as engagement-conditioning signals in a training set, with no HTML parsing step.

#### 📱 Competitive intelligence analysts

Track a competitor's `fromUsername` alongside a market keyword, and compare `engagementVelocity` across both to see whose posts are gaining traction faster, not just which has more raw likes.

#### 🔬 Researchers

Build a public-data-only dataset of a search topic on Threads for social or discourse research, using `hashtags`/`mentions` for network mapping and `taken_at` for time-series analysis.

#### 🎥 Product and SaaS builders

Build a Threads engagement dashboard, sentiment tool, or benchmarking product on scheduled runs, using `percentileRank` as a ready-made "top performer" signal instead of writing your own ranking logic.

### Is it legal to scrape Threads?

Yes — scraping publicly accessible data is generally lawful in the US; the Ninth Circuit held in *hiQ Labs v. LinkedIn* (9th Cir. 2019) that accessing public web data does not violate the Computer Fraud and Abuse Act. That precedent covers accessing public data, not every downstream use of it. Scraping may still violate Threads' own Terms of Service, which is a civil contract matter between you and Meta, not a criminal one — running this Actor does not create legal risk by itself, but commercial reuse of the data might, depending on your jurisdiction and use case.

Because Threads posts carry personal data — usernames, full names, profile pictures, and verification status of identifiable individuals — GDPR (EU/UK) and CCPA (California) considerations attach to storing and using that data, separately from the scraping-legality question above. Threads Search Post Scraper With Engagement Analytics returns only publicly accessible data. What you do with that data is your responsibility — consult legal counsel for commercial applications involving personal data.

### ❓ Frequently asked questions

#### Does Threads Search Post Scraper With Engagement Analytics work without a Threads account?

Yes. No Threads login, session cookie, or API key is required — every field is read from Threads' public pages and public search/GraphQL endpoints.

#### How does it handle Threads' anti-scraping measures?

It rotates a small pool of user agents, retries transient HTTP errors (429/502/503/504) with exponential backoff and jitter, and escalates through a tiered proxy path — direct (or SHADER datacenter, if proxy is enabled) → SHADER → RESIDENTIAL — sticking to the higher tier once escalated. ⚠️ Threads returns an empty page to un-proxied requests from most cloud environments, so leaving `proxyConfiguration` off is a common cause of empty runs.

#### Can I run it at scale without getting blocked?

There is no published uptime or success-rate figure. What is documented in the code: search-phrase pagination stops after 30 pages or two consecutive empty pages, whichever comes first; targets within a single run are processed one at a time, not in parallel; and residential-proxy retries give up after 3 consecutive failures. For large batches, run multiple targets across separate concurrent Actor runs rather than one very long list.

#### How fresh is the data it returns?

Live — every run fetches directly from Threads at run time. Nothing is cached or served from a previous run.

#### Which fields work best for AI training and RAG indexing?

For RAG, `captionText` is the high-information text field — already flattened out of the nested `caption` object. For training data, `like_count`, `direct_reply_count`, `repost_count`, `quote_count`, `reshare_count`, `totalEngagement`, `engagementRate`, `engagementVelocity`, and `percentileRank` are consistently typed numeric fields across every row. All fields return as typed JSON primitives or arrays, requiring no normalization before use.

#### Does scraping Threads posts raise data-protection concerns?

Yes, to the extent the data is personal — author usernames, full names, and profile pictures are personal data under GDPR/CCPA. This Actor returns only publicly available data; the lawful basis for storing and using it sits with you as the operator, not with the Actor.

#### Does it work with Claude, ChatGPT, and other AI agent tools?

It is not reachable through an MCP server. It is callable as an Apify Actor run by any agent framework that can make an HTTP request through the Apify API — every response is typed JSON, so no parsing is required before passing a row into an LLM context window.

#### How does it compare to other Threads scrapers?

As observed on the Apify Store, 2026-08-04: `burbn/threads-search-scraper` documents multi-result search with sort/from/date filters but does not document engagement analytics, minimum-engagement filtering, or excluded-keyword filtering. `logical_scrapers/threads-post-scraper` accepts only direct post URLs — no keyword search at all — but does document full reply-thread extraction. This Actor's own strength is combining real multi-result search with the derived analytics fields and client-side filters in one run; if a full nested reply tree per post is what you need, neither this Actor nor either of those two documents that as a feature.

### ℹ️ Disclaimer

Threads Search Post Scraper With Engagement Analytics extracts only publicly available data from Threads. This tool is intended for lawful use cases only. Users are responsible for complying with Threads' terms of service and applicable data protection laws in their jurisdiction.

# Actor input Schema

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

One per line: a search phrase (recommended for this actor — runs a real multi-result Threads search), a full post URL (threads.com or threads.net), or a numeric post ID (10+ digits). Example phrase: AI startups — example URL: https://www.threads.com/@zuck/post/AbCdEfGhIjK

## `urls` (type: `array`):

Legacy alias for searchQueries, kept so inputs built for the base Threads Search Post Scraper keep working unchanged. If both fields are filled, entries from both are combined.

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

Cap on how many rows to save across the whole run (search results are paginated up to this cap). Leave empty (or 0) for no limit.

## `sortOrder` (type: `string`):

Only affects search-phrase targets. "Top" is Threads' default relevance ranking; "Recent" returns the newest matching posts first.

## `fromUsername` (type: `string`):

Only keep search results posted by this exact username (without @). Leave blank to include every author.

## `afterDate` (type: `string`):

Only keep search results posted after this date/time.

## `beforeDate` (type: `string`):

Only keep search results posted before this date/time.

## `minEngagement` (type: `integer`):

Drop search results whose likes + replies + reposts + quotes + reshares add up to less than this number. 0 = no minimum.

## `excludeKeywords` (type: `array`):

Drop any search result whose caption contains one of these words/phrases (case-insensitive). One per line.

## `includeAnalytics` (type: `boolean`):

When on, every row also gets engagementRate (total engagement ÷ likes), engagementVelocity (total engagement per hour since posting), and percentileRank (0-100 rank within this target's result batch). Turn off for the leaner base-compatible field set only.

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

Turn on Apify Proxy so requests are routed through Apify's network. Threads serves an empty page to direct/un-proxied traffic from most cloud environments, so a proxy is recommended for reliable results.

## Actor input object example

```json
{
  "searchQueries": [
    "AI startups"
  ],
  "maxItems": 20,
  "sortOrder": "top",
  "includeAnalytics": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

All scraped items in the Actor's default dataset.

# 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": [
        "AI startups"
    ],
    "maxItems": 20,
    "minEngagement": 0,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapio/threads-search-post-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": ["AI startups"],
    "maxItems": 20,
    "minEngagement": 0,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapio/threads-search-post-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": [
    "AI startups"
  ],
  "maxItems": 20,
  "minEngagement": 0,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call scrapio/threads-search-post-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapio/threads-search-post-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/3MADEU2VNXu9KOLkw/builds/R6OMwp56aP2fbJ2Ov/openapi.json
