# Threads User Posts Scraper: Hashtag & Mention Breakdown (`scrapier/threads-user-posts-scraper`) Actor

🧵 Threads User Posts Scraper scrapes public posts from any Meta Threads profile — captions, timestamps, likes & replies — and 📊 exports to CSV/JSON for analysis. ⚡ Ideal for social listening, competitor research, and content strategy. API-ready, fast, and reliable.

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

## Pricing

from $5.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/platform/actors/running/actors-in-store#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 User Posts Scraper — Ranked Hashtag & Mention Breakdown

Threads User Posts Scraper: Hashtag & Mention Breakdown pulls every recent public post from one or more Threads profiles and rolls each profile into a frequency-ranked report of the hashtags it uses most and the accounts it mentions most. You get the full post objects — caption, engagement counts, media, timestamps — plus a real per-post `hashtags` list and one summary `type: report` row per profile with top hashtags, top mentions, and unique/total tallies. Every response is structured JSON, ready to pass directly to an LLM, index into a vector store, or feed a monitoring dashboard. No Threads account, login, or session cookie is required — the actor reads what a logged-out visitor can see, extracting the tokens it needs from the profile's own public page on every run. Point it at a handful of usernames, or a few hundred pasted in bulk, and get back both the raw post content and the ranked hashtag/mention signal in the same dataset, streamed live to the Output tab as each row is saved.

### What is Threads User Posts Scraper: Hashtag & Mention Breakdown?

It is a Threads data extraction actor that takes a list of profile URLs or usernames, scrapes each profile's recent posts through Threads' public GraphQL endpoint, and returns two row types in one dataset: raw post rows (`type: post`) and, per profile, one ranked breakdown row (`type: report`). The differentiator is the breakdown row itself — none of the comparable Threads scrapers reviewed for this README compute a ranked, frequency-counted hashtag and mention summary per profile; they return post lists only, leaving that aggregation to the user. No Threads login, API token, or cookie is required — the actor extracts the GraphQL credentials it needs (app ID, CSRF token, LSD token, operation doc ID) directly from the profile's public HTML and JavaScript bundle on each run.

What it returns, per run:

- 🧵 Full post objects for each profile — caption, media, engagement counts, timestamps, permalink
- \#️⃣ A real per-post `hashtags` array, parsed from Threads' structured text fragments (not the mislabeled `tags`/usertags field)
- @ A per-post `mentions` array with the mentioned account's username, ID, and avatar
- 📈 One ranked `report` row per profile with top-N hashtags, top-N mentions, and unique/total usage counts
- 🔢 A configurable analysis depth (`postsToAnalyze`) and ranking depth (`topTagsLimit`) per run
- 🛡️ Automatic proxy escalation if Threads starts blocking requests — no proxy configuration required to get a working run

The four inputs above map directly onto the actor's input schema — there are no hidden toggles or undocumented behaviors beyond what `profiles`, `postsToAnalyze`, `topTagsLimit`, `includeBreakdownReport`, and `proxyConfiguration` already control.

### What data can you get with Threads User Posts Scraper: Hashtag & Mention Breakdown?

The actor writes two result shapes to the same dataset: individual posts, and one aggregated breakdown record per profile. Both shapes come from the same scrape — the breakdown row is computed from the posts already fetched, so there is nothing extra to configure beyond turning it on or off with `includeBreakdownReport`.

| Result Type | Extracted Fields | Primary Use Case |
| --- | --- | --- |
| Post (`type: post`) | `id`, `pk`, `code`, `username`, `profile_pic_url`, `caption`, `caption_is_edited`, `hashtags`, `mentions`, `tags`, `like_count`, `direct_reply_count`, `quote_count`, `repost_count`, `like_and_view_counts_disabled`, `audio`, `medias`, `taken_at`, `taken_at_iso`, `url`, `scrapedAt` | Content analysis, engagement tracking, media archiving |
| Breakdown report (`type: report`) | `username`, `url`, `posts_analyzed`, `unique_hashtags`, `total_hashtag_uses`, `top_hashtags[]`, `unique_mentions`, `total_mention_uses`, `top_mentions[]`, `scrapedAt` | Ranked topic and mention analysis per profile |

Both row types share the same dataset schema, so post-only columns are simply absent or empty on a report row and vice versa — this is why `type` and `isChild` exist, to let you split the two shapes cleanly in a query, a spreadsheet filter, or a `df[df["type"] == "post"]` line in pandas.

#### Ranked hashtag & mention breakdown report

When `includeBreakdownReport` is on (the default), each profile's scraped posts are aggregated into one extra row after its posts finish: `type: "report"`, `isChild: true`. The actor collects every hashtag from every post's `hashtags[]` array and every mentioned username from every post's `mentions[]` array, counts occurrences case-insensitively (the first-seen capitalization is kept for display), and ranks them by frequency, most-used first. The row carries `unique_hashtags` and `total_hashtag_uses` (and the mention equivalents `unique_mentions`/`total_mention_uses`), plus `top_hashtags` — a list of `{"hashtag": "...", "count": N}` objects — and `top_mentions` — a list of `{"username": "...", "count": N}` objects. `topTagsLimit` controls how many entries survive in each ranked list after ranking (0 keeps all of them, uncapped). `posts_analyzed` on the same row records exactly how many of that profile's posts fed the count, so you can tell a thin sample from a solid one. This is computed entirely from the posts already fetched in the same run — no extra requests, and no extra charge beyond the one `row_result` event for the report row itself.

#### Post engagement and media data

Every post row also carries the underlying content: `like_count`, `direct_reply_count`, `quote_count`, and `repost_count` for engagement; `medias[]` (image or video candidates, each with `type` set to `"image"` or `"video"`, plus `url`, and either `width`/`height` for images or `video_type` for videos) and `audio` for attached audio content; and `caption_is_edited` / `like_and_view_counts_disabled`, two booleans Threads exposes on the raw post that most scrapers drop. `tags` is kept for backward compatibility — it is Threads' own `usertags` field (accounts tagged on the media), which is a different thing from the `hashtags` array and is frequently `null` because most posts don't tag anyone on their media even when they use hashtags or mention accounts in the caption.

### ⚠️ Limitations to know before a bulk run

- **`postsToAnalyze` is a per-profile ceiling, not a guarantee.** A profile with fewer posts than requested returns however many exist; pagination stops as soon as Threads reports no next page.
- **Page size shrinks under proxy backup.** The actor requests up to 25 posts per GraphQL page directly; once it escalates to a backup proxy tier it caps page size at 15 to keep requests reliable.
- **Public content only.** There is no login step, so private accounts and anything requiring authentication are not accessible — this is a hard platform boundary, not an actor setting.
- **Hashtags fall back to caption parsing.** If Threads' response includes no structured hashtag fragments for a post, the actor scans the caption text for `#tag` patterns instead. Either way, duplicates (case-insensitive) are collapsed to one entry.
- **Failed profiles do not stop the run.** If one profile in the list errors out, the actor logs it and continues to the next; only that profile's rows are missing from the output.
- **Retries back off, they don't hammer.** Each GraphQL request retries up to 5 times on transport errors with exponential backoff (capped at 30 seconds between attempts) before the actor gives up on that page and lets the proxy-escalation logic take over.
- **Duplicate posts within a profile are dropped, not double-counted.** The actor tracks post IDs seen so far per profile during pagination, so a page overlap from Threads' API never produces two rows for the same post or inflates the hashtag/mention counts.

### Why not build this yourself?

Threads does not publish a general-purpose query API for pulling an arbitrary public profile's posts and computing a ranked hashtag/mention breakdown from them — Meta's Threads API is built around managing content on accounts you control, not searching or aggregating across public profiles you don't own. Reproducing what this actor does from scratch means reverse-engineering an undocumented GraphQL endpoint: extracting the current operation `doc_id`, the relay "provided variables" keys, and the response alias out of Threads' live JavaScript bundle (all three rotate whenever Meta ships a change), matching the `x-csrftoken` header to the session cookie, and building retry and pagination logic around it. On top of that, sustained scraping needs a proxy escalation path for when the direct connection gets rate-limited. This actor already implements all of that — token extraction, GraphQL pagination, hashtag/mention parsing, and a three-tier proxy fallback (direct → Apify Datacenter → Apify Residential) — so a run degrades gracefully instead of failing outright. It also shares one cookie jar per profile scrape so the `x-csrftoken` header stays in sync with the `csrftoken` cookie Threads issues on the initial page load — a mismatch here is a common, easy-to-miss reason a hand-rolled GraphQL call to Threads silently returns empty results instead of an error.

### What's the difference between a Threads post scraper and a hashtag & mention analytics report?

A post scraper returns raw content: one row per post, as posted. A hashtag/mention analytics report is a second-order product — someone has to count which hashtags and accounts appear most often across a profile's posts and rank them. Most Threads scrapers, including the three reviewed for this README, stop at the first job and leave the counting to you in a spreadsheet or notebook. That gap matters more as a list grows: tallying hashtag frequency by hand across 25 posts for one profile is tedious but doable, across 5,000 posts for dozens of tracked profiles it isn't practical without writing your own aggregation script.

This actor does both in the same run: it returns the raw `type: post` rows, and it returns one `type: report` row per profile with the counting already done — `top_hashtags` and `top_mentions` ranked by frequency, with `unique_hashtags`/`total_hashtag_uses` and `unique_mentions`/`total_mention_uses` tallies attached. You can turn the report row off with `includeBreakdownReport: false` if you only want the raw posts, or leave it on and treat the `type` field as a filter between the two shapes downstream.

### How to scrape Threads with Threads User Posts Scraper: Hashtag & Mention Breakdown?

Every run happens through the Apify platform — there is no separate signup, API key request, or account creation with Threads or Meta involved. You need only an Apify account and the profile(s) you want to analyze.

1. Open **Threads User Posts Scraper: Hashtag & Mention Breakdown** on the Apify Store and click **Try for free** (or **Start** if you already have it in your account).
2. Fill in `profiles` — one Threads profile URL or plain `@username` per line. This is the only required field.
3. Set `postsToAnalyze` (how many recent posts per profile, 1–5000) and `topTagsLimit` (how deep the ranked breakdown goes, 0–1000).
4. Leave `proxyConfiguration` off unless you need to force a specific country or sticky session for the backup proxy tiers — the actor auto-escalates on its own.
5. Click **Start**. Rows stream to the **Output** tab live as each post is saved; export the finished dataset as JSON, CSV, Excel, XML, or HTML from the Apify Console, or pull it with the API/SDK. If you export to CSV or Excel, nested fields like `hashtags`, `mentions`, `medias`, `top_hashtags`, and `top_mentions` are serialized as JSON strings in their column, since those formats don't support nested arrays natively — JSON export keeps them as real arrays/objects.

#### How to analyze multiple profiles in one job

`profiles` is a plain string list — paste as many profile links or usernames as you want, one per line, in the same run. Each profile is processed in sequence: its posts are scraped, saved, then (if `includeBreakdownReport` is on) its ranked breakdown row is appended before the actor moves to the next profile. There is no separate batching parameter — list size is the batch. If one profile in the list fails (a typo'd username, a profile that no longer exists, a run of blocked requests), the actor logs the error and moves on to the next entry rather than aborting the whole job, so a 200-profile list with two bad usernames still returns 198 profiles' worth of results.

### ⬇️ Input

The actor takes one JSON object. `profiles` is the only required field; everything else has a working default.

| Parameter | Required | Type | Description | Example Value |
| --- | --- | --- | --- | --- |
| `profiles` | Yes | array (string list) | Profile links (`threads.com` / `threads.net`) or plain @usernames — one per line, bulk lists welcome. | `"https://www.threads.com/@zuck"` or `"zuck"` |
| `postsToAnalyze` | No | integer | How many recent posts to scrape and feed into the hashtag/mention breakdown, per profile. Range 1–5000, default 25. More posts = a richer, more representative ranking. | `50` |
| `topTagsLimit` | No | integer | How many of the most-used hashtags and most-mentioned accounts to keep in each profile's ranked breakdown report. Range 0–1000, default 20. `0` keeps all of them. | `20` |
| `includeBreakdownReport` | No | boolean | When on (default `true`), each profile gets one extra `type: report` row with its frequency-ranked top hashtags, top mentions, and unique/total tallies. Turn off to keep only the raw post rows. | `true` |
| `proxyConfiguration` | No | object (proxy editor) | Toggle **Use Apify Proxy** on only to force specific groups (country, session) for the backup steps. Left off (default), the actor talks to Threads directly first and auto-escalates to Apify Datacenter, then Residential, only if the site blocks the run. | `{"useApifyProxy": false}` |

#### Example JSON input

```json
{
  "profiles": [
    "https://www.threads.com/@zuck",
    "mosseri",
    "https://www.threads.net/@natgeo"
  ],
  "postsToAnalyze": 50,
  "topTagsLimit": 20,
  "includeBreakdownReport": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

**Common pitfall:** `profiles` accepts a raw username, an `@username`, or a full `threads.com`/`threads.net` profile URL — mixing formats in the same list is fine, the actor normalizes each line before use. What it does **not** accept is a post URL or a hashtag page URL; this actor scrapes a profile's post feed, not an individual post or a hashtag stream. A second pitfall: raising `postsToAnalyze` well above what a profile actually posts doesn't cost you anything extra — pagination simply stops once Threads reports no further pages, so it's safe to set it high and let the actual post count come back lower for quiet accounts.

### ⬆️ Output

Results are typed, normalized JSON, written to the actor's default dataset as they are scraped — rows appear in the **Output** tab live, not only at the end of the run. Export as JSON, CSV, Excel, XML, or HTML directly from the Apify Console, or read the dataset via the Apify API or SDK. Both row types (`post` and `report`) land in the same dataset; filter on the `type` field to separate them, or use `isChild` (`false` for posts, `true` for the report row).

The Console's default **table view** shows 28 columns spanning both row types. The underlying data goes further: post rows also carry `caption_is_edited` and `like_and_view_counts_disabled` — two booleans Threads sets on the raw post that aren't part of the default table view but are present on every post row in the JSON, CSV, and API output. Switch to the JSON view, or pull the dataset via the API, to see them.

#### Scraped results

```json
[
  {
    "type": "post",
    "isChild": false,
    "profile_pic_url": "https://scontent.cdninstagram.com/v/t51.2885-19/example_avatar.jpg",
    "username": "zuck",
    "id": "3220231147841639674_314216",
    "pk": "3220231147841639674",
    "code": "Cywjyrdv9T6",
    "caption": "Good conversation with @hubermanlab about AI and mixed reality. #AI #Threads",
    "caption_is_edited": false,
    "like_count": 3756,
    "direct_reply_count": 402,
    "quote_count": 20,
    "repost_count": 131,
    "like_and_view_counts_disabled": false,
    "audio": null,
    "medias": [
      { "type": "image", "url": "https://scontent.cdninstagram.com/example1.jpg", "width": 1080, "height": 1350 }
    ],
    "hashtags": ["AI", "Threads"],
    "tags": null,
    "mentions": [
      { "username": "hubermanlab", "profile_pic_url": "https://scontent.cdninstagram.com/example_hl.jpg", "id": "178912345" }
    ],
    "taken_at": "2026-07-20 22:51:29",
    "taken_at_iso": "2026-07-20T22:51:29Z",
    "url": "https://www.threads.net/@zuck/post/Cywjyrdv9T6",
    "scrapedAt": "2026-07-26T09:14:02Z"
  },
  {
    "type": "post",
    "isChild": false,
    "profile_pic_url": "https://scontent.cdninstagram.com/v/t51.2885-19/example_avatar.jpg",
    "username": "zuck",
    "id": "3220198765432109876_314216",
    "pk": "3220198765432109876",
    "code": "Cx9jK2mvAB4",
    "caption": "Building in the open. #AI #buildinpublic",
    "caption_is_edited": false,
    "like_count": 5120,
    "direct_reply_count": 610,
    "quote_count": 44,
    "repost_count": 203,
    "like_and_view_counts_disabled": false,
    "audio": null,
    "medias": null,
    "hashtags": ["AI", "buildinpublic"],
    "tags": null,
    "mentions": [],
    "taken_at": "2026-07-18 15:02:11",
    "taken_at_iso": "2026-07-18T15:02:11Z",
    "url": "https://www.threads.net/@zuck/post/Cx9jK2mvAB4",
    "scrapedAt": "2026-07-26T09:14:05Z"
  },
  {
    "type": "post",
    "isChild": false,
    "profile_pic_url": "https://scontent.cdninstagram.com/v/t51.2885-19/example_avatar.jpg",
    "username": "zuck",
    "id": "3220187654321098765_314216",
    "pk": "3220187654321098765",
    "code": "Cy1pQ8rsTZ3",
    "caption": "Great chat with @cillachan about mixed reality.",
    "caption_is_edited": true,
    "like_count": 2890,
    "direct_reply_count": 315,
    "quote_count": 12,
    "repost_count": 98,
    "like_and_view_counts_disabled": false,
    "audio": { "id": "781234", "title": "Original audio" },
    "medias": [
      { "type": "video", "url": "https://scontent.cdninstagram.com/example1.mp4", "video_type": 101 }
    ],
    "hashtags": null,
    "tags": null,
    "mentions": [
      { "username": "cillachan", "profile_pic_url": "https://scontent.cdninstagram.com/example_cc.jpg", "id": "556677889" }
    ],
    "taken_at": "2026-07-15 11:40:33",
    "taken_at_iso": "2026-07-15T11:40:33Z",
    "url": "https://www.threads.net/@zuck/post/Cy1pQ8rsTZ3",
    "scrapedAt": "2026-07-26T09:14:08Z"
  },
  {
    "type": "report",
    "isChild": true,
    "username": "zuck",
    "url": "https://www.threads.net/@zuck",
    "posts_analyzed": 50,
    "unique_hashtags": 14,
    "total_hashtag_uses": 37,
    "top_hashtags": [
      { "hashtag": "AI", "count": 9 },
      { "hashtag": "buildinpublic", "count": 5 },
      { "hashtag": "Threads", "count": 4 }
    ],
    "unique_mentions": 8,
    "total_mention_uses": 19,
    "top_mentions": [
      { "username": "hubermanlab", "count": 4 },
      { "username": "cillachan", "count": 3 }
    ],
    "scrapedAt": "2026-07-26T09:14:09Z"
  }
]
```

Every pushed row — each post and each profile's breakdown report — is billed under the single `row_result` charged event; there is no separate uncharged accounting row emitted by this actor. In the example above, that means a run against one profile produces four charged rows: three posts plus one report. Scale that to a `profiles` list of ten and `postsToAnalyze: 50` with `includeBreakdownReport` on, and you're looking at up to 500 post rows plus 10 report rows — 510 `row_result` events — as an upper bound, since any profile with fewer than 50 actual posts contributes fewer rows.

### How can I use the data extracted with Threads User Posts Scraper: Hashtag & Mention Breakdown?

The dataset is structured to support both a spreadsheet-style review of individual posts and a programmatic read of each profile's ranked summary, so the same run serves a few different jobs at once.

- 📊 **Social media analysts:** feed `top_hashtags` and `top_mentions` per profile into a weekly report to see which topics and accounts a KOL or brand is leaning on, without manually tallying posts one by one in a spreadsheet.
- 🤖 **AI engineers and LLM developers:** pass the JSON dataset straight into a model as grounding context — an agent that answers "what has this account been posting about" reads `caption` and `hashtags` directly, no cleanup or extra parsing step required.
- 🔬 **Market and trend researchers:** run the same profile list on a schedule and diff `top_hashtags` between runs to catch a topic pivot, an emerging hashtag, or a new recurring mention as it happens, instead of after the fact.
- 🏢 **Brand and community teams:** use `mentions` and `top_mentions` to see which accounts a monitored profile engages with most (collaborators, partners, or competitors), and `like_count`/`direct_reply_count`/`repost_count`/`quote_count` to track engagement per post over time.

### How do you monitor hashtag & mention trends over time?

Monitoring here means running the same `profiles` list on a recurring schedule and comparing each profile's `type: report` row against its previous run to see what shifted — a competitor pivoting their content strategy, a brand account starting a new campaign hashtag, or a creator's collaborator list changing are all visible as movement in this row without reading a single post caption by hand. Because the breakdown is recomputed fresh from whatever posts fall inside `postsToAnalyze` each time, a rising `count` on a hashtag in `top_hashtags`, or a mention that newly appears in `top_mentions`, signals a real change in what the profile is posting about or who it's engaging with — not noise. The fields worth diffing between runs are `top_hashtags[].count`, `top_mentions[].count`, `unique_hashtags`, `unique_mentions`, and `posts_analyzed` (a drop here means the profile posted less, or deleted posts, since the last run). A practical loop: schedule a run across your tracked profile list, pull the latest `report` row per `username` after each run, diff it against the row from the prior run, and alert when a hashtag or mention's `count` crosses a threshold you care about. Set this up with an **Apify schedule** on the actor (Console → Schedules, or the `schedules` API endpoint) so runs fire automatically on the cadence you need — the actor itself has no built-in cron, so recurrence is configured at the platform level.

### Integrate Threads User Posts Scraper: Hashtag & Mention Breakdown and automate your workflow

Threads User Posts Scraper: Hashtag & Mention Breakdown works with any language or tool that can send an HTTP request, through the Apify API and official client libraries. The three paths below cover a one-off script, a scheduled pipeline, and a Node-based backend — pick whichever matches your stack; all three call the same actor and return the same dataset shape.

#### REST API with Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")

run_input = {
    "profiles": ["https://www.threads.com/@zuck", "mosseri"],
    "postsToAnalyze": 50,
    "topTagsLimit": 20,
    "includeBreakdownReport": True,
}

run = client.actor("<YOUR_USERNAME>/threads-user-posts-scraper-hashtag-mention-breakdown").call(run_input=run_input)

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

Replace `<YOUR_API_TOKEN>` with your Apify API token and `<YOUR_USERNAME>` with the account this actor is published under.

#### REST API with cURL

```bash
curl "https://api.apify.com/v2/acts/<YOUR_USERNAME>~threads-user-posts-scraper-hashtag-mention-breakdown/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "profiles": ["https://www.threads.com/@zuck"],
    "postsToAnalyze": 25,
    "topTagsLimit": 20,
    "includeBreakdownReport": true
  }'
```

`run-sync-get-dataset-items` starts the run and returns the finished dataset directly in the response — no separate polling step needed for smaller jobs. For longer-running batches of many profiles, use the standard `runs` endpoint and poll or use a webhook instead.

#### REST API with Node.js

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

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

const input = {
  profiles: ['https://www.threads.com/@zuck', 'mosseri'],
  postsToAnalyze: 50,
  topTagsLimit: 20,
  includeBreakdownReport: true,
};

const run = await client.actor('<YOUR_USERNAME>/threads-user-posts-scraper-hashtag-mention-breakdown').call(input);

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Scheduled monitoring and delivery

Create a recurring **Schedule** for this actor in the Apify Console (or via the Apify API) to re-run the same profile list on a cadence — daily, weekly, or a custom cron expression. Pair it with an Apify **Webhook** on the `ACTOR.RUN.SUCCEEDED` event to push a notification, or trigger a downstream workflow, each time a run finishes and fresh rows land in the dataset. Because each `type: report` row already carries `posts_analyzed`, `unique_hashtags`, and `unique_mentions`, a webhook handler can compare those numbers against the last run's values without re-reading every post — the delta signal is in the report row itself.

### Is it legal to scrape Threads posts and profiles?

Scraping publicly accessible Threads content is generally lawful in most jurisdictions — this actor only retrieves what any logged-out visitor to a public profile can already see; it does not access private accounts or bypass any login wall. In *hiQ Labs, Inc. v. LinkedIn Corp.* (9th Circuit, 2019), the court held that scraping publicly available web data does not violate the U.S. Computer Fraud and Abuse Act. Threads posts, usernames, and mentioned accounts are personal data belonging to real people, so GDPR (EU/UK) and CCPA (California) considerations apply to how you store, process, and retain what you collect — not to the act of viewing public pages itself. Scraping for aggregate research or monitoring (this actor's use case) carries a different risk profile than scraping to build individual profiles for targeting or resale. Consult your legal team for commercial use cases involving bulk storage or re-publication of the data.

### ❓ Frequently asked questions

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

No. The actor extracts the app ID, CSRF token, LSD token, and GraphQL operation ID it needs directly from each profile's public page on every run — there is no session cookie, password, or API token to supply.

#### Can I force a specific proxy country for the backup steps?

Yes — expand `proxyConfiguration` and turn **Use Apify Proxy** on to set a country or sticky session for the Datacenter/Residential backup tiers. Left off (the default), the actor connects directly first and only escalates to Apify's proxies if Threads blocks the request.

#### How many posts does the scraper return per profile?

Up to `postsToAnalyze` (default 25, max 5000) — but never more than the profile actually has. Internally the actor fetches posts in pages of up to 25 at a time directly (capped at 15 per page once a backup proxy tier is active), and keeps paginating until it hits your `postsToAnalyze` limit or Threads' GraphQL response reports no next page — whichever comes first. So a low-activity profile can return fewer rows than requested; that's the profile running out of posts, not the actor stopping early.

#### Does the scraper extract real hashtags, not just tagged accounts?

Yes — `hashtags` is parsed from Threads' structured `text_fragments` on each post (falling back to scanning the caption text for `#tag` patterns if none are present). This is separate from `tags`, which is Threads' own `usertags` field for accounts tagged on media, kept only for backward compatibility and frequently `null`.

#### How deep is the ranked hashtag & mention breakdown?

Set by `topTagsLimit` (default 20, max 1000, or `0` for unlimited). It controls how many entries survive in each profile's `top_hashtags` and `top_mentions` lists after they're ranked by frequency across all analyzed posts.

#### Can I get just the raw posts without the ranked report row?

Yes — set `includeBreakdownReport` to `false`. Each profile will then only produce `type: post` rows; the aggregated `type: report` row is skipped.

#### How does the scraper handle Threads' anti-bot measures?

It connects directly by default and escalates automatically if the platform starts rejecting requests: first to Apify's Datacenter proxy, then to Residential proxy with up to three rotated session attempts per profile. Requests also retry with exponential backoff on transport errors before the actor gives up on that profile.

#### How do I monitor a profile's hashtag and mention trends over time?

Run the same `profiles` list on an Apify Schedule and compare each profile's latest `type: report` row against its previous run — track `top_hashtags[].count` and `top_mentions[].count` for the entries that move.

#### Does this actor work with Claude, ChatGPT, and AI agent frameworks?

It is callable as a standard HTTP endpoint through the Apify API by any agent framework capable of making a request — an agent can trigger a run, poll the dataset, and use the returned JSON (captions, hashtags, mentions, engagement counts) as grounded context before generating an answer. Because the ranked breakdown row is precomputed server-side, an agent doesn't need to run its own aggregation step to answer "what hashtags does this account use most" — it can read `top_hashtags` straight off the `type: report` row.

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

As observed on the Apify Store (checked 2026-07-26): `apibox/threads-user-posts-scraper` returns individual post objects — username, caption, engagement counts, media, mentions — keyed on a single `username`/`num` input, with no per-profile hashtag or mention ranking documented. `caprolok/all-social-media-posts-extractor-by-hashtag-and-username` covers Instagram, TikTok, LinkedIn, and Twitter/X in addition to a generic post shape (caption, content type, timestamp, reply count), without a Threads-specific hashtag/mention breakdown and without Threads as one of its listed platforms. `claude_code_reviewer/threads-scraper` offers five modes (user, hashtag page, search, single post, feed) in one actor and advertises no login required, plus merged multi-segment thread handling — but its documented output fields are post- and reply-level (`postId`, `content`, engagement counts, `threadParts[]`), with no ranked per-profile hashtag/mention aggregation row. This actor's `type: report` row — ranked top hashtags and top mentions with unique/total tallies, computed automatically per profile from the posts already scraped — is not documented on any of the three as of the date checked.

#### Can I use this actor without managing proxies or platform credentials?

Yes. There is no Threads login, cookie, or token to obtain — the actor handles token extraction itself, pulling the app ID, CSRF token, LSD token, and GraphQL doc ID fresh from each profile's public page every run — and proxy backup is fully automatic unless you choose to override it in `proxyConfiguration`. You only need an Apify account to run the actor; there's no separate Meta Developer account, OAuth flow, or app review process to go through, unlike Meta's own Threads API.

### 💬 Your feedback

Found a bug, or a field that doesn't match what Threads returns? Let us know through the actor's **Issues** tab on its Apify Store listing, or leave a review — both are monitored and help prioritize fixes and improvements.

# Actor input Schema

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

Profile links (`threads.com` / `threads.net`) or plain @usernames — one per line, bulk lists welcome.

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

## `postsToAnalyze` (type: `integer`):

How many recent posts to scrape and feed into the hashtag/mention breakdown, per profile (1–5000). More posts = a richer, more representative ranking. Example: 50 → the breakdown is computed over the latest 50 posts.

## `topTagsLimit` (type: `integer`):

How many of the most-used hashtags and most-mentioned accounts to keep in each profile's ranked breakdown report (0 = keep them all). Example: 20 → the report lists the top 20 hashtags and top 20 mentions by frequency.

## `includeBreakdownReport` (type: `boolean`):

When on, each profile gets one extra `type: report` row with its frequency-ranked top hashtags, top mentions, and unique/total tallies. Turn off to keep only the raw post rows.

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

🔧 Toggle **Use Apify Proxy** on only if you want to force specific groups (country, session) for the backup steps. Otherwise leave **Off** — the actor auto-escalates to Datacenter / Residential when needed.

## Actor input object example

```json
{
  "profiles": [
    "https://www.threads.com/@zuck"
  ],
  "postsToAnalyze": 25,
  "topTagsLimit": 20,
  "includeBreakdownReport": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# 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("scrapier/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("scrapier/threads-user-posts-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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 scrapier/threads-user-posts-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=scrapier/threads-user-posts-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/WbavqXzLfymluRg80/builds/0lFVZg2meVTJbjezl/openapi.json
