Truth Social Scraper | Profiles, Posts & Search avatar

Truth Social Scraper | Profiles, Posts & Search

Pricing

from $1.70 / 1,000 dataset items

Go to Apify Store
Truth Social Scraper | Profiles, Posts & Search

Truth Social Scraper | Profiles, Posts & Search

Scrape public Truth Social profiles, posts, media, metrics, author data, and complete source objects using profile URLs, post URLs, or search queries.

Pricing

from $1.70 / 1,000 dataset items

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

0

Bookmarked

8

Total users

4

Monthly active users

5 days ago

Last modified

Share

Truth Social Scraper: Posts, Profiles, Engagement Metrics & Media

Truth Social Scraper turns Truth Social into structured data. Point it at public profile, group or post URLs and get normalized posts, replies, profile metadata, media and engagement metrics, plus the complete source objects so no public field is lost. Resume an interrupted pull without being billed twice for posts you already collected, or schedule it and get only what's new, changed or gone since the last run. Export to JSON, CSV or Excel, or pull results straight into your app through the API.

Why This Scraper?

  • URLs or handles, any shape. Add public profile, group or post URLs to the same list -- including the /with_replies and /media profile tabs and public group feeds (/group/<name>) -- or skip URLs entirely and list plain @handle usernames; Profile content then picks profile metadata only, posts only, replies only, or both, per run.
  • Filters that save you money. Keep only posts with media, drop reposts, keep only posts newer than a date (paging stops as soon as it reaches older posts), or continue backwards from a given post.
  • Rich, normalized output across three dataset views. Convenience fields for content, engagement (likes, ReTruths, replies, upvotes and downvotes), author metadata, media, quoted posts, reposts and group posts -- split into posts, profiles and changes dataset views, plus the complete source account/status object so nothing nested is dropped.
  • Resume and scheduled monitoring, without re-paying. Point a new run at a previous run or dataset ID and it skips every post already saved there, or turn on Incremental mode to classify each post NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED against a remembered baseline so a recurring run reports only what changed.
  • Fails loudly, not silently. A run where every request is refused stops with a clear error instead of billing you for a "no data" result; a genuinely empty match still succeeds and pushes nothing.
  • Send results onward. Optional MCP connectors push a summary of each record into Notion, Linear, Airtable or Apify as soon as the scrape finishes.

Use Cases

  • Political and public-figure monitoring: track a public account's posts, edits and removed content over time.
  • Media and journalism: pull post content, media and engagement metrics for reporting and fact-checking.
  • Brand and reputation monitoring: watch a brand or organization's Truth Social activity on a schedule.
  • Academic and social research: build datasets of public posts, engagement patterns and account metadata.
  • Content archiving: keep a running record of a profile's posts, including ones later edited or removed.
  • Competitive and market research: monitor announcements from organizations or public figures as they post.

Data You Get

Sample shape: values are illustrative placeholders, not from a live record.

FieldExample
recordType"post" (also "reply", or "profile" for profile records)
sourceQuery"https://truthsocial.com/@exampleuser" (the start URL the row came from; a plain handle from Usernames shows as its profile URL)
postId / id"113456789012345678" / "113456789012345678" (postId is the reposted source id for a repost, else the status id; id is the status's own id, differing from postId only for a repost)
postUrl"https://truthsocial.com/@exampleuser/113456789012345678"
content / contentHtml"Sample post text goes here." / "<p>Sample post text goes here.</p>"
createdAt / editedAt / language"2026-08-01T12:00:00.000Z" / null (set when the author edited the post) / "en"
repliesCount / reTruthsCount / likesCount42 / 15 / 320
upvotesCount / downvotesCount320 / 4 (Truth Social's own up/down tally, separate from likesCount)
quotesCountnull -- Truth Social does not publish a quote count, so this column is always empty; it is kept so existing integrations keep working
isReblog / rebloggedByAcct / rebloggedFromUrlfalse / null / URL of the original post when the row is a repost, else null
mediaUrls / hasMedia / mediaCount / mediaTypes["https://static-assets.truthsocial.com/sample.jpg"] / true / 1 / ["image"]
mediaAttachmentsone entry per image or video: id, type, url, previewUrl, textUrl, description
tags / mentions["sample"] / mentioned accounts on the post
pollpoll options and vote counts, when present
cardTitle / cardUrllink-preview card title and URL, when present
authorUsername / authorDisplayName / authorAvatarUrl / authorVerified / authorCreatedAt"exampleuser" / "Example User" / the author's avatar, verified badge and account creation date
authorFollowersCount / authorFollowingCount / authorPostsCountthe author's follower, following and post counts at scrape time
pinned / sponsoredfalse / false
quoteId / quotedPostUrl / quotedPostContent / quotedAuthorUsernameid of the quoted post, or null; the quoted post's URL, plain text and author, when the post quotes another one
inReplyToUsernamehandle of the account a reply answers, when the site includes it
groupId / groupName / groupUrlthe Truth Social group a post was made in, else null
titlethe post title some group posts carry, else null
version / editablethe post's edit version and whether it can still be edited
rawStatusthe complete source status object

Profile records (recordType "profile", returned only when Profile content is profileOnly) carry sourceQuery, userId, username, acct, displayName, bio and bioHtml, url, avatar and header images, followersCount, followingCount, statusesCount, createdAt, lastStatusAt, verified, locked, bot, website, location, premium, isGroupAccount, fields, emojis and the complete rawAccount object.

The dataset ships four views: overview (every column, mixing post and profile rows), posts (post/reply columns only), profiles (bio, counts, website, location, premium, verified) and changes (changeType, changedFields, firstSeenAt, lastSeenAt, postId, postUrl, content, createdAt, populated only in Incremental mode). Switch views from the dataset's Choose columns dropdown on the run's Output tab.

How to Use

  1. Add one or more Truth Social profile, group or post URLs under Start here, or list plain handles under Usernames instead -- mix both if you like. Leave both empty and the actor scrapes @realDonaldTrump by default.
  2. For profile URLs, pick Profile content: profile only, posts only, replies only, or both. A pasted .../with_replies or .../media URL (see the shapes table below) applies that tab's content for just that URL, regardless of this setting.
  3. Optionally narrow profile and group posts under Filters: Only posts with media, Include reposts (on by default), Posts newer than (a date such as 2026-10-01 or a period such as 7 days) and Start from post ID (return only posts older than that post). Filters apply to profile and group URLs, never to a pasted post URL.
  4. Set Max items to cap run size and cost; optionally set Max posts/replies per profile or Max pages.
  5. For recurring monitoring, turn on Incremental changes for scheduled runs under Resume and recurring updates, then click Start.

A single profile, posts and replies:

{
"startUrls": [{ "url": "https://truthsocial.com/@realDonaldTrump" }],
"profileContentType": "postsAndReplies",
"maxItems": 50
}

Individual posts by URL:

{
"startUrls": [
{ "url": "https://truthsocial.com/@exampleuser/113456789012345678" },
{ "url": "https://truthsocial.com/@anotheruser/113456789012345999" }
],
"includeRaw": false
}

Only the last 7 days of a profile's own posts with media, no reposts:

{
"startUrls": [{ "url": "https://truthsocial.com/@exampleuser" }],
"onlyMedia": true,
"includeReblogs": false,
"postsNewerThan": "7 days",
"maxItems": 200
}

Recurring incremental monitoring:

{
"startUrls": [{ "url": "https://truthsocial.com/@exampleuser" }],
"profileContentType": "postsOnly",
"incrementalMode": true,
"emitExpired": true,
"stateKey": "exampleuser-posts"
}

Supported URL shapes

A leading www. is accepted, but the https:// (or http://) scheme itself must be present -- Apify's own input validator rejects a pasted URL with no scheme before this actor ever runs, so a bare truthsocial.com/@handle is not a valid Start here entry (use Usernames for that instead). Tracking params (?utm_source=...) are ignored either way. Every row below is one of the prefilled Start here examples.

Default when you omit the field: leaving Start here (startUrls) out of the input now scrapes one example profile, https://truthsocial.com/@realDonaldTrump, instead of nothing. Click Restore example input in the Console to load one example of every shape below. If you only want specific accounts, send your own URLs or usernames; the default applies only when both Start here and Usernames are empty -- if you send usernames and omit startUrls (the platform then fills in the example profile), the example profile is skipped and only your usernames are scraped; if you list your own URLs next to usernames, both are scraped.

ShapeExampleResolves to
Profilehttps://truthsocial.com/@realDonaldTrumpthat account's posts/replies, per Profile content
Profile -- posts & replies tabhttps://truthsocial.com/@realDonaldTrump/with_repliesthe account's posts and replies, even if Profile content is set to Posts only
Profile -- media tabhttps://truthsocial.com/@realDonaldTrump/mediaonly posts/replies from that account that carry media
Post (canonical)https://truthsocial.com/@realDonaldTrump/posts/116813202769287423that single post
Post (bare id)https://truthsocial.com/@realDonaldTrump/116813202769287423the same single post
Post (alternate link form)https://truthsocial.com/users/realDonaldTrump/statuses/116813202769287423the same single post
Grouphttps://truthsocial.com/group/wine-with-devin-nunesthe public group's posts, newest first, as post rows with groupId/groupName/groupUrl filled in (Profile content does not apply; the Filters do)
Group posthttps://truthsocial.com/group/wine-with-devin-nunes/posts/117121586897225540that single post made in the group
www. prefix (scheme still required)https://www.truthsocial.com/@realDonaldTrumpthe same as the matching profile/post shape above
Not supported (logged and skipped)tag/hashtag pages (/tags/...), the groups directory (/groups), search URLs (/search?...), the trending page, links.truthsocial.com short links, a bare /, or any other hostnothing -- the run logs a warning naming the URL and moves on, it is never misrouted to the wrong profile

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/truthsocial-com-scraper").call(run_input={
"startUrls": [{"url": "https://truthsocial.com/@realDonaldTrump"}],
"maxItems": 50,
})
for post in client.dataset(run["defaultDatasetId"]).iterate_items():
print(post["postId"], post["content"][:80])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/truthsocial-com-scraper').call({
startUrls: [{ url: 'https://truthsocial.com/@realDonaldTrump' }],
maxItems: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Resume and recurring updates

  • Resume (resumeFromRunId) continues one interrupted pull: paste its run or dataset ID and the actor skips every post it already saved there. Truth Social's timeline is newest-first, so a resumed profile walk stops paginating as soon as it catches up, so it costs less than a full re-scrape, not just returns fewer rows. An id that cannot be resolved to a run or dataset fails the run immediately, before anything is collected or billed.
  • Incremental mode (incrementalMode) is for running the same profile/post set again and again. Each post is classified NEW, UPDATED (with changedFields), UNCHANGED (suppressed and not billed unless emitUnchanged is on), REAPPEARED or EXPIRED. EXPIRED is produced only once a run completely scans every tracked profile with emitExpired on; a run cut short by Max items, Max pages, or combined with Resume skips EXPIRED detection that run and keeps the previous state as-is (and is itself budgeted to the run's remaining Max items headroom, so a very large backlog of missing posts is never tombstoned in one over-budget run -- the rest is picked up by a later run). REAPPEARED only appears for a post that an earlier emitExpired run had already marked missing and that then shows up again on any later run that finds it, complete scan or not; it is not produced on a normal or first run. stateKey names or shares one monitoring baseline; left empty, a key is derived automatically from startUrls and profileContentType. Combining Incremental mode with Resume is allowed only on the very first run for a state key; once that key has tracked state, a run using both fails instead of mixing the two mechanisms. A profileOnly profile row is tracked too (its own counters, like followers, never cause a false UPDATED on their own). On post rows the author's follower/following/post counts, avatar and verified badge, and the editable flag (which switches off by itself when the edit window closes) never cause an UPDATED on their own either -- a real edit still shows up through content, editedAt and version. A run using any of the Filters only looks at part of each profile, so it never marks posts EXPIRED.
  • Ignore fields when detecting changes (ignoreFieldsForChanges) excludes extra field names from change detection, on top of what's already excluded by default: a poll's votes_count/voters_count, each poll option's own votes_count, and a closed poll's expired flag never trigger a false UPDATED by themselves, at any nesting depth -- they drift on their own as people keep voting, not because the post changed. The author's avatar and verified badge (authorAvatarUrl, authorVerified) and the start URL a row came from (sourceQuery) are left out of the diff too: they describe the author or your input, not the post. Ignored fields are still returned in the output; only the change-detection diff skips them. Add a field name here if something else in your own setup turns out to be noisy.

Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.

What gets written to the connector: a condensed summary of each record, a title plus its key fields flattened to plain markdown text, not the full JSON. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
  3. For Notion, also set notionParentPageUrl to the page where items should be created; if it's left blank for a Notion connector, that export is skipped with a warning. Other connectors ignore this field.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave mcpConnectors empty to skip export entirely.

Input Parameters

ParameterTypeDefaultDescription
startUrlsarray[{"url": "https://truthsocial.com/@realDonaldTrump"}] (prefill: one example per supported shape)Public Truth Social profile, group or post URLs, mixed in the same list. See Supported URL shapes for every accepted form.
usernamesarray[]Optional. Plain handles, with or without a leading @, merged into the same run as startUrls -- each one becomes a profile fetch. When Start here still holds only the example profile, that example is skipped.
profileContentTypestringpostsAndRepliesFor profile URLs: profileOnly, postsOnly, repliesOnly, or postsAndReplies. Ignored for post and group URLs.
maxPostsPerProfileinteger0No per-profile cap by default; the run still stops at Max items. Fewer rows are returned when a profile has fewer public matching records.
onlyMediabooleanfalseKeep only posts and replies with their own image or video (as on the profile's Media tab; reposts are left out).
includeReblogsbooleantrueTurn off to drop reposts and keep only the profile's own posts and replies.
postsNewerThanstringnoneKeep only posts published on or after this date (2026-10-01, 2026-10-01T12:00:00Z) or within a period counted back from the run start (7 days, 12 hours, 2 weeks). Paging stops as soon as a profile reaches older posts. A repost counts from when it was reposted. An unreadable value fails the run before anything is fetched or billed.
startFromIdstringnoneReturn only posts older than this post: a post ID or post URL. Anything else fails the run before anything is fetched or billed.
maxItemsinteger20 (also prefilled as 20)Maximum matching rows saved across the whole run.
maxPagesinteger0No page limit by default; the run stops at Max items.
resumeFromRunIdstringnonePaste a previous run ID or dataset ID to continue a large pull without re-collecting or re-billing posts already saved there.
incrementalModebooleanfalse (also prefilled as false)Turn on for daily or recurring monitoring; later runs return only NEW and UPDATED posts by default. REAPPEARED only occurs after an earlier run with Emit expired posts on has marked a post missing.
stateKeystringnoneNames or shares an incremental-mode baseline; left empty, a key is derived automatically from startUrls and profileContentType.
peekbooleanfalse (also prefilled as false)Incremental mode only: report what changed since the last run without saving the new baseline, so the next run reports the same changes again.
emitUnchangedbooleanfalse (also prefilled as false)Also return (and bill) posts that have not changed since the last incremental run.
emitExpiredbooleanfalse (also prefilled as false)Also return (and bill) posts no longer found; only produced once a run has completely scanned every tracked profile, not when Max items, Max pages, or Resume cut it short. Budgeted to the run's remaining Max items headroom.
includeRawbooleantrueKeeps the complete source account/status object on every record.
ignoreFieldsForChangesarray[]Incremental mode only. Extra field names to exclude from UPDATED change detection, in addition to the built-in poll vote/voter/option-vote counters and the expired flag. Matches a field name at any nesting depth. Ignored fields are still returned in the output.
countryCodestringUSPreferred connection country; US is recommended for Truth Social.
proxyConfigurationobjectApify Residential (useApifyProxy: true, group RESIDENTIAL; same as the prefill)Connection settings. A working connection is required; a run configured with no connection at all is rejected outright and fails.
mcpConnectorsarraynoneOptional: send a summary of each record to apps you authorized under Integrations.
notionParentPageUrlstringnoneNotion connector only: page under which records are created. Required only when exporting to Notion.
maxNotifyListingsinteger50Cap on items written to each connector per run; does not affect the dataset.

Output Example

Sample shape: values are illustrative placeholders, not from a live record.

{
"recordType": "post",
"sourceQuery": "https://truthsocial.com/@exampleuser",
"postId": "113456789012345678",
"id": "113456789012345678",
"uri": "https://truthsocial.com/@exampleuser/113456789012345678",
"postUrl": "https://truthsocial.com/@exampleuser/113456789012345678",
"content": "Sample post text goes here.",
"contentHtml": "<p>Sample post text goes here.</p>",
"createdAt": "2026-08-01T12:00:00.000Z",
"editedAt": null,
"language": "en",
"repliesCount": 42,
"reTruthsCount": 15,
"likesCount": 320,
"favouritesCount": 320,
"quotesCount": null,
"inReplyToId": null,
"sensitive": false,
"visibility": "public",
"isReblog": false,
"rebloggedByAcct": null,
"mediaUrls": [],
"tags": ["sample"],
"mentions": [],
"poll": null,
"cardTitle": null,
"cardUrl": null,
"authorId": "900000000000000001",
"authorUsername": "exampleuser",
"authorAcct": "exampleuser",
"authorDisplayName": "Example User",
"authorUrl": "https://truthsocial.com/@exampleuser",
"upvotesCount": 320,
"downvotesCount": 4,
"pinned": false,
"sponsored": false,
"quoteId": null,
"authorAvatarUrl": "https://static-assets.truthsocial.com/avatar.jpg",
"authorFollowersCount": 1200,
"authorFollowingCount": 85,
"authorPostsCount": 640,
"authorVerified": false,
"authorCreatedAt": "2022-02-01T00:00:00.000Z",
"rebloggedFromUrl": null,
"mediaCount": 0,
"hasMedia": false,
"mediaTypes": [],
"mediaAttachments": [],
"quotedPostUrl": null,
"quotedPostContent": null,
"quotedAuthorUsername": null,
"groupId": null,
"groupName": null,
"groupUrl": null,
"inReplyToUsername": null,
"title": null,
"version": "1",
"editable": false,
"rawStatus": {},
"scrapedAt": "2026-09-29T00:00:00.000Z"
}

Plan Requirement

Truth Social blocks plain datacenter and direct connections to its data endpoints (the site answers them with an access-denied page), so a datacenter-only setup does not work and the actor does not rely on one. It defaults to Apify's Residential proxy group and keeps the country code set to US, which the site responds to most reliably; when the residential connection is refused, requests are retried over a backup connection. Residential access is the recommended setting, but it is not a guarantee that every page loads on every run.

A free Apify plan does not include residential proxies. On a free plan, the run logs a one-time warning and automatically continues on a backup connection instead of stopping -- most free-plan runs still complete, though some pages may come back unavailable if the backup connection can't reach them either.

If a run can't read a single page at all, it fails outright instead of returning an empty dataset, with one of three messages depending on what happened:

  • the account's plan has no proxy access at all (no paid plan, no custom proxy URLs supplied);
  • a free plan whose backup connection also could not load the requested pages;
  • any other connection problem (a paid plan, or an unrecognized entitlement) where every request failed.

Upgrading to a plan that includes residential proxies, or supplying your own proxy URLs, resolves all three.

FAQ

How much does it cost?

You pay a small per-run start fee plus a per-record fee for each row saved to the dataset. The Pricing tab shows the current rates. Use Max items to cap the cost of any run.

This actor collects only publicly available profile and post data. You are responsible for how you use it: follow Truth Social's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Some public social media records may still contain personal data, so handle results accordingly.

Can I get only new or changed posts on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental changes for scheduled runs. Each run then returns only NEW and UPDATED posts by default (plus REAPPEARED once an earlier run with Emit expired posts on has marked one missing); UNCHANGED ones are suppressed and not billed unless emitUnchanged is on.

What's the difference between Resume and Incremental mode?

Resume (resumeFromRunId) continues one specific interrupted run using a pasted run or dataset ID. Incremental mode remembers its own baseline and is meant for running the same profile/post set repeatedly on a schedule. Combining the two is allowed only the first time you turn Incremental mode on for a state key; once that key has tracked state, a run using both fails instead of mixing the two mechanisms.

Why did my run fail instead of returning an empty dataset?

If every request to Truth Social is refused, the run fails loudly with a message naming the reason instead of billing you for a "no data" result: no proxy access on this account's plan at all, a free plan whose backup connection also couldn't load the requested pages, or a general connection problem on any other plan. On a free plan specifically, a one-time warning is logged when residential proxies are unavailable and the run automatically continues on a backup connection -- the failure above only happens if that backup connection ALSO can't reach the requested pages. Upgrading to a plan with residential proxies, or supplying your own proxy URLs, resolves it.

If the site DID answer but nothing matched your input (for example, an account or post that doesn't exist), the run still succeeds with an empty dataset and reports why on the run's status message.

Groups, yes: paste a public group URL (https://truthsocial.com/group/<name>) and you get the group's posts, newest first, with groupId, groupName and groupUrl filled in. Search, trending, hashtags and comments, no: Truth Social only serves those to signed-in accounts -- search, trending and the comment threads under a post answer anonymous requests with an authorization error, and hashtag feeds with an empty list. This actor works without any login, so pasted search, tag or trending URLs are skipped with a warning rather than half-scraped.

Is there a clean-text option?

You get both on every post: content is already plain text, and contentHtml keeps the original HTML. There is no "include muted" option either, because muting only exists for a signed-in account and never applies to anonymous scraping.

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push a summary of each record into Notion, Linear or Airtable as soon as the run finishes.

🔗 Want more social media data?

Pair this actor with these related scrapers from the same team:

📱 Lemon8 Profile Scraper
Scrape Lemon8 user profiles with automated multi-profile discovery. Extract profile data...
📱 Lemon8 Search Scraper
Scrape Lemon8 posts by keyword across multiple regions. Extract posts, images, videos...
📱 Lemon8 Feed Scraper
Scrape Lemon8 feeds across 22 categories and 10+ regions. Extract posts, images, videos...
📱 Mastodon
From $1/1K. Scrape trending Mastodon profiles and related posts from any Mastodon...
📱 Lemon8 Media Scraper
Lightweight Lemon8 scraper for extracting image and video URLs from posts, with optional...
📱 Reddit⚡ Lightning
From $1/1K. Scrape Reddit posts, comments, and media from any subreddit or user profile -...

👉 Browse all abotapi scrapers

💬 Support

  • 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
  • 🛠️ Need another site, extra fields or a private build? Email contact@abotapi.com.
  • ⭐ Enjoying it? A quick review on the actor page helps other users find it.