Truth Social Scraper | Profiles, Posts & Search
Pricing
from $1.70 / 1,000 dataset items
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
Maintained by CommunityActor stats
0
Bookmarked
8
Total users
4
Monthly active users
5 days ago
Last modified
Categories
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_repliesand/mediaprofile tabs and public group feeds (/group/<name>) -- or skip URLs entirely and list plain@handleusernames; 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,profilesandchangesdataset 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.
| Field | Example |
|---|---|
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 / likesCount | 42 / 15 / 320 |
upvotesCount / downvotesCount | 320 / 4 (Truth Social's own up/down tally, separate from likesCount) |
quotesCount | null -- Truth Social does not publish a quote count, so this column is always empty; it is kept so existing integrations keep working |
isReblog / rebloggedByAcct / rebloggedFromUrl | false / 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"] |
mediaAttachments | one entry per image or video: id, type, url, previewUrl, textUrl, description |
tags / mentions | ["sample"] / mentioned accounts on the post |
poll | poll options and vote counts, when present |
cardTitle / cardUrl | link-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 / authorPostsCount | the author's follower, following and post counts at scrape time |
pinned / sponsored | false / false |
quoteId / quotedPostUrl / quotedPostContent / quotedAuthorUsername | id of the quoted post, or null; the quoted post's URL, plain text and author, when the post quotes another one |
inReplyToUsername | handle of the account a reply answers, when the site includes it |
groupId / groupName / groupUrl | the Truth Social group a post was made in, else null |
title | the post title some group posts carry, else null |
version / editable | the post's edit version and whether it can still be edited |
rawStatus | the 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
- 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
@realDonaldTrumpby default. - For profile URLs, pick Profile content: profile only, posts only, replies only, or both. A pasted
.../with_repliesor.../mediaURL (see the shapes table below) applies that tab's content for just that URL, regardless of this setting. - 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-01or a period such as7 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. - Set Max items to cap run size and cost; optionally set Max posts/replies per profile or Max pages.
- 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.
| Shape | Example | Resolves to |
|---|---|---|
| Profile | https://truthsocial.com/@realDonaldTrump | that account's posts/replies, per Profile content |
| Profile -- posts & replies tab | https://truthsocial.com/@realDonaldTrump/with_replies | the account's posts and replies, even if Profile content is set to Posts only |
| Profile -- media tab | https://truthsocial.com/@realDonaldTrump/media | only posts/replies from that account that carry media |
| Post (canonical) | https://truthsocial.com/@realDonaldTrump/posts/116813202769287423 | that single post |
| Post (bare id) | https://truthsocial.com/@realDonaldTrump/116813202769287423 | the same single post |
| Post (alternate link form) | https://truthsocial.com/users/realDonaldTrump/statuses/116813202769287423 | the same single post |
| Group | https://truthsocial.com/group/wine-with-devin-nunes | the public group's posts, newest first, as post rows with groupId/groupName/groupUrl filled in (Profile content does not apply; the Filters do) |
| Group post | https://truthsocial.com/group/wine-with-devin-nunes/posts/117121586897225540 | that single post made in the group |
www. prefix (scheme still required) | https://www.truthsocial.com/@realDonaldTrump | the 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 host | nothing -- 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 ApifyClientclient = 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 classifiedNEW,UPDATED(withchangedFields),UNCHANGED(suppressed and not billed unlessemitUnchangedis on),REAPPEAREDorEXPIRED.EXPIREDis produced only once a run completely scans every tracked profile withemitExpiredon; 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).REAPPEAREDonly appears for a post that an earlieremitExpiredrun 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.stateKeynames or shares one monitoring baseline; left empty, a key is derived automatically fromstartUrlsandprofileContentType. 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. AprofileOnlyprofile row is tracked too (its own counters, like followers, never cause a falseUPDATEDon their own). On post rows the author's follower/following/post counts, avatar and verified badge, and theeditableflag (which switches off by itself when the edit window closes) never cause anUPDATEDon their own either -- a real edit still shows up throughcontent,editedAtandversion. A run using any of the Filters only looks at part of each profile, so it never marks postsEXPIRED. - Ignore fields when detecting changes (
ignoreFieldsForChanges) excludes extra field names from change detection, on top of what's already excluded by default: a poll'svotes_count/voters_count, each poll option's ownvotes_count, and a closed poll'sexpiredflag never trigger a falseUPDATEDby 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.
- Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto 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
| Parameter | Type | Default | Description |
|---|---|---|---|
startUrls | array | [{"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. |
usernames | array | [] | 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. |
profileContentType | string | postsAndReplies | For profile URLs: profileOnly, postsOnly, repliesOnly, or postsAndReplies. Ignored for post and group URLs. |
maxPostsPerProfile | integer | 0 | No per-profile cap by default; the run still stops at Max items. Fewer rows are returned when a profile has fewer public matching records. |
onlyMedia | boolean | false | Keep only posts and replies with their own image or video (as on the profile's Media tab; reposts are left out). |
includeReblogs | boolean | true | Turn off to drop reposts and keep only the profile's own posts and replies. |
postsNewerThan | string | none | Keep 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. |
startFromId | string | none | Return only posts older than this post: a post ID or post URL. Anything else fails the run before anything is fetched or billed. |
maxItems | integer | 20 (also prefilled as 20) | Maximum matching rows saved across the whole run. |
maxPages | integer | 0 | No page limit by default; the run stops at Max items. |
resumeFromRunId | string | none | Paste a previous run ID or dataset ID to continue a large pull without re-collecting or re-billing posts already saved there. |
incrementalMode | boolean | false (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. |
stateKey | string | none | Names or shares an incremental-mode baseline; left empty, a key is derived automatically from startUrls and profileContentType. |
peek | boolean | false (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. |
emitUnchanged | boolean | false (also prefilled as false) | Also return (and bill) posts that have not changed since the last incremental run. |
emitExpired | boolean | false (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. |
includeRaw | boolean | true | Keeps the complete source account/status object on every record. |
ignoreFieldsForChanges | array | [] | 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. |
countryCode | string | US | Preferred connection country; US is recommended for Truth Social. |
proxyConfiguration | object | Apify 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. |
mcpConnectors | array | none | Optional: send a summary of each record to apps you authorized under Integrations. |
notionParentPageUrl | string | none | Notion connector only: page under which records are created. Required only when exporting to Notion. |
maxNotifyListings | integer | 50 | Cap 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.
Is it legal to scrape Truth Social?
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.
Can it scrape search results, trending posts, hashtags, groups or comments?
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 -... |
💬 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.