# Truth Social Scraper: Engagement Analytics (`scrapier/truth-social-scraper`) Actor

Truth Social Scraper: Engagement Analytics: Extract posts, profiles, likes, replies, reposts, views, timestamps, hashtags, and engagement metrics. Analyze audience activity, track content performance, research trends, monitor competitors, and build structured datasets for Truth Social analytics.

- **URL**: https://apify.com/scrapier/truth-social-scraper.md
- **Developed by:** [Scrapier](https://apify.com/scrapier) (community)
- **Categories:** Social media, Lead generation, Other
- **Stats:** 4 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/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

### Truth Social Scraper — Posts, Engagement Tiers and Rates

Truth Social Scraper: Engagement Analytics pulls public posts from any Truth Social profile — or a specific list of post IDs and URLs — and scores every post with **totalEngagement**, a per-metric **engagementBreakdown**, an **engagementRatePer1kFollowers**, and an **engagementTier** of `low`, `medium`, `high`, or `viral`, computed directly from the platform's own reply, reblog, and favourite counts. Every row lands in your Apify dataset as structured JSON, ready to pass straight to an LLM, load into a spreadsheet, or feed a monitoring pipeline. Set a minimum engagement threshold or sort by score, and the ranking work is already done before the data leaves the run.

### What is Truth Social Scraper: Engagement Analytics?

Truth Social Scraper: Engagement Analytics is an Apify Actor that queries Truth Social's public, Mastodon-compatible timeline and status API for one or more profile handles (or specific post IDs/URLs), and returns every post as a JSON row already scored for engagement. No Truth Social account, password, or API key is required — the Actor reads only what any visitor's browser can load from a public profile.

What sets it apart from a plain post scraper is that the scoring happens inside the run, not in a spreadsheet afterward: **totalEngagement**, **engagementBreakdown**, **engagementRatePer1kFollowers**, and **engagementTier** are computed and attached to every post before it's saved, and you can filter or rank on those numbers using the input parameters directly — no post-processing step needed. Whether you're building a Trump Truth Social monitor, tracking a set of political accounts, or pulling posts for a research dataset, the output is the same typed JSON shape on every run, which makes it a practical Truth Social API alternative for anyone who needs consistent, engagement-scored data without hand-rolling the scoring math themselves.

Real query controls exposed in the input:

- **Profile mode** — one or more `truthSocialUsername` handles, one per line, `@` optional
- **Single-post mode** — `fetchSinglePostByIdOrUrl` accepts post IDs or full `truthsocial.com` links, and skips profile mode entirely when populated
- **Reply / original filtering** — `onlyFetchReplies` toggles between original posts and replies only
- **Media filtering** — `onlyFetchPostsWithMedia` keeps only posts with at least one image or video attachment
- **Incremental mode** — `continueFromLastPostId` fetches only posts newer than the last one seen for that username
- **Cursor mode** — `startFromPostId` (an 18-digit post ID) pages backward from a specific point in a profile's history
- **Engagement filter and sort** — `minEngagement` drops low-performing posts before they're saved; `sortByEngagement` ranks a profile's kept posts highest-first

### What data can you get with Truth Social Scraper: Engagement Analytics?

Every run returns post content, the computed engagement analytics, an account snapshot, and — where present — the nested repost/quote object.

| Result type | Extracted fields | Primary use case |
| --- | --- | --- |
| Post content & metadata | `id`, `created_at`, `content`, `url`, `in_reply_to_id`, `media_attachments` | Archiving posts, building a timeline, feeding an LLM |
| Computed engagement analytics | `totalEngagement`, `engagementBreakdown` (`replies`, `reblogs`, `favourites`), `engagementRatePer1kFollowers`, `engagementTier` | Ranking, alerting, virality tracking |
| Account snapshot | `account.id`, `account.username`, `account.followers_count` (read directly by the Actor; the rest of the account object passes through unchanged) | Reach and audience context for each post |
| Nested repost / quote | `reblog`, `quote` — native Truth Social objects, cleaned the same way as the parent post | Thread context, tracing what a profile is amplifying |

#### Computed engagement analytics

This is the capability the other Truth Social scrapers on Apify don't have: raw interaction counts turned into a ready-to-use score, not just three separate numbers you have to combine yourself. For every post, the Actor reads `replies_count`, `reblogs_count`, and `favourites_count` from Truth Social's own API response and computes:

```json
{
  "totalEngagement": 30485,
  "engagementBreakdown": { "replies": 2435, "reblogs": 5407, "favourites": 22643 },
  "engagementRatePer1kFollowers": 3.5701,
  "engagementTier": "viral"
}
```

`totalEngagement` is `replies_count + reblogs_count + favourites_count`, each coerced to a non-negative integer (a missing or non-numeric count becomes `0` — never faked). `engagementRatePer1kFollowers` is `totalEngagement ÷ (account.followers_count ÷ 1000)`, rounded to 4 decimal places, and is `null` whenever the account's `followers_count` is missing or `0`. `engagementTier` buckets `totalEngagement` against fixed thresholds:

| `totalEngagement` | `engagementTier` |
| --- | --- |
| 10,000 or more | `viral` |
| 1,000 – 9,999 | `high` |
| 100 – 999 | `medium` |
| 0 – 99 | `low` |

These four fields are computed only on the top-level post — a nested `reblog` or `quote` keeps its own native `replies_count`/`reblogs_count`/`favourites_count` but does not get its own `totalEngagement`, `engagementTier`, or `engagementRatePer1kFollowers`. If you need engagement scoring on a reposted or quoted status too, treat its `id` as its own post and fetch it directly through `fetchSinglePostByIdOrUrl`.

#### Account snapshot embedded in every post

Each post carries Truth Social's own `account` object for its author. The Actor reads `account.followers_count` directly to compute `engagementRatePer1kFollowers`, and the dataset view surfaces the account object alongside the post so you don't need a second lookup to know who posted it or how large their audience is. The rest of the account object — display name, avatar, bio, and so on — passes through exactly as Truth Social's API returns it. When `followers_count` is missing, zero, or not a usable number, `engagementRatePer1kFollowers` is written as `null` instead of a computed value — the Actor never substitutes an estimated or default follower count to force a rate out of incomplete data.

### Why not build this yourself?

Truth Social does not publish a public developer API the way X/Twitter or Reddit do. The only interface available is the same internal, Mastodon-compatible endpoint that Truth Social's own web and mobile apps call — and it sits behind Cloudflare bot protection. A DIY scraper has to solve Cloudflare challenge pages, TLS fingerprinting, proxy rotation, and 403/429 backoff, and keep re-solving them every time Truth Social's edge configuration changes.

This Actor already implements that layer: a three-tier connection escalation (direct → Apify auto-proxy → Apify residential proxy), with Chrome-131 TLS fingerprinting applied even on the direct tier so a bare, un-proxied request doesn't look like a generic HTTP library to Cloudflare, a curl-cffi Chrome-131 TLS-impersonation fallback that kicks in on residential when a plain HTTPS client gets a 403, exponential backoff on 5xx/429 responses (honoring a server's `Retry-After` header, capped at 10 minutes), and residential IP-session rotation on repeated blocks — up to 8 rotations before a request gives up. Timelines are paged 40 posts at a time, with a roughly 4.5-second pause (plus jitter) between pages so pagination itself doesn't trip Cloudflare. Across all of that, a single request can be attempted up to 47 times across tiers before the run reports a real failure. None of that is something you configure — it runs automatically on every request, so you get a `truthSocialUsername` in and structured, engagement-scored posts out, without maintaining a Cloudflare-evasion stack yourself.

### What is the difference between engagement count and engagement rate?

Engagement **count** and engagement **rate** answer different questions, and mixing them up leads to the wrong conclusion about which post actually performed best. `totalEngagement` is an absolute count — replies plus reblogs plus favourites — so it naturally favors whichever account has the largest audience; a post from an account with 8 million followers will usually out-count one from an account with 80,000 followers even if the smaller account's post resonated far more with the people who actually saw it.

`engagementRatePer1kFollowers` normalizes for that: it's `totalEngagement` divided by the account's `followers_count` in thousands, so a post is scored against its own audience size, not against every other account in your dataset. That distinction matters most once you're comparing posts across multiple profiles of different sizes — ranking by `totalEngagement` alone will almost always surface the biggest account's posts, while ranking by `engagementRatePer1kFollowers` surfaces which posts actually moved their own audience.

Truth Social Scraper: Engagement Analytics returns both on every post — `totalEngagement` for absolute scale, `engagementRatePer1kFollowers` for relative resonance — plus `engagementTier`, which buckets the absolute count into `low`/`medium`/`high`/`viral` for quick filtering. Use `sortByEngagement` (which ranks by the absolute `totalEngagement`) when you want a single profile's biggest posts; read `engagementRatePer1kFollowers` yourself when comparing across profiles of different sizes.

### ⚡ How to scrape Truth Social with Truth Social Scraper: Engagement Analytics?

1. Open **Truth Social Scraper: Engagement Analytics** in the Apify Console (or find it on the Apify Store) and click **Run**.
2. Enter one or more handles in **truthSocialUsername** — or expand **Advanced** and paste post IDs/URLs into **fetchSinglePostByIdOrUrl** instead.
3. Set your query controls: **maxPosts**, **onlyFetchReplies**, **onlyFetchPostsWithMedia**, **minEngagement**, **sortByEngagement**.
4. Click **Start**.
5. Once the run finishes, download the dataset as JSON, CSV, or Excel from the **Storage** tab, or pull it with the Apify API.

A real request body, using the Actor's actual parameter names:

```json
{
  "truthSocialUsername": ["realDonaldTrump"],
  "maxPosts": 100,
  "onlyFetchPostsWithMedia": false,
  "minEngagement": 500,
  "sortByEngagement": true
}
```

#### How to run multiple queries in one job

`truthSocialUsername` accepts a list, one handle per line. Add as many profiles as you want scraped in a single run — each one is fetched in turn, up to `maxPosts` posts per profile, and each keeps its own incremental bookmark when `continueFromLastPostId` is on, so monitoring ten accounts doesn't mean ten separate Actor runs or ten separate bookmarks to track by hand. There's no documented concurrency setting to raise; multiple usernames in one run are processed sequentially, not in parallel, and `minEngagement`/`sortByEngagement` are applied per profile, so each profile's posts are ranked against its own timeline, not pooled together with every other profile in the run.

### ⬇️ Input

Every field below comes directly from the Actor's input schema. Nothing is required — the schema's `required` list is empty — but the run needs either a populated `truthSocialUsername` or a non-empty `fetchSinglePostByIdOrUrl`; `truthSocialUsername` ships prefilled with `realDonaldTrump` so a default run always has a target unless you clear it.

| Parameter | Required | Type | Default / range | Description |
| --- | --- | --- | --- | --- |
| `truthSocialUsername` | No | array of strings | prefill `["realDonaldTrump"]` | Profile handles to scrape, one per line. The `@` is optional. Ignored when `fetchSinglePostByIdOrUrl` has entries. |
| `maxPosts` | No | integer | default `20`; min `5`; max `5000` | Number of posts to collect from a profile's timeline. Not used in single-post mode. |
| `continueFromLastPostId` | No | boolean | default `false` | Only fetch posts newer than the last post ID saved for that username. Cannot be combined with `startFromPostId`. |
| `onlyFetchReplies` | No | boolean | default `false` | `false` = normal posts only, replies excluded. `true` = replies only. |
| `onlyFetchPostsWithMedia` | No | boolean | default `false` | Keep only posts with at least one item in `media_attachments`. |
| `cleanContent` | No | boolean | default `true` | Strip HTML from `content` (and from any nested `reblog`/`quote` content) into plain text. |
| `minEngagement` | No | integer | default `0`; min `0` | Drop any post whose `totalEngagement` is below this value, before it's pushed to the dataset. |
| `sortByEngagement` | No | boolean | default `false` | Buffer each profile's kept posts and push them ranked by `totalEngagement`, highest first — still capped at `maxPosts`. |
| `startFromPostId` | No | string | — | An 18-digit post ID; fetch posts older than this ID. Cannot be combined with `continueFromLastPostId`. |
| `fetchSinglePostByIdOrUrl` | No | array of strings | — | One or more post IDs or full `truthsocial.com` links. When non-empty, profile-timeline mode is skipped entirely. |
| `proxyConfiguration` | No | object (Apify Proxy editor) | prefill `{ "useApifyProxy": true }` | Standard Apify Proxy configuration. Optional for most runs — the Actor escalates to residential automatically when needed. |

Example full input:

```json
{
  "truthSocialUsername": ["realDonaldTrump", "andypesalva"],
  "maxPosts": 200,
  "continueFromLastPostId": false,
  "onlyFetchReplies": false,
  "onlyFetchPostsWithMedia": false,
  "cleanContent": true,
  "minEngagement": 100,
  "sortByEngagement": true,
  "startFromPostId": "",
  "fetchSinglePostByIdOrUrl": [],
  "proxyConfiguration": { "useApifyProxy": true }
}
```

**Common pitfall:** setting `continueFromLastPostId` and `startFromPostId` together fails the run before it starts — "Don't combine 'Pick up where you left off' with 'Start older than this post'." Pick one. `startFromPostId` also has to be exactly 18 digits; anything shorter, longer, or non-numeric is rejected.

Each line in `truthSocialUsername` is classified independently, not just treated as a bare handle: a plain handle or a full `truthsocial.com` profile URL is fetched as a timeline, but an entry that parses as a post ID (17–20 digits) or a full post link is fetched as a single post instead — so you can mix profile handles and specific post links or IDs in the same list and get the right thing back for each line without moving anything into `fetchSinglePostByIdOrUrl`.

When `cleanContent` is on, the Actor doesn't just strip tags — `<br>` and `<p>` become spaces, closing `</p>`, `</div>`, and `</blockquote>` tags add a space, HTML entities are unescaped, and repeated whitespace is collapsed to single spaces, so `content` reads as plain, trimmed text instead of raw markup. The same cleanup is applied recursively to a nested `reblog` or `quote` object's `content`, not just the top-level post.

If you're calling this Actor programmatically and already have input built for the base field names, the Actor also accepts legacy aliases: `username` for `truthSocialUsername`, `useLastPostId` for `continueFromLastPostId`, `startFromId` for `startFromPostId`, `onlyReplies` for `onlyFetchReplies`, `onlyMedia` for `onlyFetchPostsWithMedia`, and `singlePostId` (or `urls`) for `fetchSinglePostByIdOrUrl`. The current schema names take precedence when both are present.

### ⬆️ Output

Each pushed row is the complete post object exactly as Truth Social's API returns it, plus the four computed engagement fields. Nothing the Actor reads is stripped — `id`, `created_at`, `content`, `url`, `in_reply_to_id`, `media_attachments`, the full `account` object, and (when present) the nested `reblog`/`quote` objects all pass through, alongside `totalEngagement`, `engagementBreakdown`, `engagementRatePer1kFollowers`, and `engagementTier`. Truth Social's response can include additional native fields beyond the ones this Actor reads or filters on — those pass through unmodified too. Results are available from the **Storage** tab in any format the Apify dataset export supports, including JSON, CSV, and Excel.

The dataset's default table view — named **Posts**, inside the **Collected Truth Social posts** dataset — only surfaces 10 columns for readability: `id`, `created_at`, `content`, `totalEngagement`, `engagementTier`, `engagementRatePer1kFollowers`, `url`, `account`, `media_attachments`, and `engagementBreakdown`. That view is a display subset, not the full row — switch to the **JSON** view in Storage, or export the dataset, to see everything else the row carries, including `replies_count`, `reblogs_count`, `favourites_count`, `in_reply_to_id`, and the nested `reblog`/`quote` objects.

You're only charged for rows that actually reach the dataset. Each pushed row fires one `row_result` pay-per-event charge (see the Actor's **Pricing** tab for the current rate) — a post dropped by `minEngagement`, excluded by `onlyFetchReplies`/`onlyFetchPostsWithMedia`, or a 404 in single-post mode is never pushed, so it's never charged.

#### Scraped results

```json
[
  {
    "id": "113847449027767807",
    "created_at": "2026-05-18T04:23:30.018Z",
    "content": "Coming up, THE MOST IMPORTANT WEEKEND, PLUS, IN AMERICAN HISTORY. ENJOY! DJT",
    "url": "https://truthsocial.com/@realDonaldTrump/113847449027767807",
    "in_reply_to_id": null,
    "media_attachments": [],
    "account": {
      "id": "107780257626128497",
      "username": "realDonaldTrump",
      "display_name": "Donald J. Trump",
      "followers_count": 8541456,
      "following_count": 71,
      "statuses_count": 24750
    },
    "reblog": null,
    "quote": null,
    "replies_count": 2435,
    "reblogs_count": 5407,
    "favourites_count": 22643,
    "totalEngagement": 30485,
    "engagementBreakdown": { "replies": 2435, "reblogs": 5407, "favourites": 22643 },
    "engagementRatePer1kFollowers": 3.5701,
    "engagementTier": "viral"
  },
  {
    "id": "113852201118845201",
    "created_at": "2026-05-19T11:02:07.441Z",
    "content": "Thank you to everyone who came out today. Historic turnout!",
    "url": "https://truthsocial.com/@realDonaldTrump/113852201118845201",
    "in_reply_to_id": null,
    "media_attachments": [
      { "id": "9910233", "type": "image", "url": "https://static-assets-1.truthsocial.com/example.jpg" }
    ],
    "account": {
      "id": "107780257626128497",
      "username": "realDonaldTrump",
      "display_name": "Donald J. Trump",
      "followers_count": 8541456,
      "following_count": 71,
      "statuses_count": 24751
    },
    "reblog": null,
    "quote": null,
    "replies_count": 512,
    "reblogs_count": 940,
    "favourites_count": 6104,
    "totalEngagement": 7556,
    "engagementBreakdown": { "replies": 512, "reblogs": 940, "favourites": 6104 },
    "engagementRatePer1kFollowers": 0.8846,
    "engagementTier": "high"
  },
  {
    "id": "113860112207730933",
    "created_at": "2026-05-21T09:14:52.109Z",
    "content": "Great meeting today.",
    "url": "https://truthsocial.com/@realDonaldTrump/113860112207730933",
    "in_reply_to_id": null,
    "media_attachments": [],
    "account": {
      "id": "107780257626128497",
      "username": "realDonaldTrump",
      "display_name": "Donald J. Trump",
      "followers_count": 8541456,
      "following_count": 71,
      "statuses_count": 24752
    },
    "reblog": null,
    "quote": null,
    "replies_count": 41,
    "reblogs_count": 23,
    "favourites_count": 210,
    "totalEngagement": 274,
    "engagementBreakdown": { "replies": 41, "reblogs": 23, "favourites": 210 },
    "engagementRatePer1kFollowers": 0.0321,
    "engagementTier": "medium"
  }
]
```

### How can I use the data extracted with Truth Social Scraper: Engagement Analytics?

- **Political and media monitoring teams:** run a watchlist of handles with `sortByEngagement` on to see which posts from a profile are landing hardest right now, using `engagementTier` to prioritize what gets a human look first instead of scrolling a raw timeline in arrival order.
- **AI engineers and LLM developers:** an agent issues a run with `truthSocialUsername` or `fetchSinglePostByIdOrUrl`, receives back structured JSON with `content`, `totalEngagement`, and `engagementTier` already attached, and passes it to the model as grounded context — no separate scoring step, sentiment model, or post-processing pipeline required before the data is usable.
- **Market and social researchers:** track `engagementRatePer1kFollowers` across accounts of very different audience sizes to compare relative resonance, not just raw counts that favor whoever has the most followers — useful for cross-account studies where absolute engagement numbers alone would just reflect follower count.
- **Communications and PR teams:** set `minEngagement` to filter out noise and keep only posts that crossed a threshold worth reporting on, then export straight to CSV for a weekly summary without manually sorting a spreadsheet by a raw like-count column afterward.

### 📈 How do you monitor engagement over time?

Engagement is not static — a post's `totalEngagement` and `engagementTier` at the moment you scrape it is a snapshot, and re-running the same query later gives you a new one. Monitoring means running the same `truthSocialUsername` set on a schedule and diffing what changed between runs.

The fields to diff between two runs of the same post `id`: `totalEngagement` (has it grown, and by how much), `engagementBreakdown` (is growth coming from replies, reblogs, or favourites), `engagementRatePer1kFollowers` (is it resonating relative to the account's audience, not just in absolute terms), and `engagementTier` (has it crossed a bucket boundary — `medium` to `high`, or `high` to `viral`). A post crossing into `viral` between two runs is a stronger signal than any single-run number.

A concrete workflow: schedule a run across your watchlist every few hours with `sortByEngagement` on, keep each run's dataset, and compare the `totalEngagement` for matching `id`s against the previous run — alert when a post's `engagementTier` changes or when `totalEngagement` jumps past a threshold you care about. For new-post monitoring specifically, turn on `continueFromLastPostId` so each scheduled run only returns posts you haven't seen yet for that username; this bookmark is stored in the run's key-value store, so it persists across runs when the Actor is triggered repeatedly from the same Apify Task rather than as one-off ad-hoc runs. Point the schedule at an **Apify Schedule** attached to that Task for the delivery mechanism.

### Integrate Truth Social Scraper: Engagement Analytics and automate your workflow

Truth Social Scraper: Engagement Analytics works with any language or tool that can send an HTTP request through the Apify API, or with the official Apify client libraries.

#### REST API with Python

Install the official client (`pip install apify-client`), authenticate with an Apify API token, call the Actor by its `<username>/<actor-name>` slug, wait for the run to finish, and iterate the resulting dataset — the same pattern works for any Apify Actor, not just this one:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("<YOUR_USERNAME>/truth-social-engagement-analyzer").call(
    run_input={
        "truthSocialUsername": ["realDonaldTrump"],
        "maxPosts": 100,
        "minEngagement": 500,
        "sortByEngagement": True,
    }
)

for post in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(post["id"], post["totalEngagement"], post["engagementTier"])
```

Swap `<YOUR_APIFY_TOKEN>` for a token from your Apify account's **Integrations** settings, and `<YOUR_USERNAME>` for the username the Actor is published under — the slug format is the same one shown in this Actor's Apify Console URL and API tab. The same call works from the JavaScript/Node.js `apify-client` package, or from any HTTP client hitting the Apify REST API directly, since the underlying endpoint is language-agnostic.

#### MCP for query-grounded AI agents

Any Actor published on the Apify Store — including this one — is callable as a tool through Apify's hosted Model Context Protocol server, without a dedicated MCP build step for this Actor specifically:

```json
{
  "mcpServers": {
    "apify": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://mcp.apify.com/?tools=%3CYOUR_USERNAME%3E/truth-social-engagement-analyzer",
        "--header",
        "Authorization: Bearer <YOUR_APIFY_TOKEN>"
      ]
    }
  }
}
```

An agent in Claude Desktop, Cursor, or another MCP-compatible client can then issue a query, receive the scored posts as structured JSON, and generate an answer grounded in current Truth Social activity instead of training-data knowledge. Because the engagement fields are already computed server-side, the agent doesn't need a second tool call or a scoring prompt to work out which posts mattered most — `engagementTier` and `totalEngagement` are already sitting on the object it gets back.

#### Scheduled monitoring and delivery

The Actor itself has no built-in webhook or polling logic — that's handled at the Apify platform level. Attach an **Apify Schedule** to a saved Task to run this Actor automatically on an interval, and use **Apify webhooks** to fire your own endpoint the moment a run finishes, so new rows can flow into a database, alerting tool, or dashboard without you polling for them. Running from a saved Task (rather than one-off runs from the Actor's own page) also matters for `continueFromLastPostId`: the bookmark the Actor writes to its key-value store is tied to that run's storage, so repeated Task runs are what makes the incremental mode behave as "only new posts since last time" across a schedule.

### Is it legal to scrape Truth Social posts?

Scraping public Truth Social posts is generally lawful in the United States — courts have held that accessing data a website makes available to any visitor without a login does not violate the Computer Fraud and Abuse Act (*hiQ Labs, Inc. v. LinkedIn Corp.*, 9th Cir., 2019). Truth Social Scraper: Engagement Analytics returns only what a public profile shows any logged-out visitor; it does not access private accounts, comments requiring authentication, or anything behind a login wall.

Because posts carry account-level personal data (usernames, display names, follower counts), GDPR and CCPA principles around processing personal data can still apply depending on who you are and what you do with it — collection alone is not automatically exempt just because the source is public. Separately, Truth Social's own Terms of Service govern automated access to the platform regardless of data-protection law. Scraping for AI training and scraping for day-to-day monitoring carry different risk profiles — consult your legal team before any commercial use case involving bulk data storage.

### ❓ Frequently asked questions

#### How does Truth Social Scraper: Engagement Analytics compute totalEngagement and engagementTier?

`totalEngagement` is the sum of `replies_count`, `reblogs_count`, and `favourites_count` from Truth Social's own API response for that post, each floored at zero if missing or non-numeric. `engagementTier` buckets that sum: `viral` at 10,000+, `high` from 1,000–9,999, `medium` from 100–999, `low` below 100. Both fields are computed on every post the Actor pushes — there's no toggle to turn the calculation off.

#### How many posts does it return per profile?

Up to `maxPosts`, which defaults to 20 and can be set between 5 and 5,000 per profile per run. In single-post mode (`fetchSinglePostByIdOrUrl`), `maxPosts` is ignored — you get exactly the posts you listed, minus any that return a 404.

#### Can I filter posts by engagement score or media before they're saved?

Yes. Set `minEngagement` to drop any post whose `totalEngagement` falls below that value — filtered posts are never pushed to the dataset, so they're never charged. Turn on `onlyFetchPostsWithMedia` to keep only posts with at least one image or video attachment, independently of the engagement filter.

#### How does Truth Social Scraper: Engagement Analytics handle Truth Social's anti-bot measures?

It escalates through three connection tiers — direct, Apify's automatic proxy, and Apify residential proxy — switching up a tier on a block or proxy error, and rotating to a fresh residential session on repeated 403s. On residential, a curl-cffi request with Chrome-131 TLS impersonation is tried before falling back to a plain HTTPS client. HTTP 429 responses honor the server's `Retry-After` header when present, backing off up to 10 minutes between attempts; other retryable errors use exponential backoff. No input parameter controls this — it runs automatically.

#### How do I fetch a single post instead of a whole profile timeline?

Add the post's ID (a 17–20 digit number) or its full `truthsocial.com` link to `fetchSinglePostByIdOrUrl`. As soon as that list has an entry, profile-timeline mode is skipped entirely, even if `truthSocialUsername` is also filled in — only the listed posts are fetched and scored.

#### How do I monitor a profile's engagement over time without duplicate posts?

Turn on `continueFromLastPostId`. Each run for a given username only fetches posts newer than the last one it saved for that username, using a bookmark stored in the run's key-value store. Pair it with an **Apify Schedule** on a saved Task so the same store — and bookmark — is reused across runs.

#### Does Truth Social Scraper: Engagement Analytics work with Claude, ChatGPT, and AI agent frameworks?

Yes. It's callable as an HTTP endpoint through the Apify API by any agent framework, and it's reachable through Apify's hosted MCP server using the command shown above — which lets an MCP-compatible agent (Claude Desktop, Cursor, and others) call it as a tool and get back live, engagement-scored posts mid-conversation.

#### How does Truth Social Scraper: Engagement Analytics compare to other Truth Social scrapers on Apify?

As observed on their public Apify Store listings on 2026-07-26: `bovi/truth-social-scraper` returns raw upvote/downvote and link-card fields but no engagement scoring, tiering, or per-follower rate. `muhammetakkurtt/truth-social-scraper` returns the full raw Mastodon-style status object (visibility, language, tags, poll, and more) plus per-username incremental tracking, but likewise does no engagement computation, filtering, or ranking — that has to happen after export — and its own docs state that a post ID which can't be found still incurs a charge. Neither exposes a built-in `minEngagement` filter or a `sortByEngagement` ranking option. Truth Social Scraper: Engagement Analytics adds the computed `totalEngagement`, `engagementBreakdown`, `engagementRatePer1kFollowers`, and `engagementTier` on every post, plus both of those controls, and does not charge for a post that returns a 404.

#### What happens if a profile doesn't exist or a post has been deleted?

For profile mode, the Actor looks up the account by handle first; if that lookup fails, the run stops with an error rather than silently returning an empty dataset. For single-post mode or a post URL in the profile list, a post that returns a 404 (deleted, or an invalid ID) is skipped and logged — the run continues with the remaining targets, and you are not charged for the skipped post. Only public accounts are readable at all; there's no separate handling for private or deactivated profiles beyond the errors Truth Social's own API returns for them.

#### Can I use it without managing proxies or Truth Social credentials?

Yes. No Truth Social login, password, or API key is required — the Actor only reads public data. Leaving `proxyConfiguration` at its default (`useApifyProxy: true`) is enough; the Actor's own connection escalation handles routing through Apify's proxy tiers as needed, so you don't have to pick a proxy group or manage sessions yourself.

### 💬 Your feedback

Found a bug or a field that doesn't match what Truth Social actually returns? Let us know through this Actor's Issues tab in Apify Console, or reach Scrapier through your Apify account's contact options — reports like this are what keep the Actor matched to Truth Social's API as it changes.

# Actor input Schema

## `truthSocialUsername` (type: `array`):

✏️ Add one or more profile handles (example: **realDonaldTrump**). One entry per line. The **@** symbol is optional — we’ll figure it out.

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

🎯 Number of posts to collect from that profile’s feed (between **5** and **500**). Not used when you only fetch single posts in Advanced.

## `continueFromLastPostId` (type: `boolean`):

✅ **On:** only *new* posts since your last successful run for this profile.
⬜ **Off:** fetch fresh from the top of the feed (within your limit).

## `onlyFetchReplies` (type: `boolean`):

⬜ **Off:** normal posts (replies hidden).
✅ **On:** only reply posts.

## `onlyFetchPostsWithMedia` (type: `boolean`):

✅ **On:** keep posts that include images or video.
⬜ **Off:** everything (text-only included).

## `cleanContent` (type: `boolean`):

✅ **On (recommended):** plain text you can read at a glance.
⬜ **Off:** keep original formatting.

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

🎯 Only keep posts whose **totalEngagement** (replies + reblogs + favourites) is at least this value. Posts below the threshold are dropped from the dataset. Leave at **0** to keep everything.

## `sortByEngagement` (type: `boolean`):

✅ **On:** each profile's posts are collected, then written to the dataset ranked by **totalEngagement** (highest first). Still respects your post limit.
⬜ **Off:** posts are saved live in feed order as they load.

## `startFromPostId` (type: `string`):

📌 Optional **18-digit** post ID — we’ll load posts *older* than this. Don’t use together with **Pick up where you left off**.

## `fetchSinglePostByIdOrUrl` (type: `array`):

➕ Add post IDs or full Truth post links — one per line. When this list has items, **profile mode is skipped** and only these posts are saved.

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

🔐 If the site is picky about traffic, Apify can route through proxies you’ve enabled on your account. Totally optional for many runs.

## Actor input object example

```json
{
  "truthSocialUsername": [
    "realDonaldTrump"
  ],
  "maxPosts": 20,
  "continueFromLastPostId": false,
  "onlyFetchReplies": false,
  "onlyFetchPostsWithMedia": false,
  "cleanContent": true,
  "minEngagement": 0,
  "sortByEngagement": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# 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 = {
    "truthSocialUsername": [
        "realDonaldTrump"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapier/truth-social-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 = {
    "truthSocialUsername": ["realDonaldTrump"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapier/truth-social-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 '{
  "truthSocialUsername": [
    "realDonaldTrump"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call scrapier/truth-social-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapier/truth-social-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/ES9do0Z6WgKyeRIBR/builds/Nnx0ZHmyRcgjmvixn/openapi.json
