# Substack Newsletter Scraper — Posts, Reactions & Authors (`foxlabs/substack-newsletter-data`) Actor

Scrape any Substack newsletter's posts from its public API: title, URL, publish date, free/paid, word count, reactions, comments, restacks, tags and authors — with optional full body text. No API key, no login, no browser.

- **URL**: https://apify.com/foxlabs/substack-newsletter-data.md
- **Developed by:** [Berkan Kaplan](https://apify.com/foxlabs) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 post delivereds

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?

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

## Substack Newsletter Scraper — Posts, Reactions & Authors 🚀

**foXLabs creator & content data:** [Bluesky](https://apify.com/foxlabs/bluesky-creator-intelligence) · [Product Hunt](https://apify.com/foxlabs/producthunt-launch-leads) · [Y Combinator](https://apify.com/foxlabs/ycombinator-company-data)

🎉 Scrape any Substack newsletter's posts straight from its public API — one clean row per post with **title, URL, publish date, free/paid, word count, reactions, comments, restacks, tags and authors**, plus optional full body text. No API key, no login, no browser.

### 🔍 What is the Substack Newsletter Scraper — and when should you use it?

Give this Actor a list of newsletters and it returns their posts as structured data — built for content & competitive intelligence (what a newsletter publishes and how it performs), creator lead-gen (the authors), and topic/trend research.

**Use it when you need:** a newsletter's posts with engagement (reactions/comments/restacks); to see what's free vs paid; or the authors and tags behind a publication.

**Use something else when:** you need subscriber counts (behind Substack's login — not available); other creator sources → [Bluesky](https://apify.com/foxlabs/bluesky-creator-intelligence), [Product Hunt](https://apify.com/foxlabs/producthunt-launch-leads).

### 🤖 Use with AI agents

**Already on the Apify MCP server?** Ask for this Actor by name: `foxlabs/substack-newsletter-data`.

**Your agent can pay for its own runs.** This Actor is pay-per-event with agentic payments, so an agent can discover it, run it and settle the bill over **x402 (USDC on Base)** or **Skyfire** — no Apify account or API token of its own. Billing is per delivered post, never for errors.

Otherwise paste this into Claude, ChatGPT, Cursor or any MCP-enabled assistant:

```
I want Substack newsletter posts using the Apify Actor
`foxlabs/substack-newsletter-data`.

Input: `newsletters` is a list of handles/URLs; `maxPostsPerNewsletter` caps rows; `postedAfter`
filters by date; `includeBody` adds full text. Each row has title, postDate, audience (free/paid),
reactionCount, commentCount, restacks, authors and tags.

Start with:
{ "newsletters": ["bariweiss", "https://www.lennysnewsletter.com"], "maxPostsPerNewsletter": 20 }

Ask me which newsletters to track, run the Actor, then summarise the posts by engagement.
```

The machine-readable API, MCP config and OpenAPI definition live at `apify.com/foxlabs/substack-newsletter-data.md`.

### 📋 Overview

Everything you need to turn a Substack newsletter into clean, structured data — posts, engagement, audience and authors — in one Actor, with no login, cookies or API key.

**Why teams pick this Actor:**

- ✅ **Engagement, not just text** — reactions, comments and restacks per post.
- 💵 **Free vs paid** — each post's `audience` and an `isPaid` flag show a newsletter's monetisation.
- ✍️ **Authors & tags** — the bylines behind each post and its topic tags.
- 🎯 **Honest nulls** — a value Substack didn't give is `null`, never 0; subscriber counts (login-only) are not faked.
- 💰 **Pay only for results** — one `post` event per delivered row; unreachable newsletters return a free error row.
- 🤖 **Agent-ready** — MCP + x402 agentic payments.

### ✨ Features

- 🔍 **Posts by newsletter** — handle, `.substack.com` URL or custom domain; newest first, paginated.
- 📊 **Performance** — `reactionCount`, `commentCount`, `childCommentCount`, `restacks`, `wordcount`.
- 💵 **Monetisation** — `audience` (everyone / only_paid) + `isPaid`.
- ✍️ **Attribution** — `authors`, `authorHandles`, `tags`.
- 📝 **Optional full text** — `includeBody` adds `bodyText` (HTML stripped). 🗓️ `postedAfter` date filter.

### 🎬 Quick Start

```bash
curl -X POST "https://api.apify.com/v2/acts/foxlabs~substack-newsletter-data/runs?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"newsletters": ["bariweiss", "https://www.lennysnewsletter.com"], "maxPostsPerNewsletter": 20}'
```

### 🚀 Getting Started (3 steps)

1. **List your newsletters** — one per line: a handle, a `.substack.com` URL or a custom domain.
2. **Set the cap & filters** — `maxPostsPerNewsletter`, `postedAfter`, `includeBody` for full text.
3. **Run and export** — get a clean dataset as JSON, CSV or Excel.

### 📥 Input

| Field | Default | What it does |
|---|---|---|
| `newsletters` | required | One per line: `bariweiss`, `bariweiss.substack.com`, or a custom domain (`www.lennysnewsletter.com`). For a custom domain use the exact host that serves the newsletter. |
| `maxPostsPerNewsletter` | 100 | Posts per newsletter, newest first. |
| `postedAfter` | — | Keep only posts on/after this date (YYYY-MM-DD). |
| `includeBody` | false | Add the post's full body as plain text (`bodyText`). |
| `maxResults` | 0 | Max posts across all newsletters (0 = no cap). |

```json
{ "newsletters": ["bariweiss", "https://www.lennysnewsletter.com"], "maxPostsPerNewsletter": 20 }
```

### 📤 Output

One row per post. A value Substack didn't give is `null`, never 0.

| Field | What it is |
|---|---|
| `newsletter` | the host the post came from |
| `title`, `subtitle`, `slug`, `url` | the post |
| `type` | newsletter / podcast / thread |
| `audience`, `isPaid` | everyone / only_paid, and a paid flag |
| `postDate`, `updatedAt` | publish and update times |
| `wordcount` | word count |
| `reactionCount`, `commentCount`, `childCommentCount`, `restacks` | engagement |
| `authors`, `authorHandles`, `tags` | bylines and topic tags |
| `coverImage`, `description`, `teaser` | media and summary |
| `bodyText` | full text (with `includeBody`) |
| `language`, `source`, `scrapedAt` | meta |

```json
{
  "newsletter": "www.lennysnewsletter.com",
  "title": "All of the Lenny & Friends Summit talks",
  "url": "https://www.lennysnewsletter.com/p/all-of-the-lenny-and-friends-summit",
  "type": "newsletter",
  "audience": "everyone",
  "isPaid": false,
  "postDate": "2026-09-29T...",
  "wordcount": 1276,
  "reactionCount": 287,
  "commentCount": 7,
  "restacks": 5,
  "authors": ["Lenny Rachitsky"],
  "tags": [],
  "source": "Substack"
}
```

### 💼 Use cases

**1. Competitive content intelligence** — what a rival newsletter publishes and how it performs.
*Input:* competitor newsletters, scheduled. *Output:* posts + reactions/comments/restacks. *Use:* benchmark cadence and what resonates.

**2. Creator lead-gen** — the authors behind newsletters in your niche.
*Input:* a list of newsletters. *Output:* `authors` + `authorHandles`. *Use:* build an outreach list.

**3. Monetisation research** — how much a newsletter gates behind paid.
*Input:* the newsletters. *Output:* `audience`/`isPaid` per post. *Use:* study free-vs-paid strategy.

### 🔗 Integration

**JavaScript / Node.js**

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('foxlabs/substack-newsletter-data').call({
  newsletters: ['bariweiss', 'https://www.lennysnewsletter.com'], maxPostsPerNewsletter: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0]);
```

**Python**

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('foxlabs/substack-newsletter-data').call(
    run_input={'newsletters': ['bariweiss', 'https://www.lennysnewsletter.com'], 'maxPostsPerNewsletter': 20}
)
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)
```

Written for `apify-client` 3 for Python, where `call()` returns a run object; with version 1 or 2 it returns a dict — write `run['defaultDatasetId']` there.

**Automation (n8n / Zapier / Make):** schedule → HTTP request to the Actor API with your `newsletters` → handle the JSON dataset → push to a sheet, CRM or dashboard.

### 📊 Pricing

Pay per event, one event per delivered row: **`post`**. An unreachable newsletter returns a free error row — not charged. [View current pricing.](https://apify.com/foxlabs/substack-newsletter-data)

### ❓ FAQ

**Do I need a Substack account or API key?** No. It reads each newsletter's own public post API — no key, no login, no browser.

**Can I get subscriber counts?** No — those are behind Substack's login. This Actor returns posts, engagement, audience and authors, never a fabricated subscriber number.

**How do I enter a custom-domain newsletter?** Use the exact host that serves it (often `www.` or the `.substack.com` subdomain). If a host doesn't serve the API you get a clear error row.

**Can I get the full article text?** Yes — turn on `includeBody` (HTML stripped to plain text). Paid posts may be truncated behind the paywall.

**Can I export to CSV / Excel / JSON?** Yes — directly from the Apify dataset.

### 🐛 Troubleshooting

- **A newsletter returns an error row** — the host doesn't serve the public post API; use the exact Substack host (often `www.` or `{handle}.substack.com`).
- **Fewer posts than expected** — `maxPostsPerNewsletter`/`postedAfter` capped them, or the newsletter has that many.
- **Empty `tags`** — not every post is tagged; returned only when present.
- **No subscriber count** — it's login-only; not provided (never faked).

### ⚠️ Trademark

Independent, third-party tool. Not affiliated with, endorsed by or connected to Substack Inc. "Substack" and related marks belong to their owners and are used here for identification only.

### ⚖️ Is it legal to scrape this data?

This reads each newsletter's own public post API — the same endpoint the site itself calls. Posts carry author names (published publicly); personal data is protected by the GDPR and similar laws, so only process it with a legitimate basis. See Apify's blog post on the legality of web scraping.

### 🤝 Support & contact

- 🌐 **Website:** [data.foxlabs.com.tr](https://data.foxlabs.com.tr)
- 📧 **Email:** info@foxlabs.com.tr
- 🐛 **Issues:** open a ticket in the Actor's **Issues** tab — the run's `SOURCE_REPORT` record holds what was asked and which newsletters were unreachable.
- 🧰 **More clean data actors:** [Fox Labs on Apify](https://apify.com/foxlabs)

### Changelog

#### 0.1.0 — 2026-10-12

- Initial release: structured posts from any Substack newsletter via its public API — title, date, free/paid, word count, reactions, comments, restacks, tags, authors, optional full body; `postedAfter` filter; pay-per-event (`post`); pure HTTP, no key, no browser.

# Changelog

This Actor's version history is a separate document: https://apify.com/foxlabs/substack-newsletter-data/changelog.md

# Actor input Schema

## `newsletters` (type: `array`):

One newsletter per line: a handle (`bariweiss`), a Substack URL (`bariweiss.substack.com`) or a custom domain (`www.lennysnewsletter.com`). A bare handle resolves to `handle.substack.com`. Newsletters that publish on their own web address (e.g. Lenny's Newsletter, The Free Press) must be given by that address — their posts are not served on `handle.substack.com`. If you pass such a handle, the run tells you to re-run with the web address.

## `maxPostsPerNewsletter` (type: `integer`):

How many posts to deliver per newsletter, newest first.

## `postedAfter` (type: `string`):

Keep only posts published on or after this date (posts are read newest-first, so the walk stops at older ones).

## `includeBody` (type: `boolean`):

Add the post's full body as plain text (`bodyText`). Larger output; off by default.

## `maxResults` (type: `integer`):

Maximum posts to deliver across all newsletters (0 = no cap).

## Actor input object example

```json
{
  "newsletters": [
    "bariweiss",
    "https://www.lennysnewsletter.com"
  ],
  "maxPostsPerNewsletter": 100,
  "includeBody": false,
  "maxResults": 0
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `report` (type: `string`):

No description

# 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 = {
    "newsletters": [
        "bariweiss",
        "https://www.lennysnewsletter.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("foxlabs/substack-newsletter-data").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 = { "newsletters": [
        "bariweiss",
        "https://www.lennysnewsletter.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("foxlabs/substack-newsletter-data").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 '{
  "newsletters": [
    "bariweiss",
    "https://www.lennysnewsletter.com"
  ]
}' |
apify call foxlabs/substack-newsletter-data --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,foxlabs/substack-newsletter-data"
        }
    }
}
```

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/i1fDEZDQ4u9oke2fY/builds/a7gUQaOIJNFhVacWf/openapi.json
