Mastodon Posts by Hashtag, Account, Thread & Trends (no login)
Pricing
from $1.05 / 1,000 mastodon data rows
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
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,#selfhostedor your product name on an open network withsinceHours+onlyNewon 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+linksgives 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:
hashtagSearchshows which spelling of a topic is alive (climatevsclimatechangevsclimatecrisis) with seven days of usage, before you commit a monitoring run to the wrong tag. - Research and scouting:
profileskeeps follower, following and post counts of a list of local and remote handles fresh;authorInstanceslets 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.
| Field | Type | Default | Allowed values / notes |
|---|---|---|---|
mode | string | hashtagPosts | hashtagPosts, accountPosts, threadReplies, trending, hashtagSearch, profiles — see Modes |
hashtags | string[] | prefill opensource, linux | Hashtags to read in hashtagPosts, with or without #, case ignored. The row limit is split evenly over them |
alsoTagged | string[] | – | hashtagPosts: keep posts carrying the main tag or any of these (source parameter any) |
mustAlsoBeTagged | string[] | – | hashtagPosts: keep only posts carrying the main tag and all of these (all) |
excludeTagged | string[] | – | hashtagPosts: drop posts that also carry one of these (none) |
localOnly | boolean | false | hashtagPosts: keep only posts written on mastodon.social itself |
accounts | string[] | prefill Mastodon, MastodonEngineering | accountPosts and profiles. Forms: name, name@server, profile link — see Handles |
excludeReplies | boolean | true | accountPosts: leave out the replies the account wrote |
includeReblogs | boolean | false | accountPosts: also keep posts the account boosted (isReblog: true, booster in rebloggedBy) |
postUrls | string[] | prefill one post link | threadReplies: mastodon.social post links or bare numeric ids — see Post links |
includeAncestors | boolean | true | threadReplies: also write the posts above the given one (negative replyDepth) |
trendType | string | tags | trending: tags, links or posts — see Trend types |
searchQuery | string | prefill climate | hashtagSearch: one word; matches hashtag names, not post text |
languages | string[] | all | Keep posts whose declared language is one of these 20 codes — see Languages |
minFavourites | number | – | 0–1 000 000. Keep posts with at least this many favourites at fetch time |
minReblogs | number | – | 0–1 000 000. Same floor for boosts |
excludeSensitive | boolean | false | Drop posts flagged sensitive or behind a content warning |
onlyWithMedia | boolean | false | Keep only posts with an image, video or audio attachment |
authorInstances | string[] | all | Keep posts whose author lives on one of these servers, e.g. fosstodon.org |
sinceHours | number | – | 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 |
onlyNew | boolean | false | Write only posts that earlier runs of the same actor or task did not write |
maxItems | number | 50 | 1–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) |
fields | string[] | all | Keep 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
mode | One row per | Needs | Writes |
|---|---|---|---|
hashtagPosts | public post carrying one of your hashtags, newest first | hashtags | post rows, matchedHashtag filled |
accountPosts | public post of the accounts you name | accounts | post rows |
threadReplies | post in the discussion under a post | postUrls | post rows with replyDepth, threadRootStatusId |
trending | trending hashtag, shared news link or popular post | trendType | tag, link or post rows with trendRank |
hashtagSearch | hashtag whose name matches your word | searchQuery | tag rows with seven days of usage |
profiles | account, with its public counts | accounts | profile rows |
Trend types
trendType | Source list | Row type | Rows per run |
|---|---|---|---|
tags | hashtags the server currently promotes | tag | 20 per request, paged |
links | articles being shared right now | link | 20 per request, paged |
posts | individual posts the server promotes | post | 40 per request, paged |
Handles
accounts accepts three shapes, mixed freely in one list:
| You write | Means |
|---|---|
Mastodon or @Mastodon | the account on mastodon.social |
pluralistic@mamot.fr | an account on any other fediverse server |
https://fosstodon.org/@kev | the 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")
| Field | Type | Meaning |
|---|---|---|
rowType | string | post |
sourceMode | string | The mode that produced the row |
statusId | string | The post id on mastodon.social — feed it back into threadReplies |
url | string | The post as its own server links it (the canonical link) |
localUrl | string | The same post on mastodon.social; always usable in postUrls |
createdAt | string | When the post was written, ISO 8601 UTC |
editedAt | string | When it was last edited, or null |
language | string | Language declared by the posting app (often null for other software) |
visibility | string | public or unlisted — what the public API serves |
sensitive | boolean | Flagged sensitive by its author, or carrying a content warning |
contentWarning | string | The warning text, or null |
text | string | Post text as a reader sees it: markup removed, links written out in full |
authorHandle | string | Author name without the server, lowercased |
authorFullHandle | string | name@server for every author, so rows can be compared |
authorDisplayName | string | Name the author shows |
authorInstance | string | Server the author lives on, e.g. fosstodon.org |
authorUrl | string | Author's profile link |
authorFollowers, authorPosts | number | Public counts of the author at fetch time |
authorIsBot | boolean | The account marks itself as automated |
replyCount, reblogCount, favouriteCount, quoteCount | number | Engagement at fetchedAt |
hashtags | string[] | Every hashtag of the post, lowercased |
matchedHashtag | string | The hashtag of your input that produced this row (hashtagPosts) |
mentions | string[] | Handles mentioned in the post |
linkUrls | string[] | URLs the post links to |
cardUrl, cardTitle, cardPublisher | string | Link preview the server built, when there is one |
mediaCount | number | Number of attachments |
mediaTypes | string[] | image, video, gifv, audio |
mediaUrls | string[] | File links of the attachments (linked, never downloaded) |
isReblog | boolean | The row came from a boost (accountPosts with includeReblogs) |
rebloggedBy | string | Who boosted it |
inReplyToStatusId | string | The post this one replies to, or null |
replyDepth | number | threadReplies: 0 for the post you asked for, 1, 2… for replies, negative above it |
threadRootStatusId, threadRootUrl | string | threadReplies: the post the thread was read from |
applicationName | string | App that published the post, when the server knows it |
sourceRef | string | The exact input value this row came from |
found | boolean | false on the one row that explains an unknown handle, id or opt-out |
notFoundReason | string | Why that input gave no data |
fetchedAt | string | Fetch time, ISO 8601 UTC — engagement counts are a snapshot of this moment |
Tag row (rowType: "tag")
| Field | Type | Meaning |
|---|---|---|
tag | string | Hashtag name as the server spells it |
url | string | Tag page on mastodon.social |
usesToday, accountsToday | number | Uses and distinct accounts on the newest day the server reports |
uses7d, accounts7d | number | Sum over the seven days the server sends |
history | object[] | { day (UTC date), accounts, uses }, newest first |
trendRank | number | Position in the list the source returned, from 1 |
sourceRef | string | trending-tags, or your searchQuery |
Link row (rowType: "link")
| Field | Type | Meaning |
|---|---|---|
title, description | string | Article title and teaser as the server read them |
url | string | The article itself |
publisherName, authorName | string | Publisher and author, when the page declares them |
publishedAt | string | Publication date, ISO 8601 UTC |
language | string | Language of the article |
imageUrl | string | Preview image the server cached |
usesToday, accountsToday, uses7d, accounts7d | number | How often and by how many accounts it was shared |
trendRank | number | Position in the trending list |
Profile row (rowType: "profile")
| Field | Type | Meaning |
|---|---|---|
handle, fullHandle | string | mastodon and mastodon@mastodon.social |
displayName | string | Name the account shows |
instance | string | Server the account lives on |
bio | string | Profile text, markup removed |
followersCount, followingCount, postsCount | number | Public counts (the lists are never collected) |
createdAt | string | Join date, ISO 8601 UTC |
lastPostAt | string | Date of the last post the server knows (UTC date) |
isBot, isLocked, isDiscoverable | boolean | Flags the account set itself |
profileFields | object[] | { name, value, verified } of the links next to the bio |
avatarUrl, url | string | Avatar file and profile link |
found, notFoundReason | boolean, string | false 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 osfrom apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])# 1) read a hashtag, 2) take the localUrl of the loudest post, 3) read its threadposts = client.actor("yadroo/mastodon-public-posts").call(run_input={"mode": "hashtagPosts", "hashtags": ["opensource"], "minFavourites": 5, "maxItems": 20})rows = client.dataset(posts["defaultDatasetId"]).list_items().itemsloudest = 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.
| Run | Rows | Cost |
|---|---|---|
| Trending hashtags, one request | 20 | $0.001 + 20 × $0.0015 = $0.031 |
| Hashtag monitoring, last 24 h | 50 | $0.001 + 50 × $0.0015 = $0.076 |
| Thread of a busy announcement | 200 | $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=statusesanswers empty arrays without one. Topics are therefore reached by hashtag, by account or through the trending lists — andhashtagSearchexists 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
authorInstancetells 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+linkssometimes 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.
threadRepliesneeds a mastodon.social link or id; a link on another server is refused with a message. Use thelocalUrlcolumn 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
maxItemsabove 40 cannot be filled inhashtagSearch. - Accounts that opted out are skipped. An account hosted on mastodon.social that asked not to be indexed is not exported in
accountPostsorprofiles; you get one row withfound: falseinstead. Posts it puts under a hashtag are still read inhashtagPosts, 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
fetchedAtand 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
onlyNewonly 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: falseand 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.