Substack Posts Scraper: Whole Archive & Comments
Pricing
from $0.70 / 1,000 posts
Substack Posts Scraper: Whole Archive & Comments
Every post of a Substack newsletter, back to its first, with its subscriber count and paid plans. $1 per 1,000 posts. Full text and whole comment threads on request.
Pricing
from $0.70 / 1,000 posts
Rating
0.0
(0)
Developer
Luzi
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
7 days ago
Last modified
Categories
Security
Limited permissionsShare
Get every post of a Substack newsletter, newest first, all the way back to its first post: title, subtitle, dates, audience (free or paid), word count, likes, reactions, comment and restack counts, authors, tags, cover image, podcast and audio details, and the publication's own details: its subscriber count as Substack shows it, its paid plans and prices, and its sections and contributors. 124 fields a post. Turn on post text for each post's full body, and comments for its whole comment thread, replies included. Works with publications on their own domain. No Substack account needed.
Tested daily on a sample of two newsletters. Latest full read: 4,142 posts from three whole newsletters in about three minutes, every post each one lists, its sections and podcast included.
What does this Actor do?
You give it Substack newsletters: a publication's link, a writer's profile, or a post's link. For each publication it reads the archive from the newest post back, and saves one row per post until it reaches your limit, your date, or the publication's first post.
Each post comes with:
- its link, id, title, subtitle, description, type (newsletter, podcast, thread or video) and when it was published
- who can read it (everyone or paying subscribers) and how many words it has
- its likes, each reaction's count, how many comments and replies it has, and how many times it was restacked
- every author: name, handle, photo, bio, X (Twitter) name, bestseller tier, and the publications they write for
- its tags, its section, its cover image and the image's colours
- for a podcast: the audio link, length, episode and season numbers
- the publication's name, link, own domain, logo, tagline, owner, when it started and its first post
- the publication's subscriber count and paid-subscriber range, as Substack shows them, its paid plans with their prices, what each plan offers, its podcast, sections and contributors
With Include post text on, each post also gets:
- its full body, as HTML and as plain text (a paid post gives the free preview only)
- when it was last updated, and the posts before and after it
With Include comments on, each post also gets its whole comment thread, replies included, in reading order: each comment's text, date, likes, the commenter's name, handle and photo, whether they are the author, and the comment it answers.
Why use it?
- The whole archive. It keeps going to a publication's very first post, or stops at your limit or your date. Posts filed in sections the publication keeps off its main archive page (a podcast, a weekly digest) are read too.
- Subscribers and prices. Every row carries the publication's subscriber count and paid plans, so one export compares newsletters.
- Every comment, not a sample. With comments on, a post's whole thread comes back, replies at every depth, in the post's own row.
- One price. The same price per post with or without the text and the comments; no start fee.
- Any Substack. Publications on substack.com and on their own domain, a writer's profile, or single posts.
- Use the data anywhere. Download as JSON, CSV or Excel, or send it to your own tools through Apify's API and integrations.
How to use it
- Paste one or more newsletters into Substack links:
https://example.substack.com, a publication's own domain such ashttps://www.lennysnewsletter.com, a writer's profile (https://substack.com/@handle), or a post's link (…/p/post-name) for that one post. - Optionally, set Only posts newer than, Max posts per publication, Include post text and Include comments.
- Click Start. When the run ends, open the Output tab to view the posts or download them.
Run it with no input to see a sample: up to 50 posts each from two newsletters, charged as usual.
Input
| Field | What it does | Default |
|---|---|---|
Substack links (startUrls) | Publications, writers' profiles or posts to read. Required. | — |
Only posts newer than (onlyPostsNewerThan) | A date (YYYY-MM-DD, UTC) or a period (7 days, 2 weeks, 3 months, 1 year). Empty = the whole archive | empty |
Max posts per publication (maxPostsPerPublication) | Stop reading a publication after this many posts, from 1 to 50,000 | 100 |
Include post text (includeBody) | Also read each post's full text | off |
Include comments (includeComments) | Also read each post's whole comment thread | off |
Only new posts (onlyNewItems) | Return only the posts an earlier run did not; see New items only below | off |
Example input:
{"startUrls": ["https://www.lennysnewsletter.com", "https://noahpinion.substack.com"],"onlyPostsNewerThan": "6 months","maxPostsPerPublication": 1000,"includeBody": true}
New items only
Turn on Only new posts (onlyNewItems) for a scheduled run, or a re-run
of the same task. Each run then returns only the posts it has not returned
before: it reads each publication back to a day before the last run that read
it, and skips the posts an earlier run saved. Skipped posts are neither saved
nor charged; each new post is charged as usual.
- Runs with other settings, or from another task, keep their own list, so two schedules on one publication never skip each other's posts.
- If a publication stops at Max posts per publication, the posts past that point are not returned later. Set the maximum above what one publication gets between runs.
- A post's comments are read when the post is new; comments added after that are not read again.
- The list lives in your Apify account, in a key-value store named
substack-posts-scraper-monitor. Delete it to start afresh.
Output
One row per post. An example, shortened, with made-up values:
{"post_id": 900000001,"url": "https://example.substack.com/p/made-up-post","slug": "made-up-post","title": "A made-up post","subtitle": "What a made-up subtitle says","type": "newsletter","audience": "everyone","is_paid": false,"published_at": "2026-10-20T12:00:00.000Z","word_count": 1850,"author_name": "Made-up Writer","author_handle": "madeupwriter","reaction_count": 245,"reactions": { "❤": 245 },"comment_count": 31,"restack_count": 12,"tags": [{ "id": "a1b2", "name": "Essays", "slug": "essays" }],"body_text": "The post's text, as plain text.","publication_name": "Made-up Newsletter","publication_url": "https://example.substack.com","body_read": true,"comments_read": true,"comments": [{"comment_id": 5001,"parent_id": null,"depth": 0,"body": "A made-up comment.","posted_at": "2026-10-20T14:00:00.000Z","name": "Made-up Reader","handle": "madeupreader","is_author": false,"reaction_count": 4}],"source_url": "https://example.substack.com"}
Every field:
| Field | What it holds |
|---|---|
post_id | Substack's id for the post |
url | The post on its publication |
slug | The post's name in its link |
title | The title |
subtitle | The subtitle |
description | The post's short description, as shared |
social_title | The title shown when the post is shared, when the writer set one |
search_engine_title | The title for search engines, when the writer set one |
search_engine_description | The description for search engines, when the writer set one |
type | newsletter, podcast, thread or video |
audience | Who can read it: everyone, only_paid, founding or only_free |
is_paid | true for a post only paying subscribers can read in full |
published_at | When it was published |
updated_at | When it was last updated (body on, or a post link) |
language | The post's language code |
word_count | How many words it has, paid part included |
author_id | The first author's Substack id |
author_name | The first author's name |
author_handle | The first author's handle (substack.com/@handle) |
authors | Every author: id, name, handle, photo, bio, X (Twitter) name, guest or not, bestseller tier, subscriber tier, badge, when the profile was set up, their own publication, and the publications they write for with their role |
reaction_count | How many likes (hearts) it has |
reactions | Each reaction with its count |
comment_count | How many comments it has, replies included |
child_comment_count | How many of those are replies |
restack_count | How many times it was restacked |
tags | Its tags: id, name and slug |
section_id | The section it is filed in, when the publication has sections |
section_name | That section's name |
section_slug | That section's name in its link |
is_section_pinned | true when it is pinned in its section |
cover_image_url | The cover image's link |
cover_image_is_square | true when the cover image is square |
cover_image_is_explicit | true when the cover image is marked explicit |
cover_image_palette | The cover image's main colours, as RGB values |
truncated_body_text | The post's opening lines, when the publication shows them in its archive |
body_html | The post's body as HTML (body on, or a post link). For a paid post, the free preview |
body_text | The same body as plain text |
has_dynamic_content | true when the body has parts that change for each reader |
post_preview_limit | How much of a paid post is shown free |
should_send_free_preview | true when free subscribers got a preview by email |
free_unlock_required | true when a free subscriber must unlock it |
teaser_post_eligible | Substack's flag for posts that may be shown as a teaser |
meter_type | How the paywall counts reads, when the post has one (body on, or a post link) |
exempt_from_archive_paywall | true when it stays free after the publication's archive goes paid (body on, or a post link) |
write_comment_permissions | Who may comment: everyone, paid or none |
default_comment_sort | The comment order the writer chose, when they chose one |
podcast_url | The episode's audio link, for a podcast post |
podcast_duration | The episode's length in seconds |
podcast_art_url | The episode's own artwork (body on, or a post link) |
podcast_episode_number | The episode number |
podcast_season_number | The season number |
podcast_episode_type | full, trailer or bonus |
free_podcast_url | The free preview's audio link, for a paid episode |
free_podcast_duration | The free preview's length in seconds |
podcast_upload_id | Substack's id for the episode's audio |
podcast_preview_upload_id | Substack's id for the episode's free preview |
video_upload_id | Substack's id for the post's video |
voiceover_upload_id | Substack's id for the writer's own reading of the post |
has_voiceover | true when the writer recorded a reading of the post |
audio_items | The post's audio versions (Substack's read-aloud and others): type, voice, status and link |
restacked_post_id | For a restack: the original post's id |
restacked_post_slug | For a restack: the original post's slug |
restacked_publication_name | For a restack: the original post's publication |
restacked_publication_logo_url | That publication's logo |
previous_post_slug | The slug of the publication's post before it (body on, or a post link) |
next_post_slug | The slug of the post after it (body on, or a post link) |
is_published | true for a published post (body on, or a post link) |
is_hidden | true when the writer hid it from the publication's home page |
is_geoblocked | true when it is blocked in some countries |
country_blocks | The countries it is blocked in |
has_cashtag | true when it mentions a stock by its $ symbol |
has_shareable_clips | true when it has video or audio clips readers can share (body on, or a post link) |
live_stream_id | The live stream it came from, when it did (body on, or a post link) |
publication_id | Substack's id for the publication |
publication_name | The publication's name |
publication_url | The publication's home page, on its own domain when it has one |
publication_subdomain | The publication's name in its substack.com address |
publication_custom_domain | The publication's own domain, when it has one |
publication_logo_url | The publication's logo |
publication_hero_text | The publication's tagline |
publication_type | newsletter or podcast |
publication_created_at | When the publication was created |
publication_first_post_date | When the publication's first post was published |
publication_language | The publication's language code |
publication_subscribers | The publication's subscriber count as Substack shows it ("Over 1,200,000 subscribers"), when the writer shows it |
publication_subscribers_rounded | The same count, rounded as Substack shows it ("1.2M+") |
publication_subscribers_range | Substack's range for all its subscribers, free and paid ("Millions of subscribers") |
publication_subscribers_range_min | That range's lower end as a number (1000000) |
publication_paid_subscribers_range | Substack's range for its paid subscribers ("Tens of thousands of paid subscribers"), when it shows one |
publication_paid_subscribers_range_min | That range's lower end as a number (10000) |
publication_bestseller_tier | The owner's Substack bestseller tier (100, 1000, 10000...), when they have one |
publication_author_id | The Substack id of the publication's owner |
publication_owner_name | The owner's name |
publication_owner_handle | The owner's handle |
publication_owner_bio | The owner's bio |
publication_owner_photo_url | The owner's photo |
publication_twitter_screen_name | The publication's X (Twitter) name, when it has one |
publication_cover_photo_url | The publication's cover photo, when it has one |
publication_payments_state | Whether the publication takes paid subscriptions (enabled, disabled) |
publication_plans | Its paid plans: name, month or year, the price (20 for $20) and currency, and whether it is on sale |
publication_founding_plan_name | The name of its top paid plan |
publication_free_benefits | What a free subscriber gets, as the writer lists it |
publication_paid_benefits | What a paid subscriber gets |
publication_founding_benefits | What a founding member gets |
publication_paywall_free_trial_enabled | true when the publication offers a free trial |
publication_has_podcast | true when the publication has a podcast |
publication_podcast_title | The podcast's title |
publication_podcast_description | The podcast's description |
publication_podcast_byline | The podcast's byline |
publication_podcast_feed_url | The podcast's own feed link, when it has one |
publication_podcast_art_url | The podcast's artwork |
publication_sections | The publication's sections: id, name, slug, and whether it is a podcast |
publication_contributors | The people who write for it: id, name, handle, role, whether they own it, bio and photo |
publication_has_recommendations | true when the publication recommends other publications |
publication_email_from_name | The sender name on the publication's emails |
publication_copyright | The publication's copyright line |
publication_homepage_type | The publication's home page layout |
publication_community_enabled | true when the publication has a community (chat) for readers |
publication_invite_only | true when the publication is invite only |
publication_explicit | true when the publication is marked explicit |
body_read | With body on: true when the post's body was read. Empty with body off |
comments_read | With comments on: true, the post's comments were read (a post whose thread cannot be read is not saved). Empty with comments off |
comments | With comments on: the post's whole comment thread, replies included, in reading order. Each comment has its id, the comment it answers (parent_id), its depth, text, dates, the commenter's id, name, handle and photo, whether they are the author, their membership, likes, restacks, reply count, score, status and whether it is pinned or deleted |
source_url | The link you gave that this post was read from |
Not included: the paid part of a paid post (the text gives the free preview), comments on a paid post (Substack shows them to subscribers only), Substack Notes, exact paid-subscriber numbers (Substack shows a range), and search across all of Substack.
How much does it cost?
No surcharges and no paid options; an item that fails is not charged.
You pay for what you get:
- $1 per 1,000 posts ($0.001 a post), with or without the text and the comments. Each post is charged once per run.
- No start fee, and nothing per publication.
So a publication's 400 posts cost $0.40. If you set a maximum cost for a run, the Actor stops when it reaches it and keeps every post saved so far.
How long does a run take?
About 100 posts in 10 to 20 seconds, and three whole newsletters, 4,142 posts, in about three minutes. With the text and comments on, each post takes longer: 200 posts in about four minutes.
Good to know
- Check your input first. A link that is not a publication, profile or post link stops the run before anything is read, and the message names it.
- One link that cannot be read does not stop the run. A domain that is not a Substack, a post that was taken down or a writer with no publication ends with a message, the others carry on, and the run's status lists them. The run fails only when no link could be read.
- Paid posts. Their row has every field but the paid part of the text;
is_paidmarks them. - Large threads. A post with hundreds of comments makes a large row; keep comments off when you only need the counts.
- Counts move. Likes, comments and restacks are the numbers at the moment the post was read.
- Remove repeats by
post_id. A daily run reads recent posts again, unless Only new posts is on.
More Actors from us
- Airbnb Scraper: Every Stay, Past the 15-Page Limit: Every stay of an Airbnb search, past the 15 pages Airbnb shows, checked against its count.
- Alibaba Products Scraper: Price Tiers & Suppliers: Alibaba.com products by keyword or link, with price tiers in US dollars on every row.
- AliExpress Product Scraper: Search Results & Prices: Every product of an AliExpress search to its last page, with exact sold counts and ratings.
- Amazon Product Scraper: Search Results & Prices: Every product on Amazon's search pages to the last page, sponsored marked, with monthly sales.
- App Store Reviews Scraper: All Reviews, Not 500: Every written App Store review of an app, not just the newest 500.
- Woolworths Coles ALDI Scraper: AU Grocery Prices: Woolworths, Coles and ALDI products for any search in one run, every results page to the end.
Use it from an AI agent or the API
One call starts a run and returns its items (Apify waits up to 300 seconds; for a
longer run, start it with POST /v2/acts/…/runs and read its dataset):
POST https://api.apify.com/v2/acts/luzivanlt~substack-posts-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>
With the sample input as its JSON body:
{"startUrls": ["https://www.lennysnewsletter.com", "https://noahpinion.substack.com"],"maxPostsPerPublication": 50,"includeBody": false,"includeComments": false}
Its inputs, in the order of the input form:
| Input | What it takes | Default |
|---|---|---|
startUrls (Substack links) | Substack newsletters to read: a publication's link (https://example.substack.com, or its own domain such as https://www.example.com), any page of it, a writer's profile (https://substack.com/@handle or @handle) for their own publication, or a post's link (…/p/post-name) for that one post. | — |
onlyPostsNewerThan (Only posts newer than) | Keep only posts published from this date on: a date (YYYY-MM-DD, UTC) or a period, e.g. "7 days", "2 weeks", "3 months" or "1 year". | — |
maxPostsPerPublication (Max posts per publication) | Stop reading a publication after this many posts, newest first, from 1 to 50,000. | 100 |
includeBody (Include post text) | Also read each post's full text, as HTML and as plain text, with its last update date and the posts before and after it. | false |
includeComments (Include comments) | Also read each post's whole comment thread, replies included, into the post's row: each comment's text, date, likes, the commenter's name and handle, and the comment it answers. | false |
onlyNewItems (Only new posts) | Turn this on for a scheduled run, or a re-run of the same task: each run then returns only the posts it has not returned before, reading each publication back to a day before the last run that read it. | — |
Apify's MCP server, https://mcp.apify.com, lists it as luzivanlt/substack-posts-scraper. To give an
AI agent this Actor as a tool, add the server to the agent's MCP config:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com?tools=luzivanlt/substack-posts-scraper"}}}
The client signs in with OAuth, or add "headers": { "Authorization": "Bearer <APIFY_TOKEN>" }.
Charged per post delivered; the sample input above costs at most $0.10.
FAQ
Do I need a Substack account? No. The Actor reads what anyone can see on Substack without signing in.
Does it work with a newsletter on its own domain?
Yes. Paste the domain, such as https://www.lennysnewsletter.com, or the
newsletter's substack.com address.
Can I run it from my own code, on a schedule, or with other apps? Yes. Like any Apify Actor, you can start it through the Apify API, run it on a schedule, and connect it to other apps with Apify's integrations and webhooks.
Is it legal to scrape Substack?
This Actor reads only public posts and comments. Authors' and commenters' names, handles and photos are personal data; treat them as such. Check Substack's terms and the laws that apply to you (such as the GDPR or the CCPA) before you use the data. If you are unsure, ask a lawyer.
Support
Found a problem or missing a feature? Open an issue on the Issues tab and include the run's link.
This Actor is not affiliated with, endorsed by or sponsored by Substack.