# Threads Scraper - Public Profiles & Post URLs (`benthepythondev/threads-public-scraper`) Actor

Extract public Threads profiles, recent visible posts and exact post URLs with full text, dates, engagement counts, identity checks and duplicate removal.

- **URL**: https://apify.com/benthepythondev/threads-public-scraper.md
- **Developed by:** [Ben](https://apify.com/benthepythondev) (community)
- **Categories:** Social media, Marketing, Business
- **Stats:** 3 total users, 2 monthly users, 56.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 records

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/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: public profiles and post URLs

Read public Threads profiles, recent posts visible on profile pages, or a list of exact public post URLs. Export full source text, publication dates, engagement counts, media URLs and stable IDs to JSON, CSV or Excel.

The public Threads profile currently exposes a short preview before asking visitors to log in. This Actor reads that preview. It does not claim full timeline history, keyword search, follower lists, complete replies or authenticated content. Specific post URLs let you inspect older public posts when you already know their URLs.

**Choose preview or exact-post extraction:** profile pages expose a limited public preview; use known post URLs for specific older posts. Free-plan result events cost **$3 per 1,000 records**, plus the start event. A higher limit does not unlock a complete timeline.

### Quick start

Start with a tested example for public Threads posts:

- [Recent public Threads posts from Meta and NASA](https://apify.com/benthepythondev/threads-public-scraper/examples/meta-nasa-public-threads-posts)

Open the example and choose **Try for free** to copy its input into your account. Check the input and set a maximum run charge before starting; Actor fees still follow the pricing below. When the run finishes, open the run's dataset and select the **Overview** view and download JSON for nested fields or CSV/Excel for a table.

| Your goal | Input to use |
|---|---|
| Recent visible posts | `mode: posts` with `usernames` |
| Account snapshots | `mode: profiles` with `usernames` |
| A known public post | `postUrls` in posts mode |

The [real output example](#example-output) shows the record shape. The [Python workflow](#python-api-workflow) saves your own export to a local JSON file with a $0.10 maximum Actor charge.

### Supported inputs

| Field | Behavior |
|---|---|
| `mode` | `posts` by default; `profiles` returns account metadata. |
| `usernames` | Up to 20 usernames, such as `meta` or `nasa`. The default is `meta`. |
| `postUrls` | Up to 50 exact HTTPS Threads post URLs. These override usernames, including the default username. Requires posts mode. |
| `postedAfter`, `postedBefore` | Optional inclusive UTC dates in YYYY-MM-DD format. Filter only posts actually exposed on the requested public page. |
| `maxResults` | Maximum exported records across the run, between 1 and 200. It cannot expand the source's public preview. |

To collect recent public posts from two organizational accounts:

```json
{
  "mode": "posts",
  "usernames": [
    "meta",
    "nasa"
  ],
  "maxResults": 10
}
```

To read a known public post without adding the default account's recent posts:

```json
{"mode":"posts","postUrls":["https://www.threads.com/@meta/post/Dc4G-rQE17m"],"maxResults":1}
```

Choose profiles mode for account IDs, names, biographies and follower counts. Date filters are rejected in that mode because an account snapshot is not a dated post. No login cookies, account password or third-party data API key is required.

### Example output

One complete record from a verified September 9, 2026 cloud run. Values and public media URLs can change or expire; this is a dated sample.

```json
{
  "record_type": "post",
  "id": "3978960966109060838",
  "url": "https://www.threads.com/@meta/post/Dc4G-rQE17m",
  "username": "meta",
  "author_id": "63053551787",
  "full_name": "Meta",
  "is_verified": true,
  "text": "brb, cloud watching while my meta glasses play the perfect soundtrack",
  "posted_at": "2026-09-04T19:12:11+00:00",
  "likes": 278,
  "replies": 91,
  "reposts": 25,
  "quotes": 0,
  "is_reply": false,
  "reply_to_author": null,
  "image_urls": [],
  "video_urls": [],
  "scraped_at": "2026-09-09T20:11:06.166199+00:00"
}
```

### Output and identity checks

Every row contains `record_type`, `id`, `url`, `username` and `scraped_at`. Post rows include the complete caption supplied by the source, `posted_at` in UTC, author ID, likes, replies, reposts, quotes, reply status and exposed image/video URLs. Profile rows include the public biography, follower count, full name and verification status.

Optional fields remain null when unavailable. A zero count is preserved as zero. A missing count is never converted to zero. Profile and post records have different fields; use `record_type` when loading a mixed dataset into your database.

The parser checks the page's canonical identity when available, filters records to the requested author and, for exact URLs, requires the requested post code. Replies from other accounts are not exported as posts by the requested account. Duplicate post IDs are removed across pages and overlapping inputs. A profile's own visible replies can appear in its public preview and retain their reply flag.

The Meta post in the example supplied the text “brb, cloud watching while my meta glasses play the perfect soundtrack” during local verification on September 9, 2026. This is an example of actual source output, not a promise that engagement counts or public availability will remain unchanged.

### Coverage and failures

The Output tab exposes the dataset and `SUMMARY`. The summary reports pages read, source records, filtered records, duplicate IDs, actual exports and the stop reason. `coverage_complete` remains false: a successful public preview is not a complete account history or all replies to a post.

Dates restrict this visible window. If every exposed post falls outside the dates, a successful run can return zero records. If the page supplies no usable requested records, the Actor fails explicitly instead of reporting a successful empty scrape. A removed post, private account or login requirement may cause that error.

Media URLs may expire. The Actor exports their public URLs and does not download media files. Counts and text reflect the source at observation time. Changes between runs are expected. Retain the stable post ID as your downstream key when comparing exports or importing a scheduled feed.

### Pricing and recurring use

The Free price is $0.003 per exported record plus a $0.00005 start event at the default 512 MB. Ten records cost $0.03005 before plan discounts. Per 1,000 records, Bronze is $2.70, Silver $2.55, and Gold, Platinum and Diamond are $2.40 at the currently saved rates. Start costs $0.000045 on Bronze, $0.0000425 on Silver and $0.000040 on Gold or higher. The Store pricing panel and actual run usage are authoritative.

Only exported records create result events. Source-filtered and duplicate records are not billed as results. The Actor respects Apify's maximum charge control and records a spending-limit stop in its summary. Failed runs can leave a partial dataset, so reconcile repeated imports by stable ID.

Use a saved Task with a fixed organizational-account list for repeat observations, or supply exact URLs from your existing research workflow. An API client, n8n, Make or Zapier can read the resulting dataset. This version does not maintain persistent only-new state; deduplicate repeated runs in the receiving system.

### Python API workflow

Copy the first input JSON above into `input.json` and save this code as `export.py`. In a Python virtual environment, install `apify-client` with `python -m pip install apify-client`. Set `APIFY_TOKEN` to your own Apify token in your environment, then run `python export.py`. Keep the token out of source files and shared screenshots.

```python
import json
import os
from decimal import Decimal
from pathlib import Path

from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("benthepythondev/threads-public-scraper").call(
    run_input=json.loads(Path("input.json").read_text(encoding="utf-8")),
    max_total_charge_usd=Decimal("0.10"),
    memory_mbytes=512,
    timeout_secs=300,
)
if not run or run["status"] != "SUCCEEDED":
    raise RuntimeError(f"Inspect the run before retrying: {run and run['id']}")

rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())
Path("threads-posts.json").write_text(
    json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8"
)
print(f"Saved {len(rows)} records from run {run['id']}")
```

This starts a run in your account, waits for completion and saves `threads-posts.json`. The $0.10 limit bounds Actor charges; it is not a promised bill or result count. Check the printed run ID and server log if the result is shorter than expected. If your client loses its connection, inspect the existing run in Console before starting another one. See the [official Python client](https://docs.apify.com/api/client/python/) for other run options.

Keep `id` as the import key. For an observation history, also retain `scraped_at`; for a current-post table, upsert by ID. Scheduling repeats the public preview and does not expand it into a full account history.

For recurring runs without writing a scheduler, copy a tested public Task into your account and attach an Apify Schedule to that copy. Keep its maximum-charge limit and avoid overlapping runs when using monitor state. The Python example above is also suitable for an existing scheduler; run it from the directory containing `input.json` and provide the token through that scheduler's secret settings.

### Questions

**Can it export an entire account history?** No. Logged-out profile pages expose only their public preview. Known public post URLs are supported separately.

**Does a successful zero-result date filter mean the account posted nothing?** No. It means none of the scanned public preview records matched those bounds.

**Can I search Threads globally?** This version accepts usernames and exact post URLs. It does not advertise global search.

**Can I reuse media or personal information freely?** Public visibility does not remove applicable data protection obligations or copyright restrictions.

### Related tools

[Instagram](https://apify.com/benthepythondev/instagram-intelligence-scraper) provides public profile and timeline observations with date filters. [Meta Ad Library](https://apify.com/benthepythondev/meta-ad-library-scraper) supports advertiser-specific creative monitoring, while [Facebook Pages](https://apify.com/benthepythondev/facebook-pages-scraper) exports public organizational Page information.

Keywords: Threads public scraper, Threads profile data, public posts, exact post URLs, full captions, publication dates, engagement counts, account snapshots, CSV export, research feeds.

# Actor input Schema

## `mode` (type: `string`):

Posts reads recent public profile posts or exact post URLs. Profiles exports account metadata.

## `usernames` (type: `array`):

Up to 20 Threads usernames; ignored when post URLs are supplied.

## `postUrls` (type: `array`):

Up to 50 https://www.threads.com/@username/post/code URLs. Overrides usernames.

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

Inclusive UTC date, YYYY-MM-DD; filters only the public records actually scanned.

## `postedBefore` (type: `string`):

Inclusive UTC date, YYYY-MM-DD. Requires posts mode.

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

Upper bound on exported records; source and charge limits can return fewer.

## Actor input object example

```json
{
  "mode": "posts",
  "usernames": [
    "meta"
  ],
  "maxResults": 20
}
```

# Actor output Schema

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

No description

## `summary` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("benthepythondev/threads-public-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("benthepythondev/threads-public-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 '{}' |
apify call benthepythondev/threads-public-scraper --silent --output-dataset

```

## MCP server setup

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