Mastodon Posts by Hashtag, Account, Thread & Trends (no login) avatar

Mastodon Posts by Hashtag, Account, Thread & Trends (no login)

Pricing

from $1.05 / 1,000 mastodon data rows

Go to Apify Store
Mastodon Posts by Hashtag, Account, Thread & Trends (no login)

Mastodon Posts by Hashtag, Account, Thread & Trends (no login)

Public Mastodon data as rows: posts of one or more hashtags with boolean tag combinations, an account's public posts, the whole reply tree of a post, trending tags and news links, hashtag discovery with 7-day usage, and profile counts. Authors from any federated server. Keyless API, UTC dates.

Pricing

from $1.05 / 1,000 mastodon data rows

Rating

0.0

(0)

Developer

Samat Makatov

Samat Makatov

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Share

Public Mastodon data as clean rows, straight from the REST API of the mastodon.social server: every post under a hashtag (with boolean tag combinations), the public posts of the accounts you name, the whole reply tree under one post, what the network trends on right now (hashtags, shared news links, popular posts), which hashtags exist for a keyword and how much they are used, and follower/post counts for a watch list. Because that server federates, authors from thousands of other fediverse servers show up too, and every row says which server its author is on. No API key, no token, no cookie, no browser — and all timestamps are UTC ISO-8601.

Need it tailored to your workflow? Missing a field, a filter, another source or a different output format? Open an issue and describe what you need — we read every request and adapt the actor.

Use cases

  • Replace a closed forum feed: follow #opensource, #selfhosted or your product name on an open network with sinceHours + onlyNew on a schedule, and get only what was posted since the last run.
  • Brand and press monitoring: export the public posts of an organisation's accounts (accountPosts) with favourites, boosts and replies, and archive announcements with their links and media.
  • Community management: turn one announcement into its full discussion (threadReplies) — every reply the server knows, with its depth, author and server, so you can read or summarise a reaction in one pass.
  • Newsroom and PR monitoring: trending + links gives the articles the network is sharing right now with publisher, publication date and how many accounts shared them today and over the week.
  • Dashboard planning: hashtagSearch shows which spelling of a topic is alive (climate vs climatechange vs climatecrisis) with seven days of usage, before you commit a monitoring run to the wrong tag.
  • Research and scouting: profiles keeps follower, following and post counts of a list of local and remote handles fresh; authorInstances lets you study one community inside a worldwide hashtag.
  • Agents and LLM pipelines: public social text and trend lists without any credential — call the actor over the Apify API or MCP and narrow the columns with fields.

Input

Every field is optional; each mode uses the fields of its own section. Example values in the editor are prefills (you can change or clear them), defaults only switch behaviour.

FieldTypeDefaultAllowed values / notes
modestringhashtagPostshashtagPosts, accountPosts, threadReplies, trending, hashtagSearch, profiles — see Modes
hashtagsstring[]prefill opensource, linuxHashtags to read in hashtagPosts, with or without #, case ignored. The row limit is split evenly over them
alsoTaggedstring[]–hashtagPosts: keep posts carrying the main tag or any of these (source parameter any)
mustAlsoBeTaggedstring[]–hashtagPosts: keep only posts carrying the main tag and all of these (all)
excludeTaggedstring[]–hashtagPosts: drop posts that also carry one of these (none)
localOnlybooleanfalsehashtagPosts: keep only posts written on mastodon.social itself
accountsstring[]prefill Mastodon, MastodonEngineeringaccountPosts and profiles. Forms: name, name@server, profile link — see Handles
excludeRepliesbooleantrueaccountPosts: leave out the replies the account wrote
includeReblogsbooleanfalseaccountPosts: also keep posts the account boosted (isReblog: true, booster in rebloggedBy)
postUrlsstring[]prefill one post linkthreadReplies: mastodon.social post links or bare numeric ids — see Post links
includeAncestorsbooleantruethreadReplies: also write the posts above the given one (negative replyDepth)
trendTypestringtagstrending: tags, links or posts — see Trend types
searchQuerystringprefill climatehashtagSearch: one word; matches hashtag names, not post text
languagesstring[]allKeep posts whose declared language is one of these 20 codes — see Languages
minFavouritesnumber–0–1 000 000. Keep posts with at least this many favourites at fetch time
minReblogsnumber–0–1 000 000. Same floor for boosts
excludeSensitivebooleanfalseDrop posts flagged sensitive or behind a content warning
onlyWithMediabooleanfalseKeep only posts with an image, video or audio attachment
authorInstancesstring[]allKeep posts whose author lives on one of these servers, e.g. fosstodon.org
sinceHoursnumber–1–8760. Rolling window in UTC hours. A timeline stops paging once it passes the window; the trending-posts list is ranked by popularity, so older posts there are dropped one by one instead
onlyNewbooleanfalseWrite only posts that earlier runs of the same actor or task did not write
maxItemsnumber501–1000. Hard cap on rows, split over the hashtags or accounts you named. The run also stops at your Maximum cost per run (see Limits)
fieldsstring[]allKeep only these columns, in this order. rowType, sourceMode and the row's id column (statusId+found, tag, url or handle+found) are always kept. Column names are free text: case does not matter and one near miss such as favoriteCount is corrected, both noted in the status message; a name no row has is reported and ignored, and a list in which no name exists fails the run with the valid names

Filters are applied after the source answers, except localOnly, onlyWithMedia and the three tag combinations, which the source itself applies before paging. languages, minFavourites, minReblogs, excludeSensitive, onlyWithMedia, authorInstances, sinceHours and onlyNew only affect post rows; trending with tags or links, hashtagSearch and profiles ignore them and say so in the status message.

Reference

Modes

modeOne row perNeedsWrites
hashtagPostspublic post carrying one of your hashtags, newest firsthashtagspost rows, matchedHashtag filled
accountPostspublic post of the accounts you nameaccountspost rows
threadRepliespost in the discussion under a postpostUrlspost rows with replyDepth, threadRootStatusId
trendingtrending hashtag, shared news link or popular posttrendTypetag, link or post rows with trendRank
hashtagSearchhashtag whose name matches your wordsearchQuerytag rows with seven days of usage
profilesaccount, with its public countsaccountsprofile rows

Trend types

trendTypeSource listRow typeRows per run
tagshashtags the server currently promotestag20 per request, paged
linksarticles being shared right nowlink20 per request, paged
postsindividual posts the server promotespost40 per request, paged

Handles

accounts accepts three shapes, mixed freely in one list:

You writeMeans
Mastodon or @Mastodonthe account on mastodon.social
pluralistic@mamot.fran account on any other fediverse server
https://fosstodon.org/@kevthe same, from a profile link you pasted

Case does not matter. An account the server has never federated with cannot be resolved and yields one row with found: false and the reason in notFoundReason.

Post links

postUrls accepts mastodon.social links and bare ids: https://mastodon.social/@Mastodon/117156549508722805, https://mastodon.social/@hackernews@robot.villas/117367636517428091, https://mastodon.social/users/Mastodon/statuses/117156549508722805 or 117156549508722805.

Post ids are numbered per server, so a link on another server (https://mastodon.online/@someone/1171565933…) is refused with a message instead of a wrong answer. Every post row of every mode carries the mastodon.social link of the same post in localUrl — take that value and threadReplies will accept it.

Languages

The language column is what the posting app declared, not a guess of ours. Codes offered by the filter: en English, de German, fr French, es Spanish, pt Portuguese, it Italian, nl Dutch, ja Japanese, zh Chinese, ko Korean, ru Russian, uk Ukrainian, pl Polish, cs Czech, sv Swedish, fi Finnish, nb Norwegian, da Danish, tr Turkish, ca Catalan. Send one of these 20 two-letter codes exactly: the platform checks the list before the run starts, so a language name (German), a three-letter code (deu) or a code outside the list is refused with the list of allowed values. Regional variants count as their language (en-GB is en), and nb (Norwegian) also keeps posts declared no (Norwegian) or nn (Nynorsk); the language column still shows what the post declared. Many posts from other fediverse software declare no language at all — those are dropped while this filter is set.

Row types

rowType says what a row is: post, tag (from trending+tags and hashtagSearch), link (from trending+links) or profile. One run writes one row type, so a dataset never mixes them.

Examples

Follow two topics for the last two days, without sensitive posts

{ "mode": "hashtagPosts", "hashtags": ["opensource", "linux"], "sinceHours": 48, "excludeSensitive": true, "maxItems": 20 }

One cheap request for a niche: Linux AND open source, but no AI crossposts

{ "mode": "hashtagPosts", "hashtags": ["linux"], "mustAlsoBeTagged": ["opensource"], "excludeTagged": ["ai"], "maxItems": 20 }

Export what two organisation accounts published

{ "mode": "accountPosts", "accounts": ["Mastodon", "MastodonEngineering"], "excludeReplies": true, "includeReblogs": false, "maxItems": 20 }

Read the whole discussion under one announcement

{ "mode": "threadReplies", "postUrls": ["https://mastodon.social/@Mastodon/117156549508722805"], "includeAncestors": true, "maxItems": 25 }

What is trending right now, and which articles are being shared

{ "mode": "trending", "trendType": "tags", "maxItems": 20 }
{ "mode": "trending", "trendType": "links", "maxItems": 20 }

Find the hashtag spelling that is actually used, then watch one community inside it

{ "mode": "hashtagSearch", "searchQuery": "climate", "maxItems": 20 }
{ "mode": "hashtagPosts", "hashtags": ["climatechange"], "authorInstances": ["fosstodon.org", "mastodon.social"], "languages": ["en"], "maxItems": 20 }

Keep a watch list of accounts up to date

{ "mode": "profiles", "accounts": ["Mastodon", "MastodonEngineering", "pluralistic@mamot.fr"], "maxItems": 10 }

Output

One real row of a cloud run ({"mode": "hashtagPosts", "hashtags": ["opensource", "linux"], "sinceHours": 48, "excludeSensitive": true, "maxItems": 20}, run 9A6iUYc6Nh7L8lnMA):

{
"rowType": "post",
"sourceMode": "hashtagPosts",
"statusId": "117367721941465389",
"url": "https://mastodon.social/@joelindien/117367721941465389",
"localUrl": "https://mastodon.social/@joelindien/117367721941465389",
"createdAt": "2026-10-01T21:15:11.852Z",
"editedAt": null,
"language": "fr",
"visibility": "public",
"sensitive": false,
"contentWarning": null,
"text": "🐧 Actu logiciel libre du 01/10/2026\n• Fedora 45 Beta Performance: The Satisfyingly Boring Benchmarks\n[Phoronix] https://www.phoronix.com/review/fedora-45-beta-benchmarks\n#LogicielLibre #OpenSource #Libre",
"authorHandle": "joelindien",
"authorFullHandle": "joelindien@mastodon.social",
"authorDisplayName": null,
"authorInstance": "mastodon.social",
"authorUrl": "https://mastodon.social/@joelindien",
"authorFollowers": 14,
"authorPosts": 2927,
"authorIsBot": false,
"replyCount": 0,
"reblogCount": 0,
"favouriteCount": 0,
"quoteCount": 0,
"hashtags": [
"logiciellibre",
"opensource",
"libre"
],
"matchedHashtag": "opensource",
"mentions": [],
"linkUrls": [
"https://www.phoronix.com/review/fedora-45-beta-benchmarks"
],
"cardUrl": "https://www.phoronix.com/review/fedora-45-beta-benchmarks",
"cardTitle": "Fedora 45 Beta Performance: The Satisfyingly Boring Benchmarks",
"cardPublisher": null,
"mediaCount": 0,
"mediaTypes": [],
"mediaUrls": [],
"isReblog": false,
"rebloggedBy": null,
"inReplyToStatusId": null,
"replyDepth": null,
"threadRootStatusId": null,
"threadRootUrl": null,
"applicationName": "bot news",
"sourceRef": "opensource",
"found": true,
"notFoundReason": null,
"fetchedAt": "2026-10-01T21:19:14.212Z"
}

Post row (rowType: "post")

FieldTypeMeaning
rowTypestringpost
sourceModestringThe mode that produced the row
statusIdstringThe post id on mastodon.social — feed it back into threadReplies
urlstringThe post as its own server links it (the canonical link)
localUrlstringThe same post on mastodon.social; always usable in postUrls
createdAtstringWhen the post was written, ISO 8601 UTC
editedAtstringWhen it was last edited, or null
languagestringLanguage declared by the posting app (often null for other software)
visibilitystringpublic or unlisted — what the public API serves
sensitivebooleanFlagged sensitive by its author, or carrying a content warning
contentWarningstringThe warning text, or null
textstringPost text as a reader sees it: markup removed, links written out in full
authorHandlestringAuthor name without the server, lowercased
authorFullHandlestringname@server for every author, so rows can be compared
authorDisplayNamestringName the author shows
authorInstancestringServer the author lives on, e.g. fosstodon.org
authorUrlstringAuthor's profile link
authorFollowers, authorPostsnumberPublic counts of the author at fetch time
authorIsBotbooleanThe account marks itself as automated
replyCount, reblogCount, favouriteCount, quoteCountnumberEngagement at fetchedAt
hashtagsstring[]Every hashtag of the post, lowercased
matchedHashtagstringThe hashtag of your input that produced this row (hashtagPosts)
mentionsstring[]Handles mentioned in the post
linkUrlsstring[]URLs the post links to
cardUrl, cardTitle, cardPublisherstringLink preview the server built, when there is one
mediaCountnumberNumber of attachments
mediaTypesstring[]image, video, gifv, audio
mediaUrlsstring[]File links of the attachments (linked, never downloaded)
isReblogbooleanThe row came from a boost (accountPosts with includeReblogs)
rebloggedBystringWho boosted it
inReplyToStatusIdstringThe post this one replies to, or null
replyDepthnumberthreadReplies: 0 for the post you asked for, 1, 2… for replies, negative above it
threadRootStatusId, threadRootUrlstringthreadReplies: the post the thread was read from
applicationNamestringApp that published the post, when the server knows it
sourceRefstringThe exact input value this row came from
foundbooleanfalse on the one row that explains an unknown handle, id or opt-out
notFoundReasonstringWhy that input gave no data
fetchedAtstringFetch time, ISO 8601 UTC — engagement counts are a snapshot of this moment

Tag row (rowType: "tag")

FieldTypeMeaning
tagstringHashtag name as the server spells it
urlstringTag page on mastodon.social
usesToday, accountsTodaynumberUses and distinct accounts on the newest day the server reports
uses7d, accounts7dnumberSum over the seven days the server sends
historyobject[]{ day (UTC date), accounts, uses }, newest first
trendRanknumberPosition in the list the source returned, from 1
sourceRefstringtrending-tags, or your searchQuery
FieldTypeMeaning
title, descriptionstringArticle title and teaser as the server read them
urlstringThe article itself
publisherName, authorNamestringPublisher and author, when the page declares them
publishedAtstringPublication date, ISO 8601 UTC
languagestringLanguage of the article
imageUrlstringPreview image the server cached
usesToday, accountsToday, uses7d, accounts7dnumberHow often and by how many accounts it was shared
trendRanknumberPosition in the trending list

Profile row (rowType: "profile")

FieldTypeMeaning
handle, fullHandlestringmastodon and mastodon@mastodon.social
displayNamestringName the account shows
instancestringServer the account lives on
biostringProfile text, markup removed
followersCount, followingCount, postsCountnumberPublic counts (the lists are never collected)
createdAtstringJoin date, ISO 8601 UTC
lastPostAtstringDate of the last post the server knows (UTC date)
isBot, isLocked, isDiscoverablebooleanFlags the account set itself
profileFieldsobject[]{ name, value, verified } of the links next to the bio
avatarUrl, urlstringAvatar file and profile link
found, notFoundReasonboolean, stringfalse for an unknown handle or an account that opted out

Dataset views: Posts (author, server, text, posted, language, favourites, boosts, replies, hashtags, matched tag, link), Trending tags and links (kind, tag, article, publisher, daily and weekly counts, link), Trending and searched hashtags (rank, hashtag, uses and accounts today and over seven days, list or search word, tag page), Trending news links (rank, article, publisher, published, language, shares today and over seven days, link), Profiles (handle, name, server, followers, following, posts, joined, last post, link). The dataset schema describes every column with a title, a description and an example, for agents and MCP clients.

Every run also writes a SUMMARY record into the default key-value store (linked from the run's output as summary): the mode and the filters it ran with, the number of requests, the rate-limit budget the source reported and whether its limit stopped the run, rows read and written, rows per hashtag/account/thread, inputs that were not served, inputs without rows, a counter per filter that dropped something, and why the run stopped early when it did (your spending limit, the run timeout).

Use it from code / agents

curl -X POST "https://api.apify.com/v2/acts/yadroo~mastodon-public-posts/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mode":"hashtagPosts","hashtags":["opensource"],"sinceHours":24,"maxItems":20,"fields":["authorFullHandle","text","favouriteCount","createdAt","url"]}'
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('yadroo/mastodon-public-posts').call({ mode: 'trending', trendType: 'links', maxItems: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const row of items) console.log(row.publisherName, row.usesToday, row.url);
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
# 1) read a hashtag, 2) take the localUrl of the loudest post, 3) read its thread
posts = client.actor("yadroo/mastodon-public-posts").call(
run_input={"mode": "hashtagPosts", "hashtags": ["opensource"], "minFavourites": 5, "maxItems": 20}
)
rows = client.dataset(posts["defaultDatasetId"]).list_items().items
loudest = max(rows, key=lambda r: r.get("favouriteCount") or 0)
thread = client.actor("yadroo/mastodon-public-posts").call(
run_input={"mode": "threadReplies", "postUrls": [loudest["localUrl"]], "maxItems": 25}
)

Chaining works the same way in the other direction: a trending + tags run gives you tag values to put straight into hashtagPosts. For schedules, set sinceHours to the interval and onlyNew: true — each run then holds only what is new, and an empty run means nothing was posted. Narrow rows with fields when an LLM reads them.

MCP: add https://mcp.apify.com to Claude / Cursor / any MCP client and call the yadroo/mastodon-public-posts tool with the same JSON input.

Pricing

Pay per event: $0.001 per run start + $0.0015 per dataset row. The start event is charged on every run, including runs that write no row.

RunRowsCost
Trending hashtags, one request20$0.001 + 20 × $0.0015 = $0.031
Hashtag monitoring, last 24 h50$0.001 + 50 × $0.0015 = $0.076
Thread of a busy announcement200$0.001 + 200 × $0.0015 = $0.301

Platform usage is included: you pay only these two events, never compute or proxy on top. A 50-row run needs 2–3 HTTP requests and finishes in well under a minute at 256 MB. Bronze −10 %, Silver −20 %, Gold and above −30 % on the row price; the start event is the same on every plan.

Limits & FAQ

  • No full-text post search. The source serves post search only to callers with a login: type=statuses answers empty arrays without one. Topics are therefore reached by hashtag, by account or through the trending lists — and hashtagSearch exists to find the right hashtag first. This is a limit of the source, not something a token of ours could widen.
  • No "everything" mode. The anonymous public timeline of the server answers HTTP 422, so there is no firehose of all posts. Every run names hashtags, accounts, a post or a trend list.
  • One server's federated view. Data comes from mastodon.social. Posts and accounts of other fediverse servers appear through it — which is most of the network for popular tags — but a post only exists here if this server has seen it, and authorInstance tells you where each author actually lives. Replies that live only on a server this one does not follow are not part of a thread.
  • Trends are that server's view. The trending lists are computed by mastodon.social from what it sees, and they can be short or empty for a while; trending + links sometimes returns fewer than 20 entries. The lists are served 20 (tags, links) or 40 (posts) entries per request and paged from there.
  • Post ids are server-local. threadReplies needs a mastodon.social link or id; a link on another server is refused with a message. Use the localUrl column of any post row.
  • Hashtag search is capped at 40. The source answers at most 40 hashtags per search and refuses paging without a login, so maxItems above 40 cannot be filled in hashtagSearch.
  • Accounts that opted out are skipped. An account hosted on mastodon.social that asked not to be indexed is not exported in accountPosts or profiles; you get one row with found: false instead. Posts it puts under a hashtag are still read in hashtagPosts, because adding a hashtag is a request to be found.
  • No follower lists, no contact harvesting. Only public posts, public profile text and the public follower/following/post counts are read. Follower and following lists, e-mail addresses and phone numbers are never collected. Media files are linked, never downloaded.
  • Engagement counts are a snapshot. Favourites, boosts, replies and quotes are the values at fetchedAt and keep growing afterwards.
  • Rate limit. The source allows 300 requests per five minutes per IP address for callers without a login, and Apify IPs are shared with other callers. The actor sends one request at a time, below one per second, and reads the remaining budget from every answer (X-RateLimit-Remaining, X-RateLimit-Reset): with 10 or fewer left it spreads its next requests over the rest of the window, and when the budget is used up (or the server answers HTTP 429) it waits for the window to reset if the run has time for that. If it has not, the run ends with the rows it has and the status "Stopped at the source's rate limit … run again after HH:MM UTC" — or fails with that message when nothing was written. It never switches IPs or User-Agents to get past the limit. A typical run needs 2–6 requests.
  • Your spending limit and the run timeout. Rows are written as each page is read. When your Maximum cost per run is reached the run stops and ends with "Stopped at your spending limit: N rows delivered"; near the run timeout it stops starting new requests and ends with "Stopped before the run timeout: N rows saved". Rows already written stay, are billed once, and with onlyNew only the rows really written are remembered.
  • Empty results vs errors. An unknown hashtag with no posts, a quiet account or an empty trend list is a successful run with a note in the status message; an unknown handle or deleted post gives one row with found: false and a reason; a source that misbehaves without producing a single row makes the run fail with that reason.
  • Why a post is missing a language. Posts coming from other fediverse software often declare none. They are kept unless you set languages, which can only keep posts that declare one.
  • If the server closes these endpoints. A Mastodon server can switch on authorized fetch, which would close the public API for anonymous callers. Should that happen, this actor is marked paused here instead of trying to work around it.

Made by Yadroo. Sibling actors: bluesky-public-posts, hackernews-search, stackexchange-search, google-news-search, rss-to-json.