# Threads User Posts Scraper — Engagement & Cadence Report (`simpleapi/threads-user-posts-scraper`) Actor

🧵 Threads User Posts Scraper pulls public posts from Threads profiles — text, timestamps, engagement, media, links, hashtags & post IDs. 🔍 Great for social listening, competitor analysis, research & archiving. ⚡ Fast, reliable, API-ready exports (CSV/JSON).

- **URL**: https://apify.com/simpleapi/threads-user-posts-scraper.md
- **Developed by:** [SimpleAPI](https://apify.com/simpleapi) (community)
- **Categories:** Social media, Developer tools, Automation
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.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 Scraper — Engagement and Posting Cadence Report

Threads User Posts Scraper — Engagement & Cadence Report turns a public Threads profile into raw post rows plus a locally-computed analytics report: posting cadence (posts per week, average gap between posts, most-active day and hour), average and median engagement, an overall engagement rate, and a ranked top-posts list. It's built for creators and social media managers who want to know when they post and how well it performs, agencies benchmarking a client's or competitor's Threads cadence, and researchers who need real, reproducible engagement statistics rather than a single vanity number. No login is required — the Actor scrapes public profile data. Every section below documents an input, an output field, or exactly how each report metric is computed.

### What is Threads User Posts Scraper — Engagement & Cadence Report?

This Actor scrapes a public Threads profile's posts via Threads' own internal GraphQL API, then computes a per-profile analytics report entirely from the posts it actually collected — no separate analytics API, no estimation from partial data.

Key capabilities, read from the source:

- **Cadence metrics computed from real post timestamps.** `postsPerWeek`, `avgGapHours`, `mostActiveDayUTC`, and `mostActiveHourUTC` are all derived directly from the collected posts' `taken_at` timestamps — genuine statistics over the sampled posts, not an estimate.
- **Both average and median engagement, not just one.** The report includes `avgLikes`/`medianLikes`, `avgReplies`/`medianReplies`, `avgReposts`/`medianReposts`, and `avgEngagement`/`medianEngagement` — median is included specifically because a single viral post can badly skew an average, and reporting both lets you see that skew rather than hide it.
- **A ranked top-posts list by total engagement.** `topPosts` ranks the sampled posts by `likes + replies + quotes + reposts` combined, up to `topPostsLimit` — a quick way to see which specific posts drove the profile's best performance in the sample.
- **Engagement rate normalized against real follower count.** `engagementRate` divides average engagement by the profile's actual `followerCount`, pulled from the same token-resolution fetch that authenticates the scrape — not a separately-scraped or guessed number.
- **Direct-connection-first with automatic backup escalation.** The Actor connects directly to Threads first, only escalating to Apify Datacenter then Residential proxy tiers if Threads limits the request — minimizing proxy cost on the common case where direct access works.
- **Live Relay provider-key extraction.** Threads' GraphQL query requires an exact set of "provider" flags that Meta rotates whenever it ships a feature; sending an extra or missing key returns an execution error. The Actor extracts the current key set from the live JS bundle per request rather than hardcoding a set that would go stale.

### What data can I extract with Threads User Posts Scraper — Engagement & Cadence Report?

Every field below is read directly from `extract_posts()`/`build_report()` in the source — the dataset's default view surfaces all 36 as table columns.

#### Post rows (`type: "post"`)

| Field | Example Value | Notes |
| --- | --- | --- |
| `caption` / `url` / `code` | post text / link / short code | |
| `like_count` / `direct_reply_count` / `quote_count` / `repost_count` | `450` / `32` / `5` / `18` | Real per-post engagement counts |
| `taken_at_iso` / `taken_at` | UTC ISO / local timestamp | |
| `medias` / `mentions` / `tags` / `audio` | arrays/objects | Attached content and metadata |
| `id` / `pk` | Threads' own identifiers | |

#### Report rows (`type: "report"`, `isChild: true`)

| Field | Example Value | Notes |
| --- | --- | --- |
| `postsAnalyzed` | `25` | Real sample size the report is computed from |
| `postsPerWeek` / `avgGapHours` | `4.2` / `18.5` | Real cadence statistics |
| `mostActiveDayUTC` / `mostActiveHourUTC` | `"Tuesday"` / `14` | Computed from the sample's actual posting timestamps |
| `avgLikes` / `medianLikes` / `avgReplies` / `medianReplies` / `avgReposts` / `medianReposts` | both average and median | Median included specifically to expose skew from viral outliers |
| `avgEngagement` / `medianEngagement` | combined engagement stats | |
| `followerCount` | `85000` | Real count from the profile's own data |
| `engagementRate` | `2.4` | `avgEngagement / followerCount * 100` |
| `topPosts` | array of ranked posts | Top `topPostsLimit` posts by total engagement |

### Why not build this yourself?

Threads has no public analytics API — reaching post-level engagement data at all means calling the same internal GraphQL endpoint the Threads web app uses, which requires a `doc_id`, a CSRF token that must match a cookie set during an earlier page load, and an exact set of Relay provider flags that Meta rotates without notice; getting any of these wrong returns a generic execution error, not a helpful message. On top of that, meaningful cadence and engagement statistics require computing median alongside average yourself, since a single viral post can make a raw average badly misleading — this Actor does that computation locally from the actual collected posts rather than relying on a platform-reported metric that doesn't exist.

### How to use data extracted from Threads User Posts Scraper — Engagement & Cadence Report?

#### Creator and social media management

Run your own profile to see your real posting cadence (`postsPerWeek`, `mostActiveDayUTC`/`mostActiveHourUTC`) alongside `avgEngagement` vs `medianEngagement` — a gap between the two tells you whether your average is being propped up by one or two outlier posts.

#### Agency benchmarking and competitive research

Compare `engagementRate` and `postsPerWeek` across a client and their named competitors to show cadence and performance side by side in a single report.

#### Content strategy and timing research

Use `mostActiveDayUTC`/`mostActiveHourUTC` combined with `topPosts` to see whether a profile's best-performing posts actually align with its typical posting time, or whether timing and top performance diverge.

#### AI agents and reporting pipelines

Because the report row is a single, self-contained JSON object with every metric pre-computed, an agent can generate an accurate performance summary directly from one record without re-deriving statistics from the raw post rows.

### 🔼 Input sample

| Parameter | Required | Type | Description | Example Value |
| --- | --- | --- | --- | --- |
| `profiles` | **Yes** | array | Threads profile links or plain `@usernames`, one per line. | `["https://www.threads.com/@zuck"]` |
| `postsPerProfile` | No | integer (1–5000) | Posts to collect and feed into the report per profile. Default `25`. | `50` |
| `includeReport` | No | boolean | Emit the per-profile report row. `false` returns only raw post rows. Default `true`. | `true` |
| `topPostsLimit` | No | integer (0–100) | How many top posts to rank in the report. `0` = none. Default `5`. | `10` |
| `proxyConfiguration` | No | object | Optional; the Actor connects direct first, auto-escalating to datacenter then residential on a limit. | `{"useApifyProxy": false}` |

```json
{
  "profiles": ["https://www.threads.com/@zuck"],
  "postsPerProfile": 50,
  "includeReport": true,
  "topPostsLimit": 10
}
```

**Common pitfall:** `postsPerProfile` sets the sample size the report is computed from — cadence and engagement statistics on a small sample (e.g. 5 posts) are less statistically meaningful than on a larger one; for a reliable weekly-cadence figure, sample at least a few weeks' worth of posting activity.

### 🔽 Output sample

Output is one JSON row per post, plus one report row per profile, pushed to the run's default dataset and charged as one `row_result` event per row.

```json
{
  "username": "zuck",
  "type": "post",
  "isChild": false,
  "caption": "Excited to share our latest update...",
  "like_count": 4500,
  "direct_reply_count": 320,
  "quote_count": 50,
  "repost_count": 180,
  "taken_at_iso": "2026-07-20T14:30:00Z",
  "url": "https://www.threads.com/@zuck/post/abc123",
  "scrapedAt": "2026-07-26T09:00:00Z"
}
```

A report row:

```json
{
  "username": "zuck",
  "type": "report",
  "isChild": true,
  "postsAnalyzed": 50,
  "postsPerWeek": 4.2,
  "avgGapHours": 18.5,
  "mostActiveDayUTC": "Tuesday",
  "mostActiveHourUTC": 14,
  "avgLikes": 3200,
  "medianLikes": 2800,
  "avgEngagement": 3750,
  "medianEngagement": 3100,
  "followerCount": 85000,
  "engagementRate": 4.41,
  "topPosts": [{ "url": "https://www.threads.com/@zuck/post/abc123", "totalEngagement": 5050 }]
}
```

### How do you filter and target specific profiles?

**Sample size drives report reliability.** `postsPerProfile` is the primary lever — a larger sample gives more statistically meaningful `postsPerWeek` and `avgGapHours` figures, while a smaller sample runs faster for a quick spot-check.

**Turn off the report for a raw-data-only run.** `includeReport: false` skips report computation entirely, useful when you plan to compute your own custom statistics downstream from the raw post rows rather than using this Actor's built-in metrics.

**Rank depth vs. report size.** `topPostsLimit` controls how many ranked posts appear in the report — set it to `0` for a lean cadence-and-engagement-only report, or higher when you specifically need a "best performing content" list.

Three real examples:

```json
{ "profiles": ["mybrandaccount"], "postsPerProfile": 100, "topPostsLimit": 10 }
```

Deep cadence and top-content analysis for a single profile.

```json
{ "profiles": ["competitor1", "competitor2", "competitor3"], "postsPerProfile": 30 }
```

Multi-competitor cadence and engagement-rate comparison.

```json
{ "profiles": ["mybrandaccount"], "includeReport": false, "postsPerProfile": 200 }
```

Raw post export only, for custom downstream analytics.

### ▶️ Want to try other scrapers?

| Scraper | What it extracts |
| --- | --- |
| Threads Follower Mutual Connection Mapper | Follower relationships classified as mutual/fan/one-way |
| TikTok Profile Scraper — Engagement Rate & Posting Cadence | Creator engagement and cadence metrics |
| Instagram Engagement Estimator | Profile-level engagement rate and posting cadence |
| X/Twitter Trends Scraper by City | Trending topics across cities |

### How to extract Threads posts and report data programmatically

This Actor runs as a standard Apify Actor call — one API call in, structured JSON dataset out, using your Apify API token.

#### Python example

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("<YOUR_USERNAME>/threads-user-posts-scraper-engagement-cadence-report").call(run_input={
    "profiles": ["https://www.threads.com/@zuck"],
    "postsPerProfile": 50,
    "topPostsLimit": 10,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item.get("type") == "report":
        print(item["username"], item["postsPerWeek"], item["engagementRate"])
```

#### Export to spreadsheets or CRM

Every dataset supports one-click CSV/Excel export from the Apify Console. Filter on `type` to separate post rows from report rows before exporting.

### Is it legal to scrape Threads posts?

Scraping publicly visible Threads posts is generally lawful, since this data is published for anyone to view without logging in — the underlying legal question was tested directly in *hiQ Labs v. LinkedIn* (9th Cir.), which held that scraping public, non-password-protected data does not violate the U.S. Computer Fraud and Abuse Act. Post captions and author identity can constitute personal data under GDPR/CCPA when tied to an identifiable individual, so treat that subset accordingly if you store or reuse it at scale, and consult legal counsel for commercial applications.

### ❓ FAQ

#### Is the engagement rate comparable across profiles of different sizes?

Yes — `engagementRate` is normalized by the profile's own real `followerCount` (`avgEngagement / followerCount * 100`), so it's designed specifically for comparing profiles with very different audience sizes on a level basis.

#### Why does the report include both average and median?

A single viral post can badly skew a simple average — reporting median alongside average lets you see when that's happening. A large gap between `avgEngagement` and `medianEngagement` signals your average is being driven by a small number of outlier posts.

#### Do I need a Threads or Instagram account to use this?

No — this Actor scrapes public profile data without requiring login; it connects directly to Threads and only falls back to Apify proxy tiers if the site limits the request.

#### How is postsPerWeek calculated?

From the real timestamp span of the collected sample — the number of posts divided by the actual time window they cover, converted to a weekly rate. A small `postsPerProfile` sample covering only a few days will produce a less reliable weekly estimate than a larger one spanning several weeks.

#### What happens if a profile has fewer posts than postsPerProfile?

The Actor collects whatever is actually available and the report's `postsAnalyzed` reflects the real sample size — cadence and engagement statistics are always computed from what was genuinely collected, never padded.

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

As observed on the Apify Store on 2026-07-26, `apibox/threads-user-posts-scraper` and `george.the.developer/threads-scraper` both extract Threads posts, but neither documents a built-in cadence-and-engagement report with median statistics and a ranked top-posts list — that analytics layer is unique to this Actor.

#### Does this work with AI agent frameworks?

Yes — call it as a standard HTTP endpoint via the Apify API from any agent framework capable of making an API call; there's no MCP-specific integration for this Actor.

### Conclusion

Threads User Posts Scraper — Engagement & Cadence Report turns a profile's posts into both raw data and a ready-made analytics report — real cadence statistics, average and median engagement, and a ranked top-posts list, all computed locally from what was actually collected. It fits creator self-analysis, agency benchmarking, and content-timing research. Start a run from the Apify Console or the Apify API with your target profiles to get your first cadence report.

# Actor input Schema

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

Threads profile links (`threads.com` / `threads.net`) or plain @usernames — one per line. Each profile gets its own posts plus an engagement & cadence report row.

Example: `https://www.threads.com/@zuck` or just `zuck`

## `postsPerProfile` (type: `integer`):

How many recent posts to collect and feed into the report per profile (1–5000). More posts = more statistically meaningful cadence and engagement averages. Default is 25.

## `includeReport` (type: `boolean`):

When on, a summary record (`type: report`) is appended per profile with cadence, average/median engagement, engagement rate and the top-posts ranking. Turn off to return only the raw post rows. Default is on.

## `topPostsLimit` (type: `integer`):

How many of the highest-engagement posts to include in the report's ranked topPosts list (0 = none). Ranking is by total engagement (likes + replies + quotes + reposts). Default is 5.

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

Optional. The actor connects to Threads directly first and auto-escalates to Apify Datacenter then Residential proxies if the site limits the run. Expand only to force a specific group or country for the backup steps.

## Actor input object example

```json
{
  "profiles": [
    "https://www.threads.com/@zuck"
  ],
  "postsPerProfile": 25,
  "includeReport": true,
  "topPostsLimit": 5,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# 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 = {
    "profiles": [
        "https://www.threads.com/@zuck"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("simpleapi/threads-user-posts-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 = {
    "profiles": ["https://www.threads.com/@zuck"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("simpleapi/threads-user-posts-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 '{
  "profiles": [
    "https://www.threads.com/@zuck"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call simpleapi/threads-user-posts-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,simpleapi/threads-user-posts-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/nE4rFPf9ZqIAYzcNW/builds/B55bQr8fMLO5digGc/openapi.json
