# Truth Social Posts Scraper (`xtracto/truth-social-posts`) Actor

Scrape public posts from any Truth Social account by handle: text, media, reply and repost links, and engagement counts. No login, no browser.

- **URL**: https://apify.com/xtracto/truth-social-posts.md
- **Developed by:** [Farhan Febrian Nauval](https://apify.com/xtracto) (community)
- **Categories:** Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 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.
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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Truth Social Posts Scraper — Timelines, Media & Engagement by Handle

Scrape any public Truth Social account's posts: text, media, quote/repost links, hashtags and
engagement counts, plus the author's profile on every row. HTTP-only, no account, no browser.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `profiles` | array | *required* | `realDonaldTrump`, `@realDonaldTrump`, or `https://truthsocial.com/@realDonaldTrump` |
| `maxPostsPerProfile` | integer | `100` | Posts per account; costs about this ÷ 20 requests |
| `excludeReplies` | boolean | `false` | Drop posts replying to someone else |
| `onlyMedia` | boolean | `false` | Keep only posts with an image or video |

### Output

One row per post. The author block repeats on every row so the dataset stands on its own.

```jsonc
{
  "_input": "https://truthsocial.com/@realDonaldTrump",
  "_source": "S1-mastodon-api",
  "_scrapedAt": "2026-09-09T09:41:02Z",

  "postId": "117238707792033610",
  "url": "https://truthsocial.com/@realDonaldTrump/117238707792033610",
  "createdAt": "2026-09-09T02:25:12.159Z",
  "contentHtml": "<p>…</p>",
  "text": "…",                      // plain text; "" on an image-only post
  "language": "en", "visibility": "public", "sensitive": false,

  "repliesCount": 480, "reblogsCount": 1431,
  "favouritesCount": 6399, "upvotesCount": 6399, "downvotesCount": 0,

  "isReply": false, "inReplyToId": null, "inReplyToAccountId": null,
  "isRepost": false, "repostOfId": null, "repostOfHandle": null,
  "quoteId": null,

  "mediaAttachments": [{ "type": "image", "url": "…", "previewUrl": "…", "description": null }],
  "mediaCount": 1,
  "cardUrl": "…", "cardTitle": "…",
  "tags": [],                        // as the API reports it — see below
  "hashtags": ["NYSE", "YorkvilleAmerica"],   // read from the text
  "mentions": ["someone"],

  "authorId": "107780257626128497", "authorHandle": "realDonaldTrump",
  "authorDisplayName": "Donald J. Trump", "authorVerified": true,
  "authorFollowers": 13040677, "authorFollowing": 71, "authorStatusesCount": 36517,
  "authorNote": "…", "authorAvatar": "…", "authorWebsite": "www.DonaldJTrump.com",
  "authorLocation": "", "authorCreatedAt": "…"
}
```

### Three things worth knowing

**`tags` is unreliable, so `hashtags` is derived separately.** A post reading
`#NYSE #YorkvilleAmerica` came back with `tags: []` while another populated `tags` correctly — two
of the three tagged posts in one sample disagreed with their own text. Both fields ship: `tags`
exactly as the API reports it, `hashtags` read out of the post text. Use `hashtags` unless you
specifically want the site's own answer.

**Page sizes vary, and a short page does not mean the end.** One account returned 18, then 9, then
20, then 14 items per page — the server drops items *after* paginating. Treating a short page as
the end of the timeline silently truncates: on that account it stopped at 18 posts out of 1368.
This actor pages until a page comes back empty or the cursor stops advancing.

**Empty `text` is data.** Image-only posts have a body of literally `<p></p>`. That is a real post
with no words, not a parse failure — check `mediaCount` rather than assuming a blank row is broken.

### Scope

Truth Social runs a Mastodon fork, and only part of that API is open anonymously. Available:
account lookup by handle, and an account's own posts. **Not available** (all answer `401`):
followers, following, `/api/v1/trends`, trending truths and search. Hashtag timelines answer `200`
with an empty list for every tag tried, so they are treated as closed too.

### Errors

Every input yields at least one row, so a join against your input list stays aligned.

| `_error` | Meaning |
|---|---|
| `invalid_input` | Not a Truth Social handle or profile URL |
| `profile_not_found` | 404 — no account with that handle |
| `no_posts` | The account exists but returned no public posts |
| `page_failed` | A page failed mid-timeline; earlier posts are still in the dataset |
| `blocked` | Every TLS profile was refused |
| `unexpected_shape` | Lookup returned 200 with no account id — the API contract changed |

If every input fails, the run itself fails rather than reporting success over an empty dataset.

### Proxies and rate limits

**Residential proxies are required.** Apify's shared datacenter pool was refused with `HTTP 403` on
all four TLS profiles; residential succeeds. The input schema defaults to residential accordingly.

Truth Social limits per IP hard — roughly ten plain requests earns a `429` carrying
`retry-after: 9`. The actor paces its calls, honours that header exactly rather than guessing, and
takes a **fresh proxy exit IP per request** so a run spreads across addresses instead of walking one
into the limit. A `429` retries the same TLS fingerprint rather than burning a rung of the ladder,
since a rate limit says nothing about the fingerprint.

# Actor input Schema

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

Accounts to scrape. Accepts a handle (realDonaldTrump), an @handle, or a profile URL (https://truthsocial.com/@realDonaldTrump).

## `maxPostsPerProfile` (type: `integer`):

The API returns 20 posts per request regardless of what is asked for, so this many posts costs roughly this number divided by 20 requests.

## `excludeReplies` (type: `boolean`):

Skip posts that reply to someone else.

## `onlyMedia` (type: `boolean`):

Keep only posts carrying an image or video.

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

Residential is required and is the default. Apify's shared datacenter pool gets HTTP 403 from Truth Social on every attempt. The site also rate-limits per IP (~10 requests earns a 429), so the actor takes a fresh exit IP per request.

## Actor input object example

```json
{
  "profiles": [
    "https://truthsocial.com/@realDonaldTrump"
  ],
  "maxPostsPerProfile": 100,
  "excludeReplies": false,
  "onlyMedia": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

Dataset items shown in the 'Posts' view.

## `items` (type: `string`):

Every record this run produced, with all fields, as JSON.

# 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://truthsocial.com/@realDonaldTrump"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("xtracto/truth-social-posts").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://truthsocial.com/@realDonaldTrump"] }

# Run the Actor and wait for it to finish
run = client.actor("xtracto/truth-social-posts").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "profiles": [
    "https://truthsocial.com/@realDonaldTrump"
  ]
}' |
apify call xtracto/truth-social-posts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,xtracto/truth-social-posts"
        }
    }
}
```

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/OI0YCE9dBoRlgPSI3/builds/cWqeB3ybWprPwjOv0/openapi.json
