# Mastodon Scraper (`dami_studio/mastodon-scraper`) Actor

Mastodon scraper for public posts by hashtag, account or server timeline, with no login. Each row holds the text, author, follower count, replies, boosts, favourites, media links, language and hashtags. A server only holds the posts that reached it. $1.50 per 1,000 posts.

- **URL**: https://apify.com/dami_studio/mastodon-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Social media, Integrations, News
- **Features:** Limited permissions
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.44 / 1,000 posts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

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

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

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

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

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

# README

**Point it at any Mastodon server and pull public posts by hashtag, account or timeline**: the text
with the HTML stripped out, who posted it, their follower count, replies, boosts and favourites, any
media links, the language and the hashtags.

The thing to understand before you plan anything: **a Mastodon server only holds the posts that
reached it.** Ask two servers for the same hashtag and you get two different sets, neither of them
complete. There is no central index to ask instead, so if coverage matters, run it against two or
three servers and merge.

| | |
|---|---|
| **Input** | A server host, plus a hashtag or a handle |
| **Output** | One row per post: text, author, follower count, replies, boosts, favourites, media links, language, hashtags and link |
| **Ceiling** | 2,000 posts per run |
| **Account needed** | None, and no login anywhere |
| **Price** | $1.50 per 1,000 posts on the Free plan, a little less on paid plans. The free plan's $5 a month covers about 3,300 |

### 🔍 What Mastodon Scraper does

One server per run, in one of three modes.

**Hashtag** reads that server's timeline for a tag. Put `opensource` or `#opensource` in `query`;
either works.

**Account** reads one account's posts. `Mastodon` for a local account, or `user@otherserver.social`
for someone elsewhere, which the server you asked resolves for you.

**Public** reads the server's own timeline. With `local` off you get the federated view, everything
that server has seen. With `local` on you get only posts written there, which is the better one for
watching a single community.

One caveat on that last mode: **large servers, `mastodon.social` among them, require a login for
their public timeline.** Ask anyway and you get a `NOT_SERVED` row and nothing else. Hashtag and account
modes still work on those servers.

### 📋 What data you get from each Mastodon post

| What you get | Field |
|---|---|
| The post's id and its public web address | `id`, `url` |
| The text, with HTML removed | `text` |
| Handle, display name and follower count of the poster | `author`, `authorName`, `authorFollowers` |
| When it was posted | `createdAt` |
| Replies, boosts and favourites | `repliesCount`, `reblogsCount`, `favouritesCount` |
| The language | `language` |
| Attached images and video | `mediaUrls` |
| Hashtags | `tags` |

### ▶️ How to scrape Mastodon posts

1. Open [Mastodon Scraper](https://apify.com/dami_studio/mastodon-scraper) and click **Try for free**.
2. Type the server into **Instance**, without `https://`.
3. Pick a **Mode**, then fill **Query** with the hashtag or handle.
4. Set **Max posts**, then click **Start**.
5. Download the dataset as JSON, CSV, Excel or XML.

### 💰 How much does it cost to scrape Mastodon posts?

**$1.50 per 1,000 posts** on the Free plan, and the same in all three modes. Paid plans pay a little less: $1.48 on Starter, $1.46 on Scale and $1.44 on Business.
On the free plan, the $5 Apify gives you each month covers about 3,300 posts.

You pay per post row. Diagnostic rows are free, and a hashtag or timeline that turns up empty costs
you nothing.

### 📥 What you give it

```json
{
  "instance": "fosstodon.org",
  "mode": "hashtag",
  "query": "opensource",
  "maxItems": 200
}
```

| Field | Default | What it is |
|---|---|---|
| `instance` | `mastodon.social` | The server host, without `https://`: `fosstodon.org`, `mas.to`, `infosec.exchange`. |
| `mode` | `hashtag` | `hashtag`, `account` or `public`. |
| `query` | none | The hashtag or the handle. Leading `#` and `@` are stripped for you. Ignored in public mode, required in the other two. The form shows `opensource` as an example, so an API call has to send its own. |
| `local` | `false` | Public mode only. On means posts written on that server, off means everything it has federated. |
| `maxItems` | `100` | 1 to 2,000. Paging is automatic, forty at a time. |
| `notionConnector` | none | Optional. Writes every delivered post into your Notion. Authorise a connector once under Settings, API and Integrations, MCP connectors, then pick it here. |
| `notionParentId` | none | Optional. The Notion data source id to write into. Empty creates the pages privately in your workspace. |
| `proxyConfiguration` | off | Optional network setting. Off is right for a normal run. |

### 📤 What you get back

A real row from a real run:

```json
{
  "ok": true,
  "id": "117267800308722144",
  "text": "Linuxiac: Navidrome 0.64 Music Server & Streamer Adds Experimental Jellyfin Music API Support\nhttps://linuxiac.com/navidrome-0-64-adds-experimental-jellyfin-music-api-support/\n#linux #opensource #tech",
  "author": "kiltedtux",
  "authorName": "FuzzyFeeds",
  "authorFollowers": 26,
  "createdAt": "2026-09-14T05:43:48.744Z",
  "repliesCount": 0,
  "reblogsCount": 0,
  "favouritesCount": 0,
  "language": "en",
  "mediaUrls": [],
  "tags": ["linux", "opensource", "tech"],
  "url": "https://mastodon.social/@kiltedtux/117267800308722144"
}
```

| Field | How to read it |
|---|---|
| `text` | Links and hashtags stay as text; entities are decoded. |
| `author`, `authorName` | On a boosted post, both belong to the person who passed it on, not the original poster. |
| `repliesCount`, `reblogsCount`, `favouritesCount` | The numbers that server knows about, which are lower than the true totals across the network. `0` also means absent. |
| `tags` | Hashtags Mastodon parsed out of the post. |

**Boosted posts are the one thing to watch.** The row credits the account that passed the post on,
and its engagement numbers come from that wrapper rather than from the original, so those counts read
low. Filter them out by comparing `author` with the handle you asked for.

### 🧾 Reading the output

Posts carry `ok: true`. Anything with `ok: false` carries an `errorCode` and is not charged.

| Code | What it means |
|---|---|
| `BAD_INPUT` | An unknown mode, or a hashtag or account run with no `query`. |
| `NOT_FOUND` | That server does not know the account. Check the handle, and add `@server` for a remote one. |
| `NO_RESULTS` | The timeline exists and is empty as far as this server is concerned. |
| `NOT_SERVED` | This post was not served this run. Re-run it later. Runs before 13 October 2026 wrote `BLOCKED` here. |
| `NETWORK` | The host was unreachable, or the name was misspelt. |
| `TIME_LIMIT` | The run got close to its time limit and stopped on its own. The posts delivered before that are complete and charged. Give the run more time, or ask for fewer posts, to get the rest. |

A run that ends on one of these rows still finishes as succeeded, including when the time limit
stopped it before any post came back: the `TIME_LIMIT` row and an amber status message say so.

The Console's **Overview** table shows author, text, favourites, boosts, date and URL, and renders
diagnostic rows as blank lines. Download the dataset for the tags, media and follower counts.

### 💡 What people use it for

- Watching a brand or project name as a hashtag across two or three servers and merging on `id`.
- Archiving an account's public posts before a server shuts down, which happens often enough to
  matter.
- Building a plain-text corpus from a niche server's local timeline, with `local` switched on.
- Finding who in a community actually gets boosted, by sorting a tag pull on `reblogsCount`.

### 🚧 What it does not do

- **No replies and no threads.** You get the post, not the conversation under it.
- **One server per run.** Covering the network means several runs and a merge on your side.
- **No search across servers.** Mastodon has no central index, and neither does this.
- **Public timelines are often gated.** Expect `NOT_SERVED` on the largest servers in public mode.
- **No private, followers-only or direct posts**, and no login that could reach them.
- **A failure partway through a long run loses that run's posts.** You get the diagnostic row
  instead. Smaller `maxItems` values survive a shaky server better.
- **Counts are one server's view**, always at or below the real total.
- **Rows are a snapshot.** Posts get edited, deleted and defederated.
- It is not an official Mastodon tool. Dami's Studio is independent and is not affiliated with or endorsed by Mastodon, or by any other company named on this page.

### 🧭 Which social scraper do you need?

| If you want | Use |
|---|---|
| Posts from a Mastodon server by hashtag, account or timeline | This one |
| Posts from Bluesky accounts or a Bluesky search | [Bluesky Scraper](https://apify.com/dami_studio/bluesky-scraper) |
| Posts from a Lemmy instance by feed, community or keyword | [Lemmy Scraper](https://apify.com/dami_studio/lemmy-scraper) |

### ❓ Questions people ask

#### Do I need a Mastodon account to scrape posts?

No. Hashtag and account timelines are public on almost every server. Only the public timeline of a
big server asks for a login, and you get a `NOT_SERVED` row there instead of posts.

#### Which Mastodon server should I use?

The one closest to the topic. A niche server's local timeline is richer for its subject than a giant
server's federated firehose.

#### Why did the same hashtag give me different posts yesterday?

Because the server's view changes as posts federate in. That is Mastodon working as designed, not a
gap here.

#### Can I read someone on another server?

Yes. Use `user@theirserver.social` in account mode and the server you asked will resolve it.

#### How do I drop boosts?

Keep only rows where `author` matches the handle you asked for.

#### Can I call it from code or connect it to an AI assistant?

The [API tab](https://apify.com/dami_studio/mastodon-scraper/api/python) has code for Python, JavaScript and the CLI.
MCP clients like Claude and ChatGPT use
`https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/mastodon-scraper`; coding assistants can load [our skill
file](https://api.apify.com/v2/key-value-stores/BTBHU3DdiuoF4JOsK/records/apify-social-communities-SKILL.md). All
three bill your Apify account at the same price.

#### Is scraping Mastodon legal?

These are public posts on public timelines. They are still
written by identifiable people, so GDPR and similar laws apply and you want a reason for collecting
them. Apify's write-up on [the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/)
is a good starting point, and we are not lawyers.

### 🆘 If something breaks

Open the **Issues** tab on the actor page. Send the server, the mode, the query and the run ID. The
`errorCode` on the diagnostic row usually names the problem by itself.

# Actor input Schema

## `instance` (type: `string`):

The Mastodon instance host to scrape, without the scheme (e.g. "mastodon.social", "fosstodon.org", "mas.to"). The actor calls that instance's public REST API.

## `mode` (type: `string`):

What to scrape: "hashtag" timeline (set query to the hashtag), "account" toots (set query to the @handle), or the "public" / federated timeline of the instance. Note: in account mode, boosted/reblogged rows show the reblogger as author (not the original poster). Some instances (e.g. mastodon.social) require auth for the public timeline and will return a NOT_SERVED diagnostic in public mode.

## `query` (type: `string`):

For hashtag mode: the hashtag to fetch, with or without the leading # (e.g. "opensource"). For account mode: the handle, with or without @ (e.g. "Mastodon" or "user@otherinstance.social"). Ignored in public mode.

## `local` (type: `boolean`):

Public mode only. If on, returns only posts originating on this instance (local timeline). If off, returns the federated timeline (posts from across the fediverse). Ignored in hashtag and account modes.

## `maxItems` (type: `integer`):

Maximum number of posts to return. The actor pages through the timeline (40 per request) until it reaches this limit or runs out of posts.

## `notionConnector` (type: `string`):

Optional. Write each post as a page into your Notion when the run finishes. Authorize a Notion connector once in Settings → API & Integrations → MCP connectors, then pick it here. Leave empty to skip (default), results are always saved to the dataset regardless.

## `notionParentId` (type: `string`):

Optional. The Notion data source ID of the database to write into (only used if a Notion connector is set). Leave empty to create the pages privately in your workspace instead.

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

Optional, and not needed for a normal run. Your own proxy servers are used exactly as given.

## Actor input object example

```json
{
  "instance": "mastodon.social",
  "mode": "hashtag",
  "query": "opensource",
  "local": false,
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Scraped rows are stored in the default dataset (one row per result). Blocked/empty/error runs return a single uncharged diagnostic row instead.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "instance": "mastodon.social",
    "query": "opensource"
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/mastodon-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "instance": "mastodon.social",
    "query": "opensource",
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/mastodon-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "instance": "mastodon.social",
  "query": "opensource"
}' |
apify call dami_studio/mastodon-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/mastodon-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/yg9vMXBSYDs3K2UPd/builds/e6VSWVKhzVqBn6bIo/openapi.json
