# Substack Scraper - Whole Archives, Comments & Notes (`clearpath/substack-scraper`) Actor

Export every post of any Substack, not just the newest 480: full text as Markdown, HTML and plain text, reactions, comments with replies, Notes, profiles and publication data with subscriber counts, paid plans and analytics. Custom domains, deep keyword search for posts and Notes. No login.

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

## Pricing

from $1.39 / 1,000 results

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

[![Every Substack post, not just the newest 480. Whole archives with full text, comments, Notes and subscriber counts.](https://api.apify.com/v2/key-value-stores/rfpznBPgVbyLYqNaQ/records/substack-scraper-readme-hero-v1-01b99351fc75.jpg)](https://console.apify.com/actors/xLv7VvQUFabP4Eku9/input)

From **Clearpath**, creators of [Medium Scraper](https://apify.com/clearpath/medium-scraper), [Reddit Search Scraper](https://apify.com/clearpath/reddit-search-scraper), [Reddit Post Comments Scraper](https://apify.com/clearpath/reddit-post-comments-bulk-scraper) and [Reddit Subreddit Posts Scraper](https://apify.com/clearpath/reddit-subreddit-posts-scraper). **200,000+ combined lifetime runs** across these three Reddit scrapers.

### Why choose this Substack scraper?

- **The whole archive, not the newest 480.** Substack's archive list stops near 480 posts, and so do tools built on it. This scraper finds every post anyway: 1,270 of 1,270 on Lenny's Newsletter, where a popular Store scraper returned 482. Custom domains and the standalone pages the archive hides are included.
- **Text and context in one run.** Full text as Markdown, HTML and plain text, with reactions, restacks, tags and authors. Add comments with replies, a profile's Notes, and one row per publication with subscriber count, paid plans and prices, recommendations and engagement analytics.
- **Fast and deep.** Keyword search goes past the roughly 2,000 posts Substack's own post search shows: 4,598 for "artificial intelligence" in a local test, older posts included. A profile's 2,118 Notes arrived in about 30 seconds. Run times vary.

<a href="https://console.apify.com/actors/xLv7VvQUFabP4Eku9/input" data-demo-cta="true"><img src="https://api.apify.com/v2/key-value-stores/rfpznBPgVbyLYqNaQ/records/substack-scraper-readme-cta-v1-73d2fc3acdfc.png" width="236" height="48" alt="Get Substack posts"></a>

#### Try it: 20 posts from Lenny's Newsletter, with comments

```json
{
  "startUrls": [
    "https://www.lennysnewsletter.com"
  ],
  "maxItemsPerSource": 20,
  "includeComments": true,
  "maxCommentsPerPost": 10
}
```

[Open the actor input](https://console.apify.com/actors/xLv7VvQUFabP4Eku9/input) and paste this in JSON mode. You get the 20 newest posts with full text, up to 10 comments and replies on each (comments are billed per comment), and one row with the publication's subscriber count, plans and analytics. Remove `maxItemsPerSource` to export the whole archive, or set a date window for recent posts only.

<table>
<tr><td style="padding:10px 14px;background:#FF6719;border:none;border-radius:4px 4px 0 0"><span style="color:#1C1917;font-size:14px;font-weight:700;letter-spacing:0.5px">Clearpath Content Research</span><span style="color:#431407;font-size:13px">&nbsp;&nbsp;&bull;&nbsp;&nbsp;Posts, articles and threads with full text</span></td></tr>
<tr><td style="padding:6px;border:1px solid #E7E5E4;border-top:none;border-radius:0 0 4px 4px;background:#FFF3EB">
<span style="display:inline-block;vertical-align:top;margin:4px;padding:8px 12px;border:1px solid #E7E5E4;border-radius:4px;background:#FFE4D6"><span style="white-space:nowrap"><img src="https://api.apify.com/v2/key-value-stores/rfpznBPgVbyLYqNaQ/records/substack-scraper-readme-nav-v1-82743860cc10.png" width="20" height="20" alt="" style="vertical-align:middle">&nbsp;&nbsp;<a href="https://console.apify.com/actors/xLv7VvQUFabP4Eku9/input" style="color:#9A3412;text-decoration:none;font-weight:700;font-size:13px">Substack Posts</a></span><br><span style="color:#9A3412;font-size:12px;font-weight:600">&#10148; You are here</span></span>
<span style="display:inline-block;vertical-align:top;margin:4px;padding:8px 12px;border:1px solid #E7E5E4;border-radius:4px;background:#FFF3EB"><span style="white-space:nowrap"><img src="https://apify-image-uploads-prod.s3.us-east-1.amazonaws.com/DSvMCAwsufMyZeLyt-actor-IRgwoYrBv3LN1x7eD-OXABeayAUo-medium-scraper-logo.png" width="20" height="20" alt="" style="vertical-align:middle">&nbsp;&nbsp;<a href="https://apify.com/clearpath/medium-scraper" style="color:#1C1917;text-decoration:none;font-weight:700;font-size:13px">Medium Articles</a></span><br><span style="color:#57534E;font-size:12px">Whole tag and author archives</span></span>
<span style="display:inline-block;vertical-align:top;margin:4px;padding:8px 12px;border:1px solid #E7E5E4;border-radius:4px;background:#FFF3EB"><span style="white-space:nowrap"><img src="https://apify-image-uploads-prod.s3.us-east-1.amazonaws.com/DSvMCAwsufMyZeLyt-actor-5LOwKe4dIDEJ64I58-RDxgtg9AKM-reddit-profile-scraper-logo.png" width="20" height="20" alt="" style="vertical-align:middle">&nbsp;&nbsp;<a href="https://apify.com/clearpath/reddit-search-scraper" style="color:#1C1917;text-decoration:none;font-weight:700;font-size:13px">Reddit Search</a></span><br><span style="color:#57534E;font-size:12px">Posts by keyword</span></span>
<span style="display:inline-block;vertical-align:top;margin:4px;padding:8px 12px;border:1px solid #E7E5E4;border-radius:4px;background:#FFF3EB"><span style="white-space:nowrap"><img src="https://apify-image-uploads-prod.s3.us-east-1.amazonaws.com/DSvMCAwsufMyZeLyt-actor-AcNroNEs63pztgCy6-BEG4cWB3Re-reddit-user-posts-comments-scraper-logo.png" width="20" height="20" alt="" style="vertical-align:middle">&nbsp;&nbsp;<a href="https://apify.com/clearpath/reddit-post-comments-bulk-scraper" style="color:#1C1917;text-decoration:none;font-weight:700;font-size:13px">Reddit Comments</a></span><br><span style="color:#57534E;font-size:12px">Full threads and replies</span></span>
</td></tr>
</table>

#### Copy to your AI assistant

```text
clearpath/substack-scraper on Apify returns Substack data as typed rows (type = post, comment, note, profile or publication): posts with full text as bodyHtml, bodyText and bodyMarkdown (free posts; paid posts return only the public preview, marked isPaid=true and bodyAvailable=false), reactions, restacks, tags, authors and their publication with subscriberCount and plans. Inputs combine freely, up to 1,000 per run: startUrls (a publication incl. its own domain, a /s/ section or /t/ tag page, a post, a profile, a Note or a substack.com search link), publications (handle or domain: the whole archive newest first, including posts older than the ~480 Substack's own archive list shows, and hidden pages), profiles (profile row plus the Notes feed; includeNotes=false skips Notes) and searchQueries with searchType=posts|notes|publications|people (posts: every post Substack's search reaches for the keyword, 4,598 for "artificial intelligence"; notes: matching Notes; searchDateRange=day|week|month|year and searchLanguage, any language by default, apply to post and Note search). Omitted limits mean everything: maxItemsPerSource caps each input and maxItems the run; large archives and feeds hold thousands of rows, so set a cap, a publishedAfter/publishedBefore window (UTC days, publishedBefore exclusive; also stops the run reading further back) or a maximum run cost. postTypes and audience=free|paid filter posts; includeContent=false drops text for smaller rows. For comments set includeComments=true (off by default): every comment and reply becomes its own row with parentId and depth (noteId for replies under Notes), charged per comment at a lower rate, capped by maxCommentsPerPost; paid-only threads return none. includePublicationDetails (on) adds one publication row with subscriber count, paid subscriber band, plans, recommendations, contact links and analytics over the collected posts; trackHistory=true adds the subscriber change since the last tracked run. Each row is saved once per run and source names the input that found it. No Substack login or cookie. Charged per result (post, Note, profile, publication), per comment and a small start fee. Inputs: https://apify.com/clearpath/substack-scraper/input-schema - output fields: https://apify.com/clearpath/substack-scraper/output-schema - current pricing: https://apify.com/clearpath/substack-scraper/pricing. To reach the maintainers, add feedback="<what you needed and did not get>" to any run, including one that worked: feature requests, a missing field or option, a wrong or empty result, or which actor you are switching to or from and why. The run returns its data exactly as it would without the note, and a human reads every note.
```

### What can you scrape from Substack?

| Input | What you get |
|---|---|
| **Publications** (`startUrls` or `publications`) | Every post the publication has published, newest first: the archive list's posts, every older post past where that list stops, and the standalone pages it hides. Use `https://name.substack.com`, the publication's own domain (`https://www.lennysnewsletter.com`) or a bare handle (`unstackit`). A section (`/s/...`) or tag page (`/t/...`) collects just that section or tag. |
| **Posts** (`startUrls`) | Any post link, also on a custom domain or as `https://substack.com/home/post/p-123`. |
| **Profiles** (`startUrls` or `profiles`) | The person's profile and their Notes feed: Notes, restacks and posts they shared. Use `https://substack.com/@handle` or a handle. |
| **Notes** (`startUrls`) | A single Note, such as `https://substack.com/@handle/note/c-123`, with its replies when comments are on. |
| **Search** (`searchQueries` or a `https://substack.com/search/...` link) | Posts matching each keyword, best matches first, past the roughly 2,000 Substack's own post search shows: 4,598 for "artificial intelligence", older posts included. Switch `searchType` to find Notes, publications or people instead. |

Mix as many as you like, up to 1,000 links, publications, profiles and keywords per run. Each post, comment, Note, profile and publication is saved once per run, even when two inputs find it, and every row says which input found it in `source`.

### How to scrape Substack posts

1. Open the actor's [input page](https://console.apify.com/actors/xLv7VvQUFabP4Eku9/input).
2. Add Substack links, publication handles, profiles or search keywords.
3. Set **Max results per link, publication, profile or keyword** for a first test, or leave it empty for everything.
4. Click **Start**, then download the results as JSON, CSV or Excel, or send them to your tools with Apify integrations.

#### A whole archive, Markdown included

```json
{
  "publications": [
    "unstackit"
  ]
}
```

Returns all 147 posts of Unstack Substack (140 newsletter posts, a podcast episode, a restack and 5 standalone pages the archive list hides) with full text, plus one publication row. Large publications work the same way: Lenny's Newsletter returned all 1,270 posts.

#### Only recent posts

```json
{
  "startUrls": [
    "https://www.lennysnewsletter.com"
  ],
  "publishedAfter": "2026-09-11"
}
```

Dates are UTC days; `publishedBefore` is exclusive. The run reads only as far back as the window needs, so a monthly check of a large newsletter stays small: this one took 11 seconds for 24 posts. Schedule it to monitor new posts.

#### Keyword search

```json
{
  "searchQueries": [
    "artificial intelligence"
  ],
  "maxItemsPerSource": 500
}
```

Posts across all of Substack, best matches first (`rank` 1, 2, 3...), each with full text and its publication. Substack's own post search shows about 2,000 posts per keyword; leave the limit empty and you get more, recent and older: 4,598 for "artificial intelligence". Narrow with `searchDateRange` (last day, week, month or year) and `searchLanguage` (any language by default).

#### Find publications or people

```json
{
  "searchQueries": [
    "artificial intelligence"
  ],
  "searchType": "publications",
  "maxItemsPerSource": 40
}
```

One row per publication with its subscriber count where Substack shows one, paid subscriber band, plans and prices, founder and contact links. `"searchType": "people"` returns profiles instead.

#### Notes about a keyword

```json
{
  "searchQueries": [
    "artificial intelligence"
  ],
  "searchType": "notes",
  "maxItemsPerSource": 200
}
```

Notes from all of Substack that mention the keyword, best matches first, each with its author, reactions and attachments. Leave the limit empty for every match: 979 Notes for this keyword.

#### A profile and its Notes

```json
{
  "profiles": [
    "kristikeller"
  ],
  "maxItemsPerSource": 200
}
```

One profile row plus the newest 200 items of the Notes feed. Leave the limit empty for the whole feed: 2,118 Notes for this profile.

#### Posts with every comment

```json
{
  "startUrls": [
    "https://unstackit.substack.com/p/substack-callout-blocks"
  ],
  "includeComments": true
}
```

The post plus all 29 comments and replies as separate rows, each with `parentId`, `depth` and whether the post's author wrote it. Comments are billed per comment and are off by default.

#### Input options

| Option | Default | What it does |
|---|---|---|
| `startUrls` | none | Any Substack link: publication, section, tag page, post, profile, Note or search page |
| `publications` | none | Publication handles or domains |
| `profiles` | none | Substack handles |
| `searchQueries` | none | Keywords to search |
| `searchType` | `posts` | `posts`, `notes`, `publications` or `people` for the keywords |
| `maxItemsPerSource` | all | Posts, Notes, publications or people per input |
| `maxItems` | all | Results of any kind in the whole run |
| `publishedAfter`, `publishedBefore` | none | Date window for posts and Notes (UTC days, `publishedBefore` exclusive) |
| `postTypes` | all | Only `newsletter`, `podcast`, `thread`, `video`, `page` or `restack` posts |
| `audience` | both | `free` or `paid` posts only |
| `searchDateRange` | all time | Post and Note search: `day`, `week`, `month` or `year` |
| `searchLanguage` | any | Post and Note search: only results in this language |
| `includeContent` | on | Full text as HTML, plain text and Markdown, plus every post detail |
| `includeComments` | off | Every comment and reply on each post, and replies under Notes, as separate rows |
| `maxCommentsPerPost` | all | Comments and replies kept per post or Note |
| `includeNotes` | on | The Notes feed of each profile |
| `includePublicationDetails` | on | One row per publication with subscribers, plans and analytics |
| `trackHistory` | off | Subscriber change since the last run that tracked the publication |

See the [Input tab](https://console.apify.com/actors/xLv7VvQUFabP4Eku9/input) for every option's details.

### What data do you get from Substack?

Every row has `type`: `post`, `comment`, `note`, `profile` or `publication`. The Console shows one view per type. Each record below is a real row; long text, the nested `publication` object and long lists are shortened where marked, and the **[full rows are in this sample file](https://api.apify.com/v2/key-value-stores/rfpznBPgVbyLYqNaQ/records/substack-scraper-readme-sample-rows-v1-aa5eeed2eb2c.json)**.

#### Posts

Title, subtitle, full text as `bodyHtml`, `bodyText` and `bodyMarkdown`, dates, word count and reading time, cover image, tags, section, reactions by emoji, comments, restacks, authors with bios, podcast audio with duration, transcript and caption files, audio narrations, and the publication with its subscriber count, plans and settings. Codes come with a readable label: `postType` with `postTypeLabel`, `audience` with `audienceLabel`.

Excerpt (text fields and the `publication` object shortened):

```json
{
  "type": "post",
  "id": 141448250,
  "url": "https://unstackit.substack.com/p/substack-forbids-you-to-do-this",
  "slug": "substack-forbids-you-to-do-this",
  "title": "Substack Forbids You To Do This",
  "subtitle": "Things you learn by trying to infringe on a brand name.",
  "description": "Things you learn by trying to infringe on a brand name.",
  "socialTitle": null,
  "searchEngineTitle": null,
  "searchEngineDescription": null,
  "postType": "newsletter",
  "postTypeLabel": "Newsletter post",
  "audience": "everyone",
  "audienceLabel": "Free for everyone",
  "isPaid": false,
  "bodyAvailable": true,
  "bodyFetched": true,
  "publishedAt": "2024-02-07T02:01:10.748Z",
  "updatedAt": "2024-02-07T18:30:17.881Z",
  "language": "en",
  "wordCount": 197,
  "readingTimeMinutes": 1,
  "coverImage": "https://substack-post-media.s3.amazonaws.com/public/images/77db001f-73d2-446b-b9b6-d485ffd41186_1080x1080.png",
  "coverImageIsSquare": true,
  "coverImageIsExplicit": false,
  "tags": [
    "Unstack Substack",
    "Substack Virtual Assistant",
    "Substack Fun Facts",
    "Substack"
  ],
  "tagDetails": [
    {
      "id": "37f89601-ac72-45d6-b286-178be6a29d41",
      "name": "Unstack Substack",
      "slug": "unstack-substack",
      "hidden": false,
      "publicationId": 2328962
    },
    {
      "id": "4e845377-6eea-445d-9248-246245ce8530",
      "name": "Substack Virtual Assistant",
      "slug": "substack-virtual-assistant",
      "hidden": false,
      "publicationId": 2328962
    },
    {
      "id": "6f290684-24bb-4fbb-9538-74070b029032",
      "name": "Substack Fun Facts",
      "slug": "substack-fun-facts",
      "hidden": false,
      "publicationId": 2328962
    },
    {
      "id": "bd2743d6-9195-4dd4-86e5-b40c34107371",
      "name": "Substack",
      "slug": "substack",
      "hidden": false,
      "publicationId": 2328962
    }
  ],
  "sectionId": null,
  "sectionName": null,
  "sectionSlug": null,
  "reactionCount": 18,
  "reactions": {
    "❤": 18
  },
  "commentCount": 10,
  "childCommentCount": 5,
  "restackCount": 2,
  "hasVoiceover": false,
  "audioItems": [],
  "podcastUrl": null,
  "podcastDurationSeconds": null,
  "podcast": {
    "postId": 141448250,
    "podcastEpisodeNumber": null,
    "podcastSeasonNumber": null,
    "podcastEpisodeType": null,
    "shouldSyndicateToOtherFeed": null,
    "syndicateToSectionId": null,
    "hideFromFeed": false,
    "freePodcastUrl": null,
    "freePodcastDuration": null,
    "previewContainsAd": false,
    "wasImportedSelfServeSync": false,
    "draftFreePodcastUrl": null,
    "draftFreePodcastDuration": null
  },
  "transcriptUrl": null,
  "captions": [],
  "truncatedBodyText": "Welcome to our very first Substack Fun Fact. Let’s make it a series, shall we?!",
  "authorId": 165322060,
  "authorName": "Kristi Keller 🇨🇦",
  "authorHandle": "kristikeller",
  "authorPhotoUrl": "https://substackcdn.com/image/fetch/$s_!TdHE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bee1713-6b2a-45fa-bbb6-e9437756af86_316x326.png",
  "authorBio": "Builder of highly engaged communities, helping others create fabulous and FUNCTIONAL Substack publications, all while wearing sweatpants and basking in the comfort of my living room.",
  "bylines": [
    {
      "id": 165322060,
      "name": "Kristi Keller 🇨🇦",
      "handle": "kristikeller",
      "photoUrl": "https://substackcdn.com/image/fetch/$s_!TdHE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bee1713-6b2a-45fa-bbb6-e9437756af86_316x326.png",
      "bio": "Builder of highly engaged communities, helping others create fabulous and FUNCTIONAL Substack publications, all while wearing sweatpants and basking in the comfort of my living room.",
      "isGuest": false,
      "bestsellerTier": 100,
      "profileUrl": "https://substack.com/@kristikeller",
      "profileSetUpAt": "2023-08-25T22:34:50.885Z",
      "readerInstalledAt": "2023-08-26T04:00:31.390Z",
      "status": {
        "bestsellerTier": 100,
        "subscriberTier": 1,
        "leaderboard": null,
        "vip": false,
        "badge": {
          "type": "bestseller",
          "tier": 100
        },
        "subscriber": null
      }
    }
  ],
  "publicationId": 2328962,
  "publicationName": "Unstack Substack",
  "publicationHandle": "unstackit",
  "publicationUrl": "https://unstackit.substack.com",
  "subscriberCount": 6000,
  "publication": {
    "id": 2328962,
    "name": "Unstack Substack",
    "subdomain": "unstackit",
    "customDomain": null,
    "customDomainOptional": false,
    "url": "https://unstackit.substack.com",
    "logoUrl": "https://substackcdn.com/image/fetch/$s_!nr84!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ec83ce1-c3b2-4b6f-87d9-41902e60a69f_500x500.png",
    "logoUrlWide": "https://substackcdn.com/image/fetch/$s_!4MuM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5e413e0-e385-4443-bf6d-ea4b0fb57f81_1344x256.png",
    "heroText": "I help fellow Substackers make the most of their time by unstacking all the back-end chores that come along with writing on this platform.",
    "authorId": 165322060,
    "createdAt": "2024-02-06T18:05:06.583Z",
    "emailFromName": "Unstack Substack",
    "copyright": "Kristi Keller",
    "foundingPlanName": "Level-Up Your Stack!",
    "communityEnabled": true,
    "inviteOnly": false,
    "paymentsState": "enabled",
    "paymentsStateLabel": "Paid subscriptions offered",
    "explicit": false,
    "homepageType": "magaziney",
    "isPersonalMode": false,
    "language": "en"
  },
  "writeCommentPermissions": "everyone",
  "writeCommentPermissionsLabel": "Anyone can comment",
  "previousPostSlug": "start-here",
  "nextPostSlug": "the-fascinating-substack-virtual-assistant",
  "bodyHtml": "<p>Welcome to our very first <strong>Substack Fun Fact</strong>.  Let’s make it a series, shall we?!</p><p>I had no idea I’d be creating a whole series with my second post ever but this one was too good not to share.  Mostly because it’s at my own",
  "bodyText": "Welcome to our very first Substack Fun Fact. Let’s make it a series, shall we?!\nI had no idea I’d be creating a whole series with my second post ever but this one was too good not to share. Mostly because it’s at my own expense.\nDID YOU KNOW…?\nYou cannot have",
  "bodyMarkdown": "Welcome to our very first **Substack Fun Fact**. Let’s make it a series, shall we?!\n\nI had no idea I’d be creating a whole series with my second post ever but this one was too good not to share. Mostly because it’s at my own expense.\n\n# DID YOU KNOW…?\n\nYou",
  "editorV2": false,
  "videoUploadId": null,
  "podcastUploadId": null,
  "shouldSendFreePreview": false,
  "freeUnlockRequired": false,
  "defaultCommentSort": null,
  "topExclusions": [],
  "pins": [],
  "isSectionPinned": false,
  "videoUpload": null,
  "podcastPreviewUploadId": null,
  "podcastUpload": null,
  "podcastPreviewUpload": null,
  "voiceoverUploadId": null,
  "voiceoverUpload": null,
  "bodyJson": null,
  "hasDynamicContent": false,
  "postPreviewLimit": null,
  "teaserPostEligible": true,
  "postCountryBlocks": [],
  "headlineTest": null,
  "coverImagePalette": {
    "Vibrant": {
      "rgb": [
        252,
        148,
        76
      ],
      "population": 3073
    },
    "DarkVibrant": {
      "rgb": [
        130.4142857142857,
        54.64285714285715,
        2.1857142857142837
      ],
      "population": 0
    },
    "LightVibrant": {
      "rgb": [
        252,
        172,
        116
      ],
      "population": 6
    },
    "Muted": {
      "rgb": [
        150.47802197802199,
        63.049450549450555,
        2.521978021978013
      ],
      "population": 0
    },
    "DarkMuted": {
      "rgb": [
        150.47802197802199,
        63.049450549450555,
        2.521978021978013
      ],
      "population": 0
    },
    "LightMuted": {
      "rgb": [
        212,
        180,
        164
      ],
      "population": 1
    }
  },
  "isGeoblocked": false,
  "isEditorPreview": false,
  "hasCashtag": false,
  "audienceBeforeArchived": null,
  "exemptFromArchivePaywall": false,
  "podcastArtUrl": null,
  "showGuestBios": true,
  "meterType": "none",
  "liveStreamId": null,
  "isPublished": true,
  "sectionPins": [],
  "hasShareableClips": false,
  "unlockedWithCampaign": false,
  "source": {
    "type": "publication",
    "value": "https://unstackit.substack.com",
    "title": "Unstack Substack",
    "url": "https://unstackit.substack.com"
  },
  "scrapedAt": "2026-10-11T10:18:18.890Z"
}
```

**Paid posts** come with the preview every visitor sees: `isPaid: true`, `bodyAvailable: false`, and the preview in the body fields. Comments on paid-only threads are not public, so they are not returned.

#### Comments and replies

One row per comment or reply, in thread order. `parentId` and `depth` rebuild the thread; `isAuthor` marks the post's authors. Replies under a Note carry `noteId` instead of `postId`.

```json
{
  "type": "comment",
  "id": 351972979,
  "url": "https://unstackit.substack.com/p/substack-callout-blocks/comment/351972979",
  "kind": "reply",
  "postId": 199369343,
  "postTitle": "How to Make Readers Stick Around For Longer Than One Post",
  "postUrl": "https://unstackit.substack.com/p/substack-callout-blocks",
  "postPublishedAt": "2026-10-03T12:02:13.784Z",
  "noteId": null,
  "publicationId": 2328962,
  "publicationName": "Unstack Substack",
  "publicationUrl": "https://unstackit.substack.com",
  "parentId": 351890799,
  "rootId": 351890799,
  "depth": 1,
  "body": "Aww Tamsin, I super appreciate that you upgraded ☺️ Don’t ever feel bad though...some of them are free but some are not. I try to offer a good balance for everyone. Thank you so much and I'm so glad you're finding them helpful!",
  "bodyJson": {
    "type": "doc",
    "attrs": {
      "schemaVersion": "v1",
      "title": null
    },
    "content": [
      {
        "type": "paragraph",
        "content": [
          {
            "type": "text",
            "text": "Aww Tamsin, I super appreciate that you upgraded ☺️ Don’t ever feel bad though...some of them are free but some are not. I try to offer a good balance for everyone. Thank you so much and I'm so glad you're finding them helpful!"
          }
        ]
      }
    ]
  },
  "title": "Aww Tamsin, I super appreciate that you upgraded ☺️ Don’t ever feel bad though...some of them are free but some are n...",
  "publishedAt": "2026-10-03T15:00:10.125Z",
  "editedAt": null,
  "authorId": 165322060,
  "authorName": "Kristi Keller 🇨🇦",
  "authorHandle": "kristikeller",
  "authorPhotoUrl": "https://substackcdn.com/image/fetch/$s_!TdHE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bee1713-6b2a-45fa-bbb6-e9437756af86_316x326.png",
  "authorProfileUrl": "https://substack.com/@kristikeller",
  "authorBestsellerTier": 100,
  "authorBestsellerLabel": "Bestseller: hundreds of paid subscribers",
  "isAuthor": true,
  "isPinned": false,
  "pinnedByUserId": null,
  "reactionCount": 1,
  "reactions": {
    "❤": 1
  },
  "restackCount": 0,
  "replyCount": 0,
  "attachments": [],
  "deleted": false,
  "status": "published",
  "ancestorPath": "351890799",
  "reactorNames": [],
  "bans": [],
  "suppressed": false,
  "userBanned": false,
  "userBannedForComment": false,
  "userSlug": "kristikeller",
  "metadata": {
    "isAuthor": true,
    "membershipState": "subscribed",
    "eligibleForGift": false,
    "authorOnOtherPub": {
      "name": "Unstack Substack",
      "id": 2328962,
      "baseUrl": "https://unstackit.substack.com"
    }
  },
  "canDm": false,
  "userStatus": {
    "bestsellerTier": 100,
    "subscriberTier": 1,
    "leaderboard": null,
    "vip": false,
    "badge": {
      "type": "bestseller",
      "tier": 100
    },
    "subscriber": null
  },
  "score": 10,
  "restacked": false,
  "source": {
    "type": "url",
    "value": "https://unstackit.substack.com/p/substack-callout-blocks",
    "title": "How to Make Readers Stick Around For Longer Than One Post",
    "url": "https://unstackit.substack.com/p/substack-callout-blocks"
  },
  "scrapedAt": "2026-10-11T10:18:28.856Z"
}
```

#### Notes

One row per feed item: `kind` is `note` (a Note), `restack` (a restacked post or Note, with `restackedPost` or `restackedNote`) or `post` (a post shared in the feed). Attachments carry their own `url`, collected in `attachmentUrls`.

```json
{
  "type": "note",
  "id": 46113288,
  "entityKey": "c-46113288",
  "kind": "note",
  "url": "https://substack.com/@kristikeller/note/c-46113288",
  "body": "Question of the day:",
  "bodyJson": {
    "type": "doc",
    "attrs": {
      "schemaVersion": "v1"
    },
    "content": [
      {
        "type": "paragraph",
        "content": [
          {
            "type": "text",
            "text": "Question of the day:"
          }
        ]
      }
    ]
  },
  "title": "Question of the day:",
  "publishedAt": "2023-12-27T17:26:16.475Z",
  "authorId": 165322060,
  "authorName": "Kristi Keller 🇨🇦",
  "authorHandle": "kristikeller",
  "authorPhotoUrl": "https://substackcdn.com/image/fetch/$s_!TdHE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bee1713-6b2a-45fa-bbb6-e9437756af86_316x326.png",
  "authorBio": "Builder of highly engaged communities, helping others create fabulous and FUNCTIONAL Substack publications, all while wearing sweatpants and basking in the comfort of my living room.",
  "authorProfileUrl": "https://substack.com/@kristikeller",
  "authorBestsellerTier": 100,
  "authorBestsellerLabel": "Bestseller: hundreds of paid subscribers",
  "reactionCount": 7,
  "reactions": {
    "❤": 7
  },
  "restackCount": 1,
  "replyCount": 7,
  "attachments": [
    {
      "id": "7120d278-e449-4b12-a83a-acf0be72d854",
      "type": "image",
      "imageUrl": "https://substack-post-media.s3.amazonaws.com/public/images/c4cc07f8-f3b2-4f14-8601-3372cce50aa1_1080x1080.png",
      "imageWidth": 1080,
      "imageHeight": 1080,
      "explicit": false,
      "url": "https://substack-post-media.s3.amazonaws.com/public/images/c4cc07f8-f3b2-4f14-8601-3372cce50aa1_1080x1080.png"
    }
  ],
  "attachmentUrls": [
    "https://substack-post-media.s3.amazonaws.com/public/images/c4cc07f8-f3b2-4f14-8601-3372cce50aa1_1080x1080.png"
  ],
  "language": null,
  "editedAt": null,
  "parentId": null,
  "canReply": true,
  "isMuted": false,
  "context": {
    "type": "note",
    "timestamp": "2023-12-27T17:26:16.475Z",
    "fallbackReason": "//TODO: create fallback reasons",
    "fallbackUrl": null,
    "isFresh": false,
    "source": "db-note",
    "page": null,
    "pageRank": 2
  },
  "publicationId": 2328962,
  "publicationName": "Unstack Substack",
  "publicationUrl": "https://unstackit.substack.com",
  "publication": {
    "id": 2328962,
    "subdomain": "unstackit",
    "customDomainOptional": false,
    "name": "Unstack Substack",
    "logoUrl": "https://substack-post-media.s3.amazonaws.com/public/images/9ec83ce1-c3b2-4b6f-87d9-41902e60a69f_500x500.png",
    "authorId": 165322060,
    "userId": 165322060,
    "handlesEnabled": false,
    "explicit": false,
    "isPersonalMode": false,
    "paymentsState": "enabled",
    "pledgesEnabled": false,
    "iosAppPaymentsEnabled": true,
    "hasReplyRules": true
  },
  "author": {
    "id": 165322060,
    "name": "Kristi Keller 🇨🇦",
    "handle": "kristikeller",
    "photoUrl": "https://substackcdn.com/image/fetch/$s_!TdHE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bee1713-6b2a-45fa-bbb6-e9437756af86_316x326.png",
    "bio": "Builder of highly engaged communities, helping others create fabulous and FUNCTIONAL Substack publications, all while wearing sweatpants and basking in the comfort of my living room.",
    "profileSetUpAt": "2023-08-25T22:34:50.885Z",
    "readerInstalledAt": "2023-08-26T04:00:31.390Z",
    "bestsellerTier": 100,
    "status": {
      "bestsellerTier": 100,
      "subscriberTier": 1,
      "leaderboard": null,
      "vip": false,
      "badge": {
        "type": "bestseller",
        "tier": 100
      },
      "subscriber": null
    },
    "primaryPublication": {
      "id": 2328962,
      "subdomain": "unstackit",
      "customDomainOptional": false,
      "name": "Unstack Substack",
      "logoUrl": "https://substack-post-media.s3.amazonaws.com/public/images/9ec83ce1-c3b2-4b6f-87d9-41902e60a69f_500x500.png",
      "authorId": 165322060,
      "userId": 165322060,
      "handlesEnabled": false,
      "explicit": false,
      "isPersonalMode": false,
      "paymentsState": "enabled",
      "pledgesEnabled": false,
      "iosAppPaymentsEnabled": true,
      "hasReplyRules": true
    }
  },
  "postId": null,
  "replyMinimumRole": "everyone",
  "mediaClipId": null,
  "restacked": false,
  "userStatus": {
    "bestsellerTier": 100,
    "subscriberTier": 1,
    "leaderboard": null,
    "vip": false,
    "badge": {
      "type": "bestseller",
      "tier": 100
    },
    "subscriber": null
  },
  "autotranslateTo": null,
  "isAiDetectionDisabled": false,
  "canShowFollowUpsell": false,
  "canShowCommentReveal": false,
  "source": {
    "type": "profile",
    "value": "https://substack.com/@kristikeller",
    "title": "Kristi Keller 🇨🇦",
    "url": "https://substack.com/@kristikeller"
  },
  "scrapedAt": "2026-10-11T10:20:45.763Z",
  "rank": 1878
}
```

#### Profiles

Excerpt (lists shortened to one entry):

```json
{
  "type": "profile",
  "id": 165322060,
  "handle": "kristikeller",
  "name": "Kristi Keller 🇨🇦",
  "bio": "Builder of highly engaged communities, helping others create fabulous and FUNCTIONAL Substack publications, all while wearing sweatpants and basking in the comfort of my living room.",
  "photoUrl": "https://substackcdn.com/image/fetch/$s_!TdHE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bee1713-6b2a-45fa-bbb6-e9437756af86_316x326.png",
  "url": "https://substack.com/@kristikeller",
  "joinedAt": "2023-08-25T22:34:50.885Z",
  "twitterScreenName": null,
  "twitterUrl": null,
  "links": [
    {
      "id": 3868807,
      "value": "www.unstacksubstack.com",
      "url": "https://www.unstacksubstack.com",
      "type": null,
      "label": "Website"
    }
  ],
  "publications": [
    {
      "id": 2328962,
      "handle": "unstackit",
      "name": "Unstack Substack",
      "role": "admin",
      "url": "https://unstackit.substack.com",
      "isPrimary": true,
      "logoUrl": "https://substack-post-media.s3.amazonaws.com/public/images/9ec83ce1-c3b2-4b6f-87d9-41902e60a69f_500x500.png",
      "heroText": "I help fellow Substackers make the most of their time by unstacking all the back-end chores that come along with writing on this platform.",
      "paymentsState": "enabled"
    }
  ],
  "primaryPublicationId": 2328962,
  "primaryPublicationName": "Unstack Substack",
  "primaryPublicationUrl": "https://unstackit.substack.com",
  "followerCount": 22342,
  "subscriberCount": 8900,
  "subscriberCountText": "8.9K+ subscribers",
  "freeSubscribersBand": "Thousands",
  "bestsellerTier": 100,
  "bestsellerLabel": "Bestseller: hundreds of paid subscribers",
  "contactInfo": {
    "emails": [],
    "phones": [],
    "website": null,
    "socials": {},
    "paymentLinks": {},
    "aggregator": null
  },
  "hasPosts": true,
  "hasActivity": true,
  "visibleSubscriptionsCount": 58,
  "subscriptions": [
    {
      "userId": 165322060,
      "id": 338851378,
      "visibility": "public",
      "membershipState": "subscribed",
      "type": "comp",
      "isFounding": false,
      "publication": {
        "id": 1143960,
        "name": "Annie Scott's Wankery Watch",
        "subdomain": "anniescott"
      }
    }
  ],
  "source": {
    "type": "profile",
    "value": "https://substack.com/@kristikeller",
    "title": "Kristi Keller 🇨🇦",
    "url": "https://substack.com/@kristikeller"
  },
  "scrapedAt": "2026-10-11T10:20:16.338Z"
}
```

#### Publications

Subscriber count and band, paid subscriber band, plans with prices in every currency, founder and contributors, recommendations, contact links found in public bios, and analytics over the posts this run collected: averages, totals, top post, posts per week, share of paid posts, engagement rate (average reactions per subscriber) and virality (top post's reactions over the average). With `trackHistory` on, `history` adds the subscriber change since the last tracked run.

Excerpt (lists shortened; every other publication setting Substack makes public is in the full row):

```json
{
  "type": "publication",
  "id": 2328962,
  "handle": "unstackit",
  "name": "Unstack Substack",
  "url": "https://unstackit.substack.com",
  "customDomain": null,
  "description": "I help fellow Substackers make the most of their time by unstacking all the back-end chores that come along with writing on this platform.",
  "language": "en",
  "logoUrl": "https://substackcdn.com/image/fetch/$s_!nr84!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ec83ce1-c3b2-4b6f-87d9-41902e60a69f_500x500.png",
  "logoUrlWide": "https://substackcdn.com/image/fetch/$s_!4MuM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5e413e0-e385-4443-bf6d-ea4b0fb57f81_1344x256.png",
  "coverPhotoUrl": "https://substack-post-media.s3.amazonaws.com/public/images/d255e1f6-65ef-4009-98d3-65734976527b_1000x500.png",
  "themeColor": "#ee7835",
  "createdAt": "2024-02-06T18:05:06.583Z",
  "firstPostAt": "2024-02-06T18:05:06.884Z",
  "subscriberCount": 6000,
  "subscriberCountText": "6,000",
  "subscriberCountApprox": "6.5K+",
  "subscriberCountVisibility": "public",
  "subscribersSummary": "Thousands of subscribers",
  "paidSubscribersText": "Hundreds of paid subscribers",
  "paidSubscribersMagnitude": 100,
  "paymentsEnabled": true,
  "paymentsState": "enabled",
  "paymentsStateLabel": "Paid subscriptions offered",
  "plans": [
    {
      "id": "monthly9usd",
      "name": "$9 a month",
      "price": 9.0,
      "currency": "USD",
      "interval": "month",
      "intervalCount": 1,
      "active": true,
      "trialDays": null,
      "pricesByCurrency": {
        "AUD": 13.0,
        "BRL": 47.0,
        "CAD": 13.0,
        "CHF": 8.0
      }
    },
    {
      "id": "yearly75usd",
      "name": "$75 a year",
      "price": 75.0,
      "currency": "USD",
      "interval": "year",
      "intervalCount": 1,
      "active": true,
      "trialDays": null,
      "pricesByCurrency": {
        "AUD": 110.0,
        "BRL": 390.0,
        "CAD": 110.0,
        "CHF": 65.0
      }
    }
  ],
  "foundingPlanName": "Level-Up Your Stack!",
  "founderName": "Kristi Keller 🇨🇦",
  "primaryAuthor": {
    "id": 165322060,
    "handle": "kristikeller",
    "name": "Kristi Keller 🇨🇦",
    "bio": "Builder of highly engaged communities, helping others create fabulous and FUNCTIONAL Substack publications, all while wearing sweatpants and basking in the comfort of my living room.",
    "photoUrl": "https://substackcdn.com/image/fetch/$s_!TdHE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0bee1713-6b2a-45fa-bbb6-e9437756af86_316x326.png",
    "twitterUrl": null
  },
  "contributors": [
    {
      "name": "Kristi Keller 🇨🇦",
      "handle": "kristikeller",
      "role": "admin",
      "owner": true,
      "userId": 165322060
    }
  ],
  "bestsellerTier": 100,
  "bestsellerLabel": "Bestseller: hundreds of paid subscribers",
  "supportEmail": null,
  "emailFromName": "Unstack Substack",
  "copyright": "Kristi Keller",
  "communityEnabled": true,
  "inviteOnly": false,
  "explicit": false,
  "homepageType": "magaziney",
  "isPersonalMode": false,
  "podcastEnabled": false,
  "podcastTitle": null,
  "podcastFeedUrl": null,
  "recommendationCount": 18,
  "recommendations": [
    {
      "id": 8000589,
      "name": "Room to be Human",
      "subdomain": "roomtobehuman",
      "url": "https://roomtobehuman.substack.com",
      "logoUrl": "https://substack-post-media.s3.amazonaws.com/public/images/a624b357-6b96-4b0d-95c9-1379fc74688a_256x256.png",
      "heroText": "A quiet place to share life’s challenges and difficult emotions. Inspiration, tools, and conversations that honour the beautifully imperfect experience of being human.",
      "blurb": "Sabine is a lovely human to work with and I would love to help give her Substack a boost by recommending her 😊",
      "recommendedAt": "2026-04-11T16:21:00.692Z"
    },
    {
      "id": 7200768,
      "name": "HomeBody(ish) Magazine",
      "subdomain": "homebodyish",
      "url": "https://homebodyish.substack.com",
      "logoUrl": "https://substack-post-media.s3.amazonaws.com/public/images/208233a0-120c-498b-a7ee-224c309220c6_600x600.png",
      "heroText": "Definitely NOT another slow living crusade. This is the anti-hustle, pro-sweatpants magazine for people with mild social skills and loads of creativity & ambition.  Step inside and shine!",
      "blurb": "This is my other publication and I'd love more subscribers~!",
      "recommendedAt": "2025-12-31T21:49:14.793Z"
    }
  ],
  "postsRequested": null,
  "postsCollected": 147,
  "archiveComplete": true,
  "postsWithoutBody": 0,
  "topPosts": [
    {
      "id": 179836383,
      "slug": "no-thanks-i-dont-want-to-read-your",
      "url": "https://unstackit.substack.com/p/no-thanks-i-dont-want-to-read-your",
      "title": "No Thanks. I Don't Want To Read Your Substack",
      "publishedAt": "2025-11-29T13:03:01.879Z",
      "reactionCount": 905,
      "commentCount": 214,
      "restackCount": 94
    },
    {
      "id": 143401415,
      "slug": "480-substack-subscribers-in-one-month",
      "url": "https://unstackit.substack.com/p/480-substack-subscribers-in-one-month",
      "title": "The Simplest Thing That Brought Me 480 Subscribers In One Month",
      "publishedAt": "2024-04-13T12:49:48.284Z",
      "reactionCount": 715,
      "commentCount": 204,
      "restackCount": 72
    }
  ],
  "analytics": {
    "sampleSize": 147,
    "sampleDateRange": {
      "from": "2024-02-06T18:05:06.884Z",
      "to": "2026-10-10T12:03:24.184Z"
    },
    "avgReactionsPerPost": 100.1,
    "avgCommentsPerPost": 44.0,
    "avgRestacksPerPost": 11.0,
    "avgWordCount": 434,
    "totalReactions": 14722,
    "totalComments": 6464,
    "totalRestacks": 1619,
    "topPostReactions": 905,
    "topPost": {
      "id": 179836383,
      "slug": "no-thanks-i-dont-want-to-read-your",
      "url": "https://unstackit.substack.com/p/no-thanks-i-dont-want-to-read-your",
      "title": "No Thanks. I Don't Want To Read Your Substack",
      "reactionCount": 905
    },
    "publishCadencePerWeek": 1.05,
    "paidPostRatio": 0.34,
    "engagementRate": 0.01669,
    "viralityIndex": 9.04
  },
  "contactInfo": {
    "emails": [],
    "phones": [],
    "website": null,
    "socials": {},
    "paymentLinks": {},
    "aggregator": null
  },
  "history": null,
  "source": {
    "type": "publication",
    "value": "https://unstackit.substack.com",
    "title": "Unstack Substack",
    "url": "https://unstackit.substack.com"
  },
  "scrapedAt": "2026-10-11T10:18:21.549Z"
}
```

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

You pay per result saved to your dataset:

| Event | Free plan | Bronze | Silver | Gold and above |
|---|---|---|---|---|
| Result: one post, Note, profile or publication | $1.99 / 1,000 | $1.79 / 1,000 | $1.69 / 1,000 | $1.39 / 1,000 |
| Comment: one comment or reply (only with comments on) | $0.59 / 1,000 | $0.55 / 1,000 | $0.49 / 1,000 | $0.39 / 1,000 |
| Actor start, once per run (per GB of run memory) | $0.005 | $0.005 | $0.005 | $0.005 |

Full text, authors, publication details and analytics are included in the result price. Examples on the Free plan:

- The quick test above, 20 posts with up to 10 comments each: 21 results, at most 200 comments: at most $0.17
- The whole archive of Lenny's Newsletter, 1,270 posts and the publication row: $2.53
- 1,000 posts from a keyword search: $2.00

Set a maximum cost per run in the run options: the run stops at that amount, keeps everything it saved and says so in the log. A run that finds nothing pays only the start. The [Pricing tab](https://apify.com/clearpath/substack-scraper/pricing) shows every charge. Free Apify plan users can run this actor; there are no extra limits beyond your plan's usage.

### FAQ

**Can I download or export all posts of a Substack newsletter?**
Yes. Enter the publication and leave `maxItemsPerSource` empty. Substack's archive list stops near 480 posts; this actor still returns every post, the oldest included, plus the standalone pages the archive hides.

**Can I export Substack posts to Markdown for AI or RAG?**
Yes. Every free post comes as `bodyMarkdown` with headings, links, lists, quotes, images and code kept, plus `bodyText` and `bodyHtml`. Subscribe boxes and share buttons are removed.

**Can I see a Substack's subscriber count and paid subscribers?**
When the publication shows it, `subscriberCount` (for example 6000) and `subscriberCountText` come on its publication row and on its posts. Substack shows paid subscribers only as a band, such as "Hundreds of paid subscribers", in `paidSubscribersText`. A publication can hide its count; its rows then say `subscriberCountVisibility: "hidden"`.

**Does it work with custom domains?**
Yes. Paste the publication's own domain, such as `https://www.lennysnewsletter.com`, or any post link on it.

**What about paid posts?**
Paid posts come with all their details (title, word count, reactions, comments count, authors) and the public preview, marked `bodyAvailable: false`. The actor does not log in and does not get past the paywall.

**Can I scrape Substack Notes and comments?**
Yes. Add a profile for its Notes feed, a Note link, or search Notes by keyword with `"searchType": "notes"`. Turn on `includeComments` for every comment and reply on each post and under each Note.

**Can I track a newsletter's growth?**
Turn on `trackHistory` and schedule the actor. Each run stores the subscriber count in your account and the publication row's `history` shows the change, growth per day and posts added since the last tracked run.

**Can I use it from Python, n8n, Make or an AI agent?**
Yes. Run it from Python or JavaScript with Apify's client libraries, connect it to Make, Zapier or n8n, or let an AI assistant run it through Apify's MCP server. The block above describes the inputs for agents.

**Why did my run return fewer results than I expected?**
Check the log. It names any publication, profile, post or link Substack did not recognize, links that are not Substack links, sources that could not be completed this time, and when the run stopped at your maximum cost. Date windows, post types, the free or paid filter and limits also narrow the results, and a result found by two inputs is saved once.

**How fresh is the data?**
Every run collects what Substack shows at that moment, including reactions and comments. Schedule the actor to keep a dataset current.

**Is it legal to scrape Substack?**
The actor collects what Substack shows to any logged-out visitor. How you use it is your responsibility: respect writers' copyright, Substack's terms and data protection law for personal details such as profiles and comments.

### Support

Questions, a missing field or a run that did not return what you expected: open an issue in the **Issues** tab or email **max@mapa.slmail.me**. Reports with the run ID get answered fastest. Substack is a trademark of Substack Inc. This actor is independent and is not affiliated with or endorsed by Substack.

# Actor input Schema

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

Any Substack link: a publication (`https://name.substack.com` or its own domain such as `https://www.lennysnewsletter.com`), a section (`/s/...`) or tag page (`/t/...`), a post (`/p/...`), a profile (`https://substack.com/@handle`), a Note (`https://substack.com/@handle/note/c-123`) or a search page (`https://substack.com/search/...`). Up to 1,000 links, publications, profiles and keywords per run, all fields together.

## `publications` (type: `array`):

Publication handles (`unstackit`) or domains (`www.lennysnewsletter.com`). Collects the whole archive, newest first, including the standalone pages Substack's archive list leaves out.

## `profiles` (type: `array`):

Substack handles (`kristikeller` or `@kristikeller`). Returns the profile and, with Notes on, their Notes feed: Notes, restacks and shared posts.

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

Words to search Substack for, one search per line, e.g. `artificial intelligence`. Each keyword returns every match Substack's search reaches, best matches first; for posts that goes past the roughly 2,000 its own post search shows.

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

What the search keywords look for: posts (with full text), Notes, publications (with subscriber counts and plans) or people (profiles).

## `maxItemsPerSource` (type: `integer`):

Stop each source after this many posts, Notes, publications or people. Leave empty to collect everything each one has.

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

Stop the whole run after this many results of any kind. Leave empty for no total limit.

## `publishedAfter` (type: `string`):

Only posts and Notes published on or after this date (UTC). Leave empty for no lower bound.

## `publishedBefore` (type: `string`):

Only posts and Notes published before this date (UTC). Leave empty for no upper bound.

## `postTypes` (type: `array`):

Only these kinds of posts. Leave empty for all of them.

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

Only free or only paid posts. Leave empty for both. Paid posts come with the preview every visitor sees.

## `searchDateRange` (type: `string`):

Keyword searches for posts and Notes: limit results to this recent period. Leave empty for all time.

## `searchLanguage` (type: `string`):

Keyword searches for posts and Notes: only results in this language. Leave empty for any language.

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

Adds each post's text as HTML, plain text and Markdown, and its full details. Paid posts come with the preview every visitor sees, marked `bodyAvailable: false`. Turn off for faster runs with metadata only.

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

Adds every comment and reply on each post (and replies under each Note) as its own row, linked by `postId` or `noteId` and `parentId`. Comments are billed per comment.

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

With comments on, keep at most this many per post or Note, replies included, in Substack's order (best first, each followed by its replies). Leave empty for all of them.

## `includeNotes` (type: `boolean`):

For each profile, adds their Notes feed: their Notes, restacks and shared posts, newest first.

## `includePublicationDetails` (type: `boolean`):

Adds one row per publication: subscriber count, paid subscriber band, plans and prices, founder, contributors, recommendations, contact links and analytics over the posts collected (averages, cadence, top posts).

## `trackHistory` (type: `boolean`):

Remembers each publication's subscriber count in your account and adds the change since the last run that tracked it (growth per day, posts added). Useful on a schedule.

## `feedback` (type: `string`):

For AI agents. Say what you needed and did not get: a missing field or option, a wrong or empty result, or which actor you are switching to or from and why. Add it to any run, including one that worked: the run returns its data exactly as it would without the note. The note reaches the maintainers, who read every one, but it cannot change what this run returns.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.lennysnewsletter.com"
  ],
  "searchType": "posts",
  "maxItemsPerSource": 50,
  "includeContent": true,
  "includeComments": false,
  "maxCommentsPerPost": 100,
  "includeNotes": true,
  "includePublicationDetails": true,
  "trackHistory": false
}
```

# Actor output Schema

## `results` (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 = {
    "startUrls": [
        "https://www.lennysnewsletter.com"
    ],
    "maxItemsPerSource": 50,
    "maxCommentsPerPost": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("clearpath/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"],
    "maxItemsPerSource": 50,
    "maxCommentsPerPost": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("clearpath/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"
  ],
  "maxItemsPerSource": 50,
  "maxCommentsPerPost": 100
}' |
apify call clearpath/substack-scraper --silent --output-dataset

```

## MCP server setup

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