# Threads Hashtag Scraper (`lightmoon/threads-hashtag-scraper`) Actor

Monitor any Threads hashtag or keyword: full caption, author, timestamp, likes, replies, reposts, links and media, 26 columns per row, to CSV or Excel. Track brand mentions and find influencers by hashtag. 15-25 posts per hashtag, stated up front. Posts with no caption are never charged for.

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

## Pricing

from $2.20 / 1,000 posts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/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

Give it a hashtag. Get back the posts Threads is showing on that tag right now —
the full caption, who wrote it, when, and how many likes and replies it has —
as a flat table you can open in a spreadsheet. Search phrases work the same way,
for topics people write about without tagging.

No login, no cookies, no browser automation.

> **Two things before you run it.** A hashtag returns **15 to 25 posts and there
> is no second page** — that is Threads' own limit, and volume comes from
> listing more hashtags, not from a bigger number in the input. And you are
> **charged only for a post that has text**: a caption-less photo still arrives
> with its image URL and costs nothing. Both are explained in full below.

### What one row looks like

Copied out of a real run, not written by hand — run the Actor on `coffee` and
you will get rows in exactly this shape.

```json
{
  "recordType": "post",
  "postUrl": "https://www.threads.net/@bjoerncarlos_/post/DFHxXWmIFT4",
  "text": "Coffee Lovers ☕\ncoffeetime",
  "username": "bjoerncarlos_",
  "userFullName": "Björn Carlos | Fitness & Lifestyle",
  "userIsVerified": false,
  "userUrl": "https://www.threads.net/@bjoerncarlos_",
  "postedAt": "2025-01-22T08:37:42Z",
  "likeCount": 765,
  "replyCount": 76,
  "repostCount": 0,
  "quoteCount": 0,
  "hashtags": "",
  "topicTag": "coffeetime",
  "mentions": "",
  "linkUrl": "",
  "mediaType": "image",
  "mediaCount": 1,
  "imageUrl": "https://scontent.cdninstagram.com/v/t51.75761-15/474245934_...",
  "videoUrl": "",
  "isReply": false,
  "postId": "3551023940293907704",
  "userId": "63223801492",
  "shortcode": "DFHxXWmIFT4",
  "query": "coffee",
  "queryType": "hashtag"
}
```

Twenty-six columns on every row, in the same order, every time. A field Threads
did not publish comes back empty — never guessed, never `"N/A"`.

| column | what it is |
|---|---|
| `recordType` | always `post`, so rows stay identifiable if you merge this export with another |
| `postUrl` | the public link to the post |
| `text` | the caption, in full, exactly as written |
| `username`, `userFullName`, `userIsVerified`, `userUrl` | who posted it |
| `postedAt` | when, as ISO 8601 UTC — sortable in any spreadsheet |
| `likeCount`, `replyCount`, `repostCount`, `quoteCount` | engagement, as integers |
| `hashtags` | the `#tags` written in the caption, comma-separated |
| `topicTag` | the topic tag the author attached, which Threads keeps separate from the caption |
| `mentions` | `@handles` mentioned, in their canonical spelling |
| `linkUrl` | the first link in the post |
| `mediaType` | `text`, `image`, `carousel`, `video` or `audio` |
| `mediaCount`, `imageUrl`, `videoUrl` | how many attachments and where they are |
| `isReply` | whether the post is a reply to another |
| `postId`, `userId`, `shortcode` | stable ids, for joining against your own data |
| `query`, `queryType` | which hashtag or phrase found this post |

Four ready-made views are included — **Posts**, **Engagement**, **Authors**,
**Media** — so the export has the columns for the job without editing.

***

### Input

```json
{
  "hashtags": ["coffee", "specialtycoffee", "coffeeshop"],
  "searchQueries": ["small business marketing"],
  "maxPosts": 0,
  "onlyWithText": false
}
```

| field | |
|---|---|
| `hashtags` | one per line. The `#` is optional; a pasted `threads.net/tag/...` URL works too |
| `searchQueries` | one **topic** per line — `coffee`, `small business marketing`. Threads matches topics, not sentences: measured in a single run, `coffee` returned 21 posts, `small business marketing` returned 20, and `best coffee in Lisbon` returned none at all. If a phrase comes back empty it is genuinely unmatched, not blocked — try a shorter, plainer term |
| `maxPosts` | a ceiling for the **whole run, across every line of input** — not a per-hashtag figure. `0` means everything available |
| `onlyWithText` | drop caption-less photos from the export as well as from the bill |

The same post appearing under two of your hashtags is stored once and charged
once.

***

### Recipes

Four jobs this is actually bought for. Each is the whole input.

**Watch what people say about your brand.** Names get written as plain words far
more often than as tags, so give it both.
`{"hashtags": ["yourbrand"], "searchQueries": ["yourbrand"], "onlyWithText": true}`

**Find creators posting on your topic.** Sort the export by `likeCount`, take
`userUrl` for the ones worth contacting.
`{"hashtags": ["homebarista", "specialtycoffee", "coffeeroasting"]}`

**Follow a launch day.** Schedule the same run hourly; new posts appear on the
tag page as they are published, and duplicates are stored once.
`{"hashtags": ["yourlaunchtag"], "maxPosts": 0}`

**Fill a topic feed.** Twenty lines of input is twenty requests and roughly
300–500 posts, which is a day's worth of material for one subject.
`{"hashtags": ["ai", "machinelearning", "llm", "opensource"]}`

***

### What it costs

**$2.20 per 1,000 posts**, charged per **post that has text**. A post with no
caption is returned with its image URL and costs nothing.

A three-line run — two hashtags and one phrase — returned 53 posts in 17
seconds. At this rate that is about **$0.12**, and the first 100 posts are free
while you evaluate.

Across 85 posts measured on five different tag and search pages, every one of
them had text, so in practice you are paying for what you get. The point of the
rule is the case where you are not.

**Free plan**: everything works the same on the free tier — there is no reduced
mode, no locked fields and no smaller row. The free trial covers the first 100
posts, which is four or five hashtags.

One request per hashtag returns about 900 KB, so a twenty-hashtag run moves
roughly 18 MB and finishes in well under two minutes.

***

### What it does not do

Stated here rather than discovered on your invoice.

- **No paging.** 15–25 posts per hashtag is what Threads offers: the tag page
  says in its own response that it has no further results, so asking for 5 000
  posts from one hashtag returns about 20. Volume comes from listing more
  hashtags and more phrases — twenty hashtags is twenty requests and roughly
  300–500 posts. To follow a hashtag over time, schedule the run daily; new
  posts appear on the tag page as they are published.
- **No follower counts.** The tag page does not carry them, and fetching each
  author's profile to add one column would multiply the cost of a run several
  times over. If you need follower counts, take the `userUrl` column into a
  profile scraper.
- **No replies or comment threads.** Only the posts listed on the tag or search
  page itself.
- **No private or login-only content.** Public posts only.
- **Media URLs expire.** Threads' image and video links are signed and
  time-limited. Download what you need soon after the run; the `postUrl` stays
  valid.

***

### Notes

Posts are returned in the order Threads lists them, which is its own relevance
ranking rather than strict reverse-chronological order. `postedAt` is there to
sort by if you want time order.

Every run writes a `RUN_SUMMARY` record next to the dataset: how many posts were
stored, how many carried text, which hashtags came back empty, how much traffic
the run used, and whether Threads offered a next page on any of them. If a run
returns nothing, it fails loudly and says which of the two reasons applied — an
empty hashtag or a refusal — rather than reporting an empty success.

***

### FAQ

#### Do I need a Threads or Instagram account?

No. No login, no cookies, no API key and no browser. The Actor reads the public
tag and search pages the same way a logged-out visitor does.

#### How many posts can one hashtag give me?

**15 to 25, and there is no second page.** That is Threads' own limit, not a
setting — the tag page states in its own response that it has no further
results. Asking for 5,000 posts from one hashtag still returns about 20.

#### Then how do I get more posts?

Two ways, and they combine. List more hashtags and phrases — twenty lines is
twenty requests and roughly 300–500 posts. And schedule the run: new posts
appear on the tag page as they are published, so a daily run accumulates what a
single run cannot.

#### Why did my search phrase return nothing?

Threads matches **topics, not sentences.** Measured in one run: `coffee`
returned 21 posts, `small business marketing` returned 20, and
`best coffee in Lisbon` returned none. An empty phrase is genuinely unmatched,
not blocked — shorten it to a plainer term.

#### How much does 1,000 posts cost?

**$2.20.** You are charged per post that has text; a caption-less photo arrives
with its image URL and costs nothing. The first 100 posts are free while you
evaluate.

#### Does it work on the free Apify plan?

Yes, with no reduced mode, no locked fields and no smaller row. Apify's free
plan includes $5 of monthly credit, and the 100-post free trial covers four or
five hashtags.

#### Can I get follower counts for the authors?

No. The tag page does not carry them, and fetching every author's profile to add
one column would multiply the cost of a run several times over. Take the
`userUrl` column into a profile scraper if you need them.

#### Will the image and video links keep working?

No — Threads signs them and they expire. Download what you need soon after the
run. The `postUrl` stays valid indefinitely.

#### The same post appears under two of my hashtags. Am I billed twice?

No. It is stored once and charged once, with `query` showing which input found
it first.

### Other scrapers we maintain

Same engine, same pricing model, same promise: flat rows, stated limits,
and no charge for a row that came back empty.

| | |
|---|---|
| **[Google Maps Contact Scraper](https://apify.com/lightmoon/google-maps-contact-scraper)** | Local businesses by category and city — phone, website, split address and opening hours. |
| **[Local Business Phone Scraper](https://apify.com/lightmoon/local-business-phone-scraper)** | The same job from directory sources, with Better Business Bureau accreditation on the row. |
| **[YouTube Influencer Scraper](https://apify.com/lightmoon/youtube-influencer-scraper)** | Creators by niche, with the website and socials they publish and a subscriber filter. |
| **[Telegram Channel Messages Scraper](https://apify.com/lightmoon/telegram-messages-scraper)** | Posts from public channels — text, views, reactions and media links, no account needed. |
| **[RedNote Xiaohongshu Profile Scraper](https://apify.com/lightmoon/rednote-profile-scraper)** | Public RedNote creator profiles in bulk — followers, bio, IP location and their notes. |
| **[ATS Jobs Scraper](https://apify.com/lightmoon/ats-jobs-scraper)** | Open roles straight from company career pages on Greenhouse, Lever, Ashby and Workday. |

# Actor input Schema

## `hashtags` (type: `array`):

One hashtag per line. The `#` is optional, and a pasted tag URL like `https://www.threads.net/tag/coffee` works too. Each hashtag is one request that returns the posts Threads currently shows on that tag page — typically 15 to 25 of them. There is no second page to fetch: Threads itself reports no further results for a tag, so volume comes from listing more hashtags rather than from asking for a bigger number below.

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

One topic per line, searched as words rather than as a tag — use this for subjects people write about without tagging. Keep them short and plain: Threads matches topics, not sentences. Measured in one run, `coffee` returned 21 posts and `small business marketing` returned 20, while `best coffee in Lisbon` returned none. A phrase that comes back empty is genuinely unmatched, not blocked — try a shorter term. Same shape as a hashtag otherwise: one request, 15 to 25 posts, no paging. Leave empty if you only want hashtags.

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

A ceiling for the whole run, across every hashtag and phrase. Set 0 for everything available. A run can never return more than about 25 posts per line of input, so a large number here simply means "all of it".

## `onlyWithText` (type: `boolean`):

A post that is a bare photo with no caption is never charged for, whichever way this is set. Turn it on to keep those out of the export as well.

## Actor input object example

```json
{
  "hashtags": [
    "coffee",
    "smallbusiness"
  ],
  "searchQueries": [
    "small business marketing"
  ],
  "maxPosts": 0,
  "onlyWithText": false
}
```

# Actor output Schema

## `posts` (type: `string`):

No description

## `engagement` (type: `string`):

No description

## `authors` (type: `string`):

No description

## `media` (type: `string`):

No description

## `all` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

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

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "hashtags": [
        "coffee",
        "smallbusiness"
    ],
    "searchQueries": [
        "small business marketing"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lightmoon/threads-hashtag-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 = {
    "hashtags": [
        "coffee",
        "smallbusiness",
    ],
    "searchQueries": ["small business marketing"],
}

# Run the Actor and wait for it to finish
run = client.actor("lightmoon/threads-hashtag-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 '{
  "hashtags": [
    "coffee",
    "smallbusiness"
  ],
  "searchQueries": [
    "small business marketing"
  ]
}' |
apify call lightmoon/threads-hashtag-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lightmoon/threads-hashtag-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/nWsk74ewkLEjRr0Mp/builds/eDk4cbygaiGJDcRXS/openapi.json
