Substack Newsletter Posts, Dates and Engagement avatar

Substack Newsletter Posts, Dates and Engagement

Pricing

$2.00 / 1,000 result rows

Go to Apify Store
Substack Newsletter Posts, Dates and Engagement

Substack Newsletter Posts, Dates and Engagement

Substack scraper, Substack posts scraper, newsletter monitoring: give a list of Substack publications and get one row per public post with title, date, free-or-paid flag, reactions, comments and word count.

Pricing

$2.00 / 1,000 result rows

Rating

0.0

(0)

Developer

US Tech Automations

US Tech Automations

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

Substack scraper, Substack posts scraper, newsletter monitoring: list the Substack publications you track and get one row per public post -- title, date, free-or-paid status, reactions, comments, and word count -- without opening each newsletter by hand.

Introduction

Give this Actor one or more Substack addresses. For each one it reads the publication's own public post-archive list (the same JSON the publication's /archive page loads), and returns one row per post: what it's called, when it went out, whether it's free or paid, how many reaction hearts and comments it has, its word count, and its byline. Nothing is read from behind a login. A publication this run cannot read -- because it does not exist, or blocks this run -- gets exactly one row saying so, with a real reason, never a silent gap.

Tutorial

  1. Set Publications to one or more Substack addresses: name.substack.com, a bare name (short for name.substack.com), or a custom domain such as lennysnewsletter.com.
  2. Optionally set Since date to only include posts on or after that day.
  3. Optionally set Maximum posts per publication and Maximum rows -- both control cost; every row bills, so these are your spend caps.
  4. Optionally turn on Include free post body text to also copy the plain text of each free post. Paid and founding-only posts always get body_text: null -- see "What this does not do."
  5. Optionally paste Previously seen post IDs from a prior run's post_id field to get is_new: true only on posts you have not seen.
  6. Click Start. Results land in the dataset.

Pricing

$0.002 per row. No start fee.

A run that reads 500 qualifying post rows costs $1.00. A publication that does not exist, or that this run could not read, still produces one row (so you can see what happened) and that one row still bills at $0.002.

Maximum rows is the spend cap: the run cannot cost more than that number multiplied by $0.002, whatever the publications hold.

Input and output examples

Input:

{"publications": ["importai", "lennysnewsletter.com"], "maxPostsPerPublication": 5, "maxItems": 10, "includeBodyText": true}

Output (one row, from a live run on 2026-09-21):

{
"publication": "importai",
"publication_host": "importai.substack.com",
"post_id": 216673688,
"title": "Import AI 473: The US's superintelligence strategy; human brains vs LLMs at Othello; and why loving your work might be the best moat.",
"subtitle": "What matters in AI for 21 Sep 2026",
"post_url": "https://importai.substack.com/p/import-ai-473-the-uss-superintelligence",
"post_date": "2026-09-17T11:30:37.435Z",
"audience": "everyone",
"is_paid": false,
"type": "newsletter",
"reaction_count": 24,
"comment_count": 5,
"wordcount": 3876,
"cover_image": "https://substackcdn.com/image/fetch/...",
"authors": ["Jack Clark"],
"body_text": "...24,931 characters of plain text...",
"is_new": true,
"source": "archive",
"fetched_at": "2026-09-21T16:40:12Z",
"error": false,
"error_type": null,
"message": null
}

A publication this run could not read (from the same run family, made-up address):

{
"publication": "this-publication-does-not-exist-zz999.substack.com",
"publication_host": "thispublicationdoesnotexistzz999.substack.com",
"post_id": null, "title": null, "subtitle": null, "post_url": null,
"post_date": null, "audience": null, "is_paid": null, "type": null,
"reaction_count": null, "comment_count": null, "wordcount": null,
"cover_image": null, "authors": null, "body_text": null, "is_new": null,
"source": null,
"fetched_at": "2026-09-21T16:40:12Z",
"error": true,
"error_type": "NOT_FOUND",
"message": "Archive unreadable (... HTTP 404); feed fallback also answered HTTP 404."
}

Every field in a row:

FieldWhat it holds
publicationThe address you gave, exactly as given
publication_hostThe resolved host this run actually read
post_idSubstack's own numeric post id (null on the RSS-fallback path -- RSS does not carry it)
title / subtitleThe post's headline and dek
post_urlThe post's canonical public URL
post_datePublish date/time, ISO 8601
audienceeveryone, only_paid, or founding, as Substack's own archive reports it
is_paidtrue unless audience is everyone
typenewsletter, podcast, or thread
reaction_countSum of all reaction-emoji counts Substack shows on the post
comment_countPublic comment count (comment text is never read)
wordcountSubstack's own word count for the post
cover_imageCover image URL, when the post has one
authorsPublic byline display name(s) only -- no handle, bio, or photo
body_textPlain text of the post, free posts only, only when Include free post body text is on; otherwise null
is_newtrue when post_id is not in Previously seen post IDs; null when unknown (RSS-fallback rows)
sourcearchive, feed_fallback, or null on an error row -- which endpoint this row came from
fetched_atWhen this run read the source, in UTC
errortrue for the one row a publication gets when it could not be read
error_type / messagePresent only when error is true: a short code and the real reason

Recommendations

Run weekly or monthly per publication list, and set Previously seen post IDs from the last run's post_id values to see only what changed.

Limitations

body_text only exists for free posts, and only from the RSS feed's recent window. Substack's archive JSON (the main source for every other field) never carries full post text -- only a short preview. Free-post text comes from the publication's public /feed, which typically holds the ~20 most recent items across all post types (including separate podcast episodes). An older free post can fall outside that window; it gets body_text: null, not a guess.

Paid text is never read, full stop. A post the archive itself marks only_paid or founding always gets body_text: null, even though Substack's own RSS feed happens to also carry a truncated preview of paid posts. This tool does not read it.

A blocked or unreadable archive falls back to the RSS feed, per the contract. On that fallback path, post_id, audience, is_paid, reaction_count, comment_count, and wordcount are all null -- RSS does not carry them, and this tool does not invent them. source is set to feed_fallback on every row from that path so it's never mistaken for a full archive row.

No reader, subscriber, or comment content. Only the public post-level counts and public byline names are read. Comment text, reader names, and email addresses are never requested.

A source outage or a blocked host returns one honest row, not guesses. If neither the archive nor the feed can be read for a publication, that publication gets one row with error: true and a real reason -- the run still bills for that one row, and produces no invented posts.

FAQ

Where does the data come from? Each publication's own public post-archive JSON (/api/v1/archive) and, as a fallback or for free-post text, its own public RSS feed (/feed). Both are read live on every run, never from a stored copy.

Does it need an API key or a login? No. Both endpoints are served to a signed-out visitor.

Why did I get fewer rows than Maximum rows? Either the publications held that many posts and no more, or the run hit Maximum rows first, or a publication returned one error row instead of its posts.

What happens if a publication is blocked or does not exist? That publication gets exactly one row with error: true and a real reason in error_type / message. It never gets invented posts.

Is there support? Yes. Raise it through the Apify Store issues tab for this actor, or email operations@ustechautomations.com. Replies within 2 business days.

Refunds and support

Refunds: if a run returns only error rows for a publication you confirmed is real and public, email operations@ustechautomations.com within 7 days and we refund that run. Support: same address, replies within 2 business days.