Substack Scraper — Posts, Archives & Author Info, $0.9/1k
Pricing
$0.90 / 1,000 post scrapeds
Substack Scraper — Posts, Archives & Author Info, $0.9/1k
Scrape Substack newsletters: post metadata, titles, dates, likes, author info from public archives. $0.0009/post, no start fee, no login.
Pricing
$0.90 / 1,000 post scrapeds
Rating
0.0
(0)
Developer
Mohammed Samaan
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
17 days ago
Last modified
Categories
Share
Substack Scraper — Posts, Archives & Author Info
Give it Substack publications. Each public post comes back as one row: title, subtitle, date, audience (free or paid), reactions, comment count, authors, cover image and word count. Built for newsletter research, competitor tracking, content calendars and creator lists.
It reads the publication's public archive JSON endpoint (/api/v1/archive) with plain HTTP. No browser, no proxy, no login, no cookies.
Price: $0.9 per 1,000 posts ($0.0009 each). No start fee. Publications that cannot be read (not found, not a Substack, blocked, timeout) return one error row and are not charged.
What you get per post
| Field | Meaning |
|---|---|
publication | Host that was read (e.g. newsletter.pragmaticengineer.com, or name.substack.com for a bare name) |
id, slug, url | Post id, URL slug, canonical post URL |
title, subtitle | As shown in the archive |
post_date | Publish time, ISO 8601 |
type | Post type reported by Substack (e.g. newsletter, podcast, thread) |
audience | everyone (free) or only_paid (paywalled) and other values Substack reports, passed through unchanged |
reactions | Reaction (like) count |
comments | Comment count |
authors | List of author names |
cover_image | Cover image URL, or null |
wordcount | Word count if Substack reports one, else null |
error | null on a post row; the reason on an error row |
What it does NOT do
- No post body text. Paywalled posts are listed (title, date, audience, counts) but their content is never fetched, and free-post bodies are not included either. This Actor returns archive metadata only.
- No subscriber counts, no subscriber lists, no emails, no comments text. Only what the public archive endpoint returns.
- No login and no private publications. If a publication is private or hides its archive, you get an error row.
- Posts are returned newest first; there is no date filter or keyword search.
- Substack may rate-limit or block requests. If that happens you get an error row for that publication (not charged). Posts already returned stay in the dataset.
Input
| Field | What it does |
|---|---|
publications | One per line. A custom domain (newsletter.pragmaticengineer.com), a Substack name (astralcodexten becomes astralcodexten.substack.com) or a URL (only the host is used). Duplicates are removed. Up to 100 per run. |
maxPosts | Max posts per publication, 1–5,000 (default 50). Newest first. |
If the same publication is given under two addresses (for example its .substack.com name and its custom domain), the second is skipped with an uncharged error row so you are not billed twice.
Example
Input:
{ "publications": ["newsletter.pragmaticengineer.com"], "maxPosts": 50 }
Output row:
{"publication": "newsletter.pragmaticengineer.com","id": 218367831,"title": "The Pulse: Firebase’s global outage & poor response","subtitle": "Also: OpenAI’s platform play that has similarities to AWS, more data on companies moving to open models, and more","slug": "the-pulse-firebases-global-outage","url": "https://newsletter.pragmaticengineer.com/p/the-pulse-firebases-global-outage","post_date": "2026-10-01T16:43:19.985Z","type": "newsletter","audience": "only_paid","reactions": 53,"comments": 3,"authors": ["Gergely Orosz"],"cover_image": "https://substackcdn.com/image/fetch/...","wordcount": 2889,"error": null}
Error row (not charged):
{ "publication": "nonexistent-pub-zzqx9.substack.com", "error": "Publication not found (HTTP 404)." }
Use it from code
curl -X POST "https://api.apify.com/v2/acts/leadharbor~substack-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"publications":["astralcodexten"],"maxPosts":20}'
Python, with the apify-client package:
from apify_client import ApifyClientclient = ApifyClient("YOUR_TOKEN")run = client.actor("leadharbor/substack-scraper").call(run_input={"publications": ["astralcodexten"], "maxPosts": 20})for row in client.dataset(run["defaultDatasetId"]).iterate_items():print(row["post_date"], row["title"], row["reactions"])
Use it from an AI agent
This Actor is pay-per-event and works with the Apify MCP server, so AI agents can call it directly with a list of publications.
FAQ
Am I charged for publications that fail? No. Only post rows are charged. Error rows are free.
Why is wordcount sometimes null?
Substack does not report it for every post; the field is passed through only when present.
Can I get the full text of a post? No. This Actor does not fetch post bodies, paywalled or free.
Are the counts live? They are what the archive endpoint reports at the moment of the run.
How fast is it? Posts are fetched 12 per request, one publication after another, so 1,000 posts is roughly 80 requests.
Related actors
Changelog
- 1.0.0 (6 Oct 2026): first release. Archive metadata only, pay-per-event $0.0009 per post.