# Substack Scraper - Newsletters, Posts, Comments & Leaderboards (`topapi/substack-scraper`) Actor

Scrape Substack newsletters, posts and comments: subscriber counts, paid plan prices, author socials and contact emails, leaderboard ranks, full text of free posts, likes, restacks. Search by keyword or category. $0.45 per 1,000 newsletters, $0.27 per 1,000 posts, $0.18 per 1,000 comments.

- **URL**: https://apify.com/topapi/substack-scraper.md
- **Developed by:** [Top Api](https://apify.com/topapi) (community)
- **Categories:** News, Social media, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.45 / 1,000 newsletter rows

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

### What does Substack Scraper do?

**Substack Scraper** extracts public data from [Substack](https://substack.com) and turns newsletter links, search terms or leaderboard categories into structured rows: newsletters with subscriber counts, paid plan prices, author socials and contact emails; posts with likes, comments, restacks and the full text of free posts; and comment threads. Built for sponsorship and partnership teams, creator-economy analysts, media researchers and AI teams who need Substack data without an API key.

Substack Scraper - Substack API alternative - Substack newsletter scraper - Substack leaderboard scraper - Substack posts and comments scraper

- **Three kinds of rows in one actor:** newsletters, posts and comments. The `type` field says which.
- **Newsletter leads in every newsletter row:** subscriber count, paid subscriber band, monthly / annual / founding prices, author followers, X, LinkedIn, Instagram, YouTube, TikTok and website links, contact emails from the About page, post count and posting rate.
- **Leaderboards and categories:** Substack's paid Bestsellers and Rising leaderboards for all 33 categories, plus each category's Top, Top paid and Top free lists, in rank order.
- **Full text of free posts:** plain text, HTML or Markdown, with likes, comments, restacks, word count, tags and bylines. Paid posts keep their metadata and Substack's public teaser line.
- **Search by keyword:** find newsletters or posts on any topic.
- **No login, cookies or API key:** the actor reads the same public data substack.com shows a signed-out visitor.
- **Monitor mode:** run it on a schedule and get only new posts and comments, and newsletters whose subscribers, prices or post count changed.
- **Pay per row, no start fee:** $0.27 per 1,000 posts, $0.45 per 1,000 newsletters, $0.18 per 1,000 comments.

### What data can you get from Substack?

<table>
<tr>
<td>📰 Newsletter name, link, tagline</td>
<td>👥 Subscriber count <code>subscriberCount</code></td>
<td>💰 Paid subscriber band</td>
</tr>
<tr>
<td>💳 Monthly, annual, founding price</td>
<td>🏆 Leaderboard rank and category</td>
<td>🏅 Bestseller tier</td>
</tr>
<tr>
<td>✍️ Author name, handle, bio, followers</td>
<td>🔗 X, LinkedIn, Instagram, YouTube, TikTok, website</td>
<td>📧 Contact emails <code>emails</code></td>
</tr>
<tr>
<td>🗓️ Created, first and last post date</td>
<td>🧮 Post count, posts per month</td>
<td>🎙️ Podcast, community, sections</td>
</tr>
<tr>
<td>📝 Post title, subtitle, link, date</td>
<td>🔒 Audience (free / paid)</td>
<td>❤️ Likes, comments, restacks</td>
</tr>
<tr>
<td>📄 Full text, HTML, Markdown</td>
<td>🔤 Word count, reading time, tags</td>
<td>💬 Comments with author, likes, depth</td>
</tr>
</table>

### How to scrape Substack with Substack Scraper

1. [Create a free Apify account](https://console.apify.com/sign-up) (it comes with free monthly credit).
2. Open **Substack Scraper** and click **Try for free**.
3. Paste newsletter links, add search terms, or pick leaderboard categories.
4. Choose what to read: posts, full text, comments, and the filters you need.
5. Click **Start**.
6. Download the results as JSON, CSV, Excel or HTML, or read them through the API.

### Input

Combine any of these in one run. Every field is optional; a run with no input scrapes a small documented sample.

| Parameter | Type | Description |
|---|---|---|
| `startUrls` | array | Newsletter links or names (`https://www.lennysnewsletter.com`, `lenny.substack.com`, `lenny`), post links (`…/p/<slug>`), profiles (`substack.com/@lenny`), leaderboard links (`substack.com/leaderboard/technology/paid`) or search links. |
| `searchQueries` | array | Search terms. |
| `searchType` | string | `newsletters` (default) or `posts`. |
| `maxResultsPerSearch` | integer | Newsletters or posts per search term. Default 20. |
| `leaderboardCategories` | array | Category slugs: `technology`, `business`, `culture`, `finance` … or `bestseller` (all categories, Rising list). |
| `boardTypes` | array | `paid` (Bestsellers, default), `rising`, `top`, `topPaid`, `topFree`. |
| `maxResultsPerBoard` | integer | Newsletters per list in rank order. Default 25; leaderboards hold 100, Top lists up to 525. |
| `includePublicationInfo` | boolean | A newsletter row for each newsletter link. Default on. |
| `newsletterDetails` | boolean | Author profile (followers, socials), About page (emails), sitemap (post count), latest posts (last post date, posts per month). Default on. |
| `includeRecommendations` | boolean | Newsletters each newsletter recommends. Default off. |
| `minSubscribers`, `onlyPaidNewsletters`, `onlyWithEmail` | | Newsletter filters, applied before the charge. |
| `includePosts` | boolean | Posts of each newsletter link. Default on. |
| `scrapePostsOfFoundNewsletters` | boolean | Posts of newsletters found by search or leaderboards. Default off. |
| `maxPostsPerNewsletter` | integer | Posts per newsletter after filters. Default 20. 0 = newsletter rows only. |
| `postSort` | string | `new` (default) or `top`. |
| `searchInNewsletter` | string | Only posts matching these words (Substack's archive search). |
| `includeContent` | boolean | Full text of free posts. Default on. |
| `contentFormat` | string | `textAndHtml` (default), `text`, `html`, `markdown`, `all`. |
| `audience`, `postType`, `dateFrom`, `dateTo`, `withinDays`, `minReactions`, `minComments`, `minWordCount`, `titleContains` | | Post filters, applied before the charge. |
| `includeComments` | boolean | Comment rows for every saved post. Default off. |
| `maxCommentsPerPost` | integer | Comments and replies per post. Default 50. |
| `maxItems` | integer | Most rows per run (all kinds together). Default 500. 0 = no cap. |
| `monitorMode`, `monitorStoreName` | | Only rows that are new or changed since the last run, remembered in a named store. |
| `proxyConfiguration` | object | Optional; Substack answers without a proxy. |
| `maxConcurrency` | integer | Parallel requests. Default 8. |

#### Input JSON

```json
{
  "startUrls": ["https://www.lennysnewsletter.com", "newsletter.pragmaticengineer.com"],
  "maxPostsPerNewsletter": 50,
  "audience": "free",
  "includeComments": true,
  "maxCommentsPerPost": 100
}
```

#### Build a sponsor or partner list from a leaderboard

```json
{
  "leaderboardCategories": ["technology", "business", "finance"],
  "boardTypes": ["paid", "rising"],
  "maxResultsPerBoard": 100,
  "maxPostsPerNewsletter": 0
}
```

Each newsletter row carries its rank, subscriber count, prices, author socials and emails. Add `minSubscribers` or `onlyWithEmail` to keep only the newsletters you can pitch.

#### Find newsletters on a topic

Put the topic in `searchQueries` and keep `searchType` on `newsletters`. Substack's newsletter search sometimes answers empty; the actor asks again, and if it stays empty it takes the newsletters behind the keyword's matching posts, ranked with the keyword in the newsletter's name or description first. The `foundVia` field says which route found each one.

### Output

Every row has a `type`: `newsletter`, `post` or `comment`. A newsletter row (excerpt, from a real run):

```json
{
  "type": "newsletter",
  "rank": 1,
  "boardType": "paid",
  "category": "Technology",
  "name": "SemiAnalysis",
  "url": "https://newsletter.semianalysis.com",
  "subscriberCount": 325000,
  "subscriberCountString": "325K+",
  "paidSubscriberDetail": "Tens of thousands of paid subscribers",
  "monthlyPrice": 50,
  "annualPrice": 500,
  "currency": "USD",
  "postCount": 349,
  "lastPostDate": "2026-10-23T19:06:44.981Z",
  "authorName": "Dylan Patel",
  "authorFollowerCount": 378202,
  "authorTwitterUrl": "https://x.com/dylan522p",
  "authorLinkedinUrl": null,
  "email": "Sales@SemiAnalysis.com",
  "leaderboardRank": 1,
  "createdAt": "2025-09-22T15:54:12.958Z"
}
```

A post row (excerpt):

```json
{
  "type": "post",
  "postId": 197186925,
  "title": "Open Thread 433",
  "url": "https://www.astralcodexten.com/p/open-thread-433",
  "publishedAt": "2026-05-11T08:43:51.779Z",
  "audience": "everyone",
  "postType": "newsletter",
  "wordCount": 283,
  "reactionCount": 72,
  "commentCount": 887,
  "restacks": 5,
  "authorName": "Scott Alexander",
  "publicationName": "Astral Codex Ten",
  "bodyText": "This is the weekly visible open thread. Post about anything you want, ask random questions, whatever. ACX has an unofficial subreddit, Disco…"
}
```

A comment row (excerpt):

```json
{
  "type": "comment",
  "commentId": 200886717,
  "postTitle": "The Dilbert Afterlife",
  "parentId": 200690507,
  "depth": 1,
  "body": "Yeah, I got this feeling, too. I don't think it was the author's intent, but he did end up saying some apologist/sympathizer statements that…",
  "publishedAt": "2026-01-16T23:15:48.515Z",
  "authorName": "Braden F",
  "authorHandle": "bradenf",
  "reactionCount": 3,
  "childrenCount": 3,
  "commentUrl": "https://www.astralcodexten.com/p/the-dilbert-afterlife/comment/200886717"
}
```

#### Fields

| Row | Field | Type | Meaning | Filled |
|---|---|---|---|---|
| newsletter | `name` | string | Newsletter name. | 100% |
| newsletter | `url` | string | Link to the newsletter (custom domain when it has one), the post, or the comment. | 100% |
| newsletter | `subscriberCount` | integer | Subscribers as a number (exact when Substack shows "Over 1,200,000 subscribers", else from the rounded figure). Null when the newsletter hides it. | 94% |
| newsletter | `subscriberCountString` | string | Subscriber figure as Substack shows it, e.g. "1.2M+". | 94% |
| newsletter | `totalSubscriberDetail` | string | Substack's exact subscriber line, e.g. "Over 1,200,000 subscribers". | 94% |
| newsletter | `paidSubscriberDetail` | string | Paid-subscriber band, e.g. "Tens of thousands of paid subscribers". | 91% |
| newsletter | `bestsellerTier` | integer | Substack Bestseller badge tier (100, 1000, 10000 paid subscribers). | 100% |
| newsletter | `monthlyPrice` | number | Monthly plan price. | 99% |
| newsletter | `annualPrice` | number | Annual plan price. | 99% |
| newsletter | `foundingPrice` | number | Founding-member plan price (minimum). | 75% |
| newsletter | `currency` | string | Plan currency. | 99% |
| newsletter | `plans` | array | Every active plan: name, interval, price, currency, founding. | 99% |
| newsletter | `postCount` | integer | Posts in its sitemap (every published post link). | 95% |
| newsletter | `lastPostDate` | string | Date of its newest post. | 71% |
| newsletter | `postsPerMonth` | number | Posting rate over its latest 12 posts. | 71% |
| newsletter | `createdAt` | string | When the newsletter was created on Substack. | 100% |
| newsletter | `authorName` | string | Author name. | 100% |
| newsletter | `authorHandle` | string | Author Substack handle. | 100% |
| newsletter | `authorFollowerCount` | integer | Author followers on Substack. | 88% |
| newsletter | `authorTwitterUrl` | string | Twitter / X link. | 70% |
| newsletter | `authorLinkedinUrl` | string | LinkedIn link. | 41% |
| newsletter | `authorInstagramUrl` | string | Instagram link. | 17% |
| newsletter | `authorYoutubeUrl` | string | YouTube link. | 35% |
| newsletter | `websiteUrl` | string | Author website. | 41% |
| newsletter | `socialLinks` | object | Every link on the author profile, by network. | 80% |
| newsletter | `email` | string | Contact email found on the About page (contact-style mailboxes first). | 27% |
| newsletter | `emails` | array | Every contact email found on the About page. | 27% |
| newsletter | `leaderboardRank` | integer | Best leaderboard rank Substack shows for the newsletter. | 84% |
| newsletter | `leaderboardCategory` | string | Category of that leaderboard rank. | 84% |
| newsletter | `rank` | integer | Position in the list this row came from (1 = top). | 100% |
| newsletter | `boardType` | string | List the row came from: paid, rising, top, topPaid, topFree. | 100% |
| newsletter | `category` | string | Category of the list the row came from, else the newsletter's leaderboard category. | 100% |
| newsletter | `contributors` | array | Writers and admins: name, handle, role, owner flag, bio, photo. | 99% |
| newsletter | `hasPodcast` | boolean | Publishes a podcast. | 100% |
| post | `postId` | integer | Substack post id. | 100% |
| post | `title` | string | Post title. | 100% |
| post | `subtitle` | string | Post subtitle. | 100% |
| post | `url` | string | Link to the newsletter (custom domain when it has one), the post, or the comment. | 100% |
| post | `publishedAt` | string | When the post or comment was published. | 100% |
| post | `audience` | string | Who can read it: everyone, only_paid, founding, only_free. | 100% |
| post | `postType` | string | newsletter, podcast, thread or video. | 100% |
| post | `wordCount` | integer | Words in the post. | 100% |
| post | `reactionCount` | integer | Likes. | 100% |
| post | `commentCount` | integer | Comments. | 100% |
| post | `restacks` | integer | Restacks (shares on Substack). | 100% |
| post | `authorName` | string | Author name. | 98% |
| post | `authors` | array | Every byline: id, name, handle, bio, photo, guest flag. | 98% |
| post | `publicationName` | string | Newsletter the post or comment belongs to. | 100% |
| post | `bodyText` | string | Full post text (free posts). | 76% |
| post | `bodyHtml` | string | Full post HTML (free posts). | 76% |
| post | `previousPostSlug` | string | Slug of the previous post. | 76% |
| comment | `commentId` | integer | Substack comment id. | 100% |
| comment | `postTitle` | string | Title of that post. | 100% |
| comment | `parentId` | integer | Id of the comment this one replies to (null for a top-level comment). | 72% |
| comment | `depth` | integer | 0 for a top-level comment, 1 for a reply, and so on. | 100% |
| comment | `body` | string | Comment text. | 99% |
| comment | `publishedAt` | string | When the post or comment was published. | 100% |
| comment | `authorName` | string | Author name. | 99% |
| comment | `authorHandle` | string | Author Substack handle. | 97% |
| comment | `reactionCount` | integer | Likes. | 100% |
| comment | `childrenCount` | integer | Direct replies. | 100% |
| comment | `commentUrl` | string | Link to the comment. | 100% |

Filled = share of rows with a value on real runs (2026-10-28): newsletters from the 100 Technology Bestsellers, posts from 176 Astral Codex Ten posts (paid posts carry no body), comments from 9,080 Astral Codex Ten comments. Every field is listed with its description in the Output tab's dataset schema (131 fields).

### Use cases

- 🤝 **Newsletter sponsorship and partnerships:** ranked lists of newsletters by category with subscriber counts, prices and the author's contact links.
- 📊 **Creator-economy research:** paid tiers and prices, subscriber bands and posting rates across the leaderboards over time.
- 🔎 **Media monitoring:** new posts from a watch list of newsletters on a schedule, with likes and comments.
- 🧠 **AI and RAG datasets:** full text of free posts in Markdown or plain text.
- 💬 **Audience research:** comment threads with commenter, likes and replies.
- 🧭 **Competitor tracking:** a rival newsletter's subscriber count, prices and post count, flagged when they change.

### How much does it cost to scrape Substack?

Pay per row, no start fee. Rows removed by your filters, duplicates and unchanged rows in monitor mode are not charged.

| Row | Price per 1,000 |
|---|---|
| Newsletter | $0.45 |
| Post (full text of free posts included) | $0.27 |
| Comment | $0.18 |

| Example run | Rows | Cost |
|---|---|---|
| 10 newsletters | 10 | $0.0045 |
| 100 posts of one newsletter + its newsletter row | 101 | $0.02745 |
| 100 Technology Bestsellers | 100 | $0.045 |
| 1,000 posts | 1,000 | $0.27 |
| 1,000 comments | 1,000 | $0.18 |

Set **Maximum cost per run** in the run options to cap any run; the actor stops cleanly at the limit.

### Integrations, API, scheduling & MCP

- **Schedules:** run Substack Scraper daily with monitor mode on to collect only new posts and changed newsletters.
- **Webhooks:** get a call when a run finishes and pull the dataset.
- **Make, Zapier, n8n, Google Sheets, Slack:** connect through Apify's integrations.
- **API:** start runs and read results from [Python](https://apify.com/TopApi/substack-scraper/api/python) or [JavaScript](https://apify.com/TopApi/substack-scraper/api/javascript) with the Apify client.
- **MCP:** AI agents can call the actor through the [Apify MCP server](https://apify.com/TopApi/substack-scraper/api/mcp).

### More scrapers from TopApi

- 🐦 [X Profile Scraper](https://apify.com/TopApi/x-profile-scraper): X (Twitter) profiles, followers and bios
- 📺 [YouTube Channel Email Scraper](https://apify.com/TopApi/youtube-channel-email-scraper): creator emails and socials
- 📈 [Social Blade Scraper](https://apify.com/TopApi/socialblade-scraper): creator stats, ranks and history
- 🎮 [Twitch Scraper](https://apify.com/TopApi/twitch-scraper): channels, live streams, VODs and clips
- 📸 [Instagram Email Scraper](https://apify.com/TopApi/instagram-contact-scraper): emails, phones and websites from profiles

All TopApi actors: [apify.com/TopApi](https://apify.com/TopApi)

### Limits & empty runs

- Substack rate-limits each address. The actor paces itself and, when Apify's shared address keeps drawing refusals, moves to a datacenter route. Measured: 100 posts with full text in 84 s, 9,080 comments in 16 s, 25 leaderboard newsletters with details in 30 s, 100 in 4.8-5.7 minutes, 1,000 posts with full text in 16.5 minutes.
- Substack's newsletter search answers empty to about half of requests, and to every request from Apify's addresses; the actor then uses the newsletters behind the keyword's matching posts (`foundVia`).
- Paid posts: metadata and the public teaser line only.
- Hidden subscriber counts are `null`, never 0 (94% of the Technology Bestsellers show one). About-page emails were found for 27% of them.
- Leaderboards hold 100 newsletters; Top lists up to 525.
- A free post whose full text Substack refuses is left out, not charged, and listed in the run summary. A run that finds nothing says why and charges nothing.

### FAQ

#### How much does Substack Scraper cost?

$0.45 per 1,000 newsletters, $0.27 per 1,000 posts and $0.18 per 1,000 comments, with no start fee. The full text of free posts is included in the post price.

#### How many results can Substack Scraper return?

Every post in a newsletter's archive, up to 100 newsletters per leaderboard, up to 525 per category Top list, and as many search results as Substack lists. `maxItems` caps a run.

#### Can Substack Scraper find newsletter emails and socials?

Yes. Each newsletter row carries the contact emails written on its About page and the links on the author's Substack profile (X, LinkedIn, Instagram, YouTube, TikTok, website).

#### Does Substack Scraper get paid posts?

It returns paid posts' metadata (title, date, likes, comments, word count) and Substack's public teaser line. The paid body is not read.

#### Can I monitor newsletters on a schedule?

Yes. Turn on monitor mode and schedule the actor; each run returns only new posts and comments and newsletters whose subscribers, prices or post count changed.

#### Can I use Substack Scraper with the Apify API or Python?

Yes. Start runs and read datasets with the Apify client for Python or JavaScript, or plain HTTP.

#### Can AI agents use Substack Scraper through MCP?

Yes, through the Apify MCP server.

#### Do I need a Substack account, login or API key?

No. The actor reads public Substack data only.

#### Why did my Substack Scraper run return no results?

The run's status message says why: the link is not a Substack newsletter, the filters removed everything, or nothing is new in monitor mode. Nothing is charged for an empty run.

#### Your feedback

Found a bug or need a field? Open an issue in the **Issues** tab.

# Actor input Schema

## `startUrls` (type: `array`):

A newsletter ("https://www.lennysnewsletter.com", "lenny.substack.com" or "lenny"; custom domains work), a post (".../p/<slug>"), a profile ("substack.com/@lenny"), a leaderboard ("substack.com/leaderboard/technology/paid") or a search link ("substack.com/search/ai").

## `searchQueries` (type: `array`):

Words to search Substack for, e.g. "artificial intelligence", "personal finance". Search type below picks newsletters or posts.

## `searchType` (type: `string`):

What the search terms find: newsletters (one full newsletter row each) or posts.

## `maxResultsPerSearch` (type: `integer`):

Newsletters (or posts) per search term.

## `leaderboardCategories` (type: `array`):

Substack categories to read rankings from. "All categories" has a Rising list only.

## `boardTypes` (type: `array`):

Which ranking of each category: Substack's paid Bestsellers leaderboard (100), the Rising leaderboard (100), or the category's Top / Top paid / Top free lists (up to 525).

## `maxResultsPerBoard` (type: `integer`):

Newsletters per category list, in rank order. Leaderboards hold 100; Top lists up to 525.

## `includePublicationInfo` (type: `boolean`):

Save a newsletter row for each newsletter link (newsletters found by search or a leaderboard always get one).

## `newsletterDetails` (type: `boolean`):

Read the author's public profile (followers, Twitter/X, LinkedIn, Instagram, YouTube, TikTok, website), the About page (contact emails), the sitemap (post count) and the latest posts (last post date, posts per month). Off = faster big leaderboard sweeps with the list fields only.

## `includeRecommendations` (type: `boolean`):

Add the newsletters each newsletter recommends (name, link) to its row.

## `minSubscribers` (type: `integer`):

Skip newsletters with fewer subscribers (or no public count). Skipped newsletters are not charged and their posts are not read.

## `onlyPaidNewsletters` (type: `boolean`):

Skip newsletters without a paid plan (not charged, posts not read).

## `onlyWithEmail` (type: `boolean`):

Skip newsletters with no contact email on their About page (not charged, posts not read).

## `includePosts` (type: `boolean`):

Read the posts of every newsletter link you gave.

## `scrapePostsOfFoundNewsletters` (type: `boolean`):

Also read the posts of every newsletter a search term or leaderboard found.

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

Posts per newsletter after your filters. 0 = newsletter rows only.

## `postSort` (type: `string`):

Read each archive newest first or most-liked first.

## `searchInNewsletter` (type: `string`):

Only posts of each newsletter that match these words (Substack's archive search).

## `includeContent` (type: `boolean`):

Read the full body of every free post. Paid posts keep their metadata and Substack's public teaser line.

## `contentFormat` (type: `string`):

How the full text of free posts is saved: plain text, HTML, Markdown, or several at once.

## `audience` (type: `string`):

Keep only free posts or only paid posts. Paid posts carry metadata and the public teaser line, never the paid body.

## `postType` (type: `string`):

Keep only one kind of post.

## `dateFrom` (type: `string`):

YYYY-MM-DD.

## `dateTo` (type: `string`):

YYYY-MM-DD.

## `withinDays` (type: `integer`):

Keep only posts from the last N days.

## `minReactions` (type: `integer`):

Keep only posts with at least this many likes.

## `minComments` (type: `integer`):

Keep only posts with at least this many comments.

## `minWordCount` (type: `integer`):

Keep only posts with at least this many words.

## `titleContains` (type: `string`):

Only posts whose title or subtitle contains these words.

## `includeComments` (type: `boolean`):

Save the comment threads of every post (replies flattened, with parent id and depth).

## `maxCommentsPerPost` (type: `integer`):

Comments and replies per post, in Substack's order (best first).

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

Most rows to save in this run (newsletters, posts and comments together). 0 = no cap.

## `monitorMode` (type: `boolean`):

Scheduled runs save only posts and comments not saved before, and newsletters that are new or changed (subscribers, prices, post count, rank). State is kept in the named store below.

## `monitorStoreName` (type: `string`):

Named key-value store that remembers what earlier runs saved. Use one name per watch list.

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

Optional. Substack answers without a proxy; a proxy you set is tried first.

## `maxConcurrency` (type: `integer`):

Requests to Substack at the same time.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.lennysnewsletter.com"
  ],
  "searchType": "newsletters",
  "maxResultsPerSearch": 20,
  "leaderboardCategories": [
    "technology"
  ],
  "boardTypes": [
    "paid"
  ],
  "maxResultsPerBoard": 5,
  "includePublicationInfo": true,
  "newsletterDetails": true,
  "includeRecommendations": false,
  "onlyPaidNewsletters": false,
  "onlyWithEmail": false,
  "includePosts": true,
  "scrapePostsOfFoundNewsletters": false,
  "maxPostsPerNewsletter": 3,
  "postSort": "new",
  "includeContent": true,
  "contentFormat": "textAndHtml",
  "audience": "all",
  "postType": "all",
  "includeComments": false,
  "maxCommentsPerPost": 50,
  "maxItems": 500,
  "monitorMode": false,
  "monitorStoreName": "substack-monitor",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "maxConcurrency": 8
}
```

# Actor output Schema

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

One item per Substack newsletter, post or comment (the type field says which).

## `summary` (type: `string`):

Per-source counts, filtered and duplicate counts, not-found and unreachable inputs, the search route each keyword used, routes, field fill and charges.

# 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 = {
    "startUrls": [
        "https://www.lennysnewsletter.com"
    ],
    "leaderboardCategories": [
        "technology"
    ],
    "maxResultsPerBoard": 5,
    "maxPostsPerNewsletter": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("topapi/substack-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 = {
    "startUrls": ["https://www.lennysnewsletter.com"],
    "leaderboardCategories": ["technology"],
    "maxResultsPerBoard": 5,
    "maxPostsPerNewsletter": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("topapi/substack-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 '{
  "startUrls": [
    "https://www.lennysnewsletter.com"
  ],
  "leaderboardCategories": [
    "technology"
  ],
  "maxResultsPerBoard": 5,
  "maxPostsPerNewsletter": 3
}' |
apify call topapi/substack-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,topapi/substack-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/P7BeYhBHeK5CLcsOm/builds/BnQkV5MFdQ0JBL2m2/openapi.json
