# Mastodon Posts Scraper — Hashtags & Accounts (`devilscrapes/mastodon-posts-scraper`) Actor

Scrape Mastodon posts by hashtag or by account across any federated instance via the public REST API — no login, no app registration. We paginate the Link-header timeline, retry on rate limits, and skip dead hashtags/accounts instead of failing the run.

- **URL**: https://apify.com/devilscrapes/mastodon-posts-scraper.md
- **Developed by:** [DevilScrapes](https://apify.com/devilscrapes) (community)
- **Categories:** Social media
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

<div align="center">
  <img src=".actor/icon.svg" width="160" alt="Devil Scrapes mark" />

## Mastodon Posts Scraper — Hashtags & Accounts

**💰 $0.002 / post**  ·  $0.20 per run  ·  pay only for results  ·  no credit card to try

*We do the dirty work so your dataset stays clean.* 😈

Scrape Mastodon posts by hashtag or by account across any federated instance via the public REST API — no login, no app registration. Run both query modes together in one job and get clean, denormalised rows ready for social listening, brand monitoring, or fediverse research.

</div>

***

### 🎯 What this scrapes

Mastodon is federated — thousands of independently-run instances, no single "Twitter-style" global feed. This Actor targets the two query modes that stay reliably open across the network without authentication:

1. **Hashtag timeline** — `GET /api/v1/timelines/tag/<tag>` on your chosen instance, walked backward through history via `Link`-header pagination.
2. **Account timeline** — resolves an `@handle` (bare or federated `user@remote.instance` form) to its numeric id, then walks that account's public statuses.

Run any mix of hashtags and accounts in a single job; each query is independently fault-isolated, so one dead hashtag or mistyped handle never sinks the whole run.

### 🔥 Features

- 🛡️ **Browser fingerprint impersonation** — `curl-cffi` replays a real Chrome TLS handshake for every request.
- 🔁 **Retries with exponential backoff** on `429 / 5xx`, honouring `Retry-After` and `x-ratelimit-reset` — the tag and account endpoints share one rate-limit bucket, so we pace accordingly.
- 🧱 **Fault isolation per query** — a bad hashtag, a typo'd handle, or a dead custom instance is logged and skipped, never a crashed run.
- 🌍 **Multi-instance support** — point at `mastodon.social` or any other federated host; not locked to one server.
- 🧊 **Clean, typed dataset rows** — Pydantic-validated, ISO-8601 timestamps, stable ids, both raw HTML and stripped plain text for every post.
- 💰 **Pay-Per-Event pricing** — $0.20 to start a run, then $0.002 per post row written.
- 🌐 **Optional Apify Proxy routing** — off by default for the public API; switch on for small self-hosted instances that rate-limit by source IP.

### 💡 Use cases

- **Social listening** — track a hashtag your brand or product cares about across the fediverse, not just one instance.
- **Community/brand monitoring** — watch what a competitor's or partner's Mastodon account is posting without checking manually.
- **Fediverse research** — build a longitudinal dataset of hashtag or account activity for academic or journalistic analysis.
- **Content curation** — pull recent posts under a topic hashtag to seed a newsletter, digest, or aggregator feed.
- **Sentiment / NLP pipelines** — feed `content_text` straight into a classifier without writing your own HTML stripper.

### ⚙️ How to use it

1. Open the Actor input form.
2. Set **Instance** to the federated host you want to query (default `mastodon.social`).
3. Add one or more **Hashtags** and/or **Accounts** — at least one of the two must be non-empty.
4. Adjust **Results per query** to cap how many posts are pulled per hashtag/account (default 40, max 2000).
5. Leave **Proxy configuration** off unless you're hitting a small self-hosted instance that rate-limits by IP.
6. Click **Start**. Results stream into the default dataset and can be exported as JSON, CSV, Excel, or XML.

### 📥 Input

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `instance` | string | no | `mastodon.social` | Federated host to query, no scheme/trailing slash |
| `hashtags` | array\<string> | no\* | `[]` | Hashtag names, with or without leading `#` |
| `accounts` | array\<string> | no\* | `[]` | Account handles, bare or `user@remote.instance` form |
| `results_per_query` | integer | no | `40` | Max post rows per hashtag/account (1–2000) |
| `proxyConfiguration` | object | no | `{"useApifyProxy": false}` | Apify Proxy config |

\* At least one of `hashtags` or `accounts` must be non-empty.

```json
{
  "instance": "mastodon.social",
  "hashtags": ["python", "fediverse"],
  "accounts": ["Mastodon"],
  "results_per_query": 5,
  "proxyConfiguration": { "useApifyProxy": false }
}
```

### 📤 Output

One row per post, tagged with which query produced it.

```json
{
  "id": "117243600697061639",
  "instance": "mastodon.social",
  "source_type": "hashtag",
  "source_query": "fediverse",
  "created_at": "2026-09-09T23:09:30.904Z",
  "content_html": "<p>...</p>",
  "content_text": "I've spent half of today trying to get more of my YouTube videos uploaded to @samxaviagaming ...",
  "visibility": "public",
  "favourites_count": 0,
  "reblogs_count": 1,
  "is_reblog": false,
  "account_acct": "sam@solent.social",
  "account_display_name": "Sam",
  "mentions": [{"id": "116954324987104741", "username": "samxaviagaming", "acct": "samxaviagaming@solent.social", "url": "https://solent.social/@samxaviagaming"}],
  "tags": [{"name": "fediverse", "url": "https://mastodon.social/tags/fediverse"}],
  "scraped_at": "2026-09-10T12:00:00+00:00"
}
```

### 💰 Pricing

Pay-Per-Event (PPE) — you pay only for what lands in your dataset:

| Event | Price (USD) | When |
|---|---|---|
| `actor-start` | $0.20 | Once per run, at boot |
| `post-scraped` | $0.002 | Per post row written to the dataset |

At 1,000 posts a run costs about **$2.20** total. Zero rows written means you pay only the $0.20 start fee.

### 🚧 Limitations

- **No public/global timeline** — Mastodon's `/api/v1/timelines/public` endpoint requires authentication on many instances (confirmed on `mastodon.social` itself); only hashtag and account timelines ship in this version.
- **Shared rate-limit bucket** — hashtag and account queries on the same instance share one bucket (300 requests / 5 minutes on `mastodon.social`); running many hashtags/accounts with a high `results_per_query` may take longer as the Actor backs off.
- **No streaming API** — this Actor polls the REST API; it does not subscribe to `/api/v1/streaming` for live updates.
- **Custom-emoji shortcodes stay literal** — `content_text`/`content_html` keep `:shortcode:` text as the API sends it; no image substitution is performed.

### ❓ FAQ

**Do I need a Mastodon account or API key?**

No. Every endpoint this Actor calls — tag timelines and account lookups/statuses — is public and unauthenticated.

**Can I scrape more than one instance in a single run?**

Not in one run — `instance` is a single federated host per run. Run the Actor multiple times (or on a schedule) with different `instance` values to cover several servers.

**What happens if a hashtag or account doesn't exist?**

That one query is logged as a warning and skipped; the rest of your configured hashtags/accounts still run. The run only fails if every configured query fails outright.

**What happens if my hashtag or account has zero matching posts?**

The run succeeds with zero rows for that query and a status message describing what was searched — that's a genuine empty result, not a failure.

### 💬 Your feedback

Found a bug, hit a rate limit, or need a new field on the output row? Open an issue on the Actor's Apify Store page or contact the Devil Scrapes team at [apify.com/DevilScrapes](https://apify.com/DevilScrapes). We ship updates within days of validated reports.

# Actor input Schema

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

Federated Mastodon host to query, without scheme or trailing slash (e.g. <code>mastodon.social</code>). Pasted URLs are normalised automatically.

## `hashtags` (type: `array`):

Hashtag names to scrape from the instance's tag timeline, with or without a leading <code>#</code>. At least one of <strong>Hashtags</strong> or <strong>Accounts</strong> must be non-empty.

## `accounts` (type: `array`):

Account handles to scrape, bare username (<code>Mastodon</code>) or federated <code>user@remote.instance</code> form, with or without a leading <code>@</code>. At least one of <strong>Hashtags</strong> or <strong>Accounts</strong> must be non-empty.

## `results_per_query` (type: `integer`):

Maximum number of post rows to fetch per hashtag or account. Client-side cap on top of Link-header pagination.

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

Apify Proxy configuration. Off by default — this is a public, non-fingerprinting REST API. Enable if you are scraping a small self-hosted instance that rate-limits by source IP.

## Actor input object example

```json
{
  "instance": "mastodon.social",
  "hashtags": [
    "python",
    "fediverse"
  ],
  "accounts": [
    "Mastodon"
  ],
  "results_per_query": 5,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

All dataset items as JSON.

## `datasetItemsCsv` (type: `string`):

Same data exported to CSV.

## `datasetView` (type: `string`):

Open the run dataset in the Console.

# 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",
    "hashtags": [
        "python",
        "fediverse"
    ],
    "accounts": [
        "Mastodon"
    ],
    "results_per_query": 5,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/mastodon-posts-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",
    "hashtags": [
        "python",
        "fediverse",
    ],
    "accounts": ["Mastodon"],
    "results_per_query": 5,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/mastodon-posts-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",
  "hashtags": [
    "python",
    "fediverse"
  ],
  "accounts": [
    "Mastodon"
  ],
  "results_per_query": 5,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call devilscrapes/mastodon-posts-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,devilscrapes/mastodon-posts-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/dKSpdSgaH93Cf9jLR/builds/BqjULqw4YoRs5Fg3H/openapi.json
