Substack APIafterschool.substack.com ↗
Access After School newsletter posts, full text, engagement metrics, and outbound links via 2 endpoints covering Gen Z trends and youth-culture content.
What is the Substack API?
The After School Substack API provides access to the full archive of the After School newsletter across 2 endpoints, returning post metadata, body text, and engagement signals. The list_posts endpoint lets you paginate and search across every post by title or body content, while get_post returns the complete text of a single post along with reactions, restack counts, outbound links, and author details.
curl -X GET 'https://api.parse.bot/scraper/3e327c12-a3c3-446e-85e0-f70c8af4f281/list_posts?sort=top&search=gen+z' \ -H 'X-API-Key: $PARSE_API_KEY'
Typed, relational, agent-ready
A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.
- Fully typed · autocompletes
- Objects link to objects
- Typed errors & pagination
Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:
uv add parse-sdk uv run parse init uv run parse add --marketplace afterschool-substack-com-api
uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.
"""Walkthrough: After School newsletter API — browse, search, and read posts."""
from parse_apis.afterschool_substack_com_api import AfterSchool, SortOrder, PostNotFound
client = AfterSchool()
# Browse the most popular posts in the archive.
for summary in client.post_summaries.list(sort=SortOrder.TOP, limit=5):
print(summary.title, f"({summary.reaction_count} reactions)")
# Search for a topic and drill into the first matching post's full body.
hit = client.post_summaries.list(search="gen z", limit=1).first()
if hit is not None:
post = hit.details()
print(post.title, post.post_date)
print(f"preview only: {post.body_is_preview}, words: {post.wordcount}")
# Show outbound links the post references.
for link in post.links[:5]:
print(f" {link.text} -> {link.domain}")
# Navigate to the previous post in the archive if available.
if post.previous_post is not None:
prev = post.previous_post.refresh()
print("Previous:", prev.title)
# Point lookup by slug, with typed error handling.
try:
detail = client.posts.get(slug="what-gen-z-got-for-christmas-in-2025")
print(detail.title, f"comments: {detail.comment_count}")
except PostNotFound:
print("Post not found")
print("exercised: post_summaries.list / details / posts.get / refresh")
Lists newsletter posts from the After School archive, one row per post, with title, subtitle, publish date, audience (free vs paid), preview text, word count and engagement counts (reactions, comments, restacks). Supports full-text search over the archive and ordering by newest or most popular. Paginates with offset/limit: offset defaults to 0, limit defaults to 20 and is clamped to 50 per call; has_more is true and next_offset is set when a full page came back, and an empty posts array with has_more false is the valid end of the archive (or a search with no matches). Each call is a single upstream request. tags is an array that is empty on every post observed so far.
| Param | Type | Description |
|---|---|---|
| sort | string | Result ordering: newest first, or most popular first. |
| limit | integer | Posts per page; values above 50 are clamped to 50. |
| offset | integer | Number of posts to skip in the ordered result stream; pass next_offset from the previous page to continue. |
| search | string | Full-text search term matched against post titles and bodies (e.g. a trend, brand or platform name). Empty returns the whole archive. |
{
"type": "object",
"fields": {
"count": "number of posts in this page",
"limit": "page size applied (after clamping)",
"posts": "array of post summaries: post_id (integer), slug (string, use with get_post), title, subtitle, description, post_date (ISO 8601 UTC), audience (only_paid or everyone), type, url, cover_image, preview_text, wordcount, reaction_count, comment_count, restacks, tags (array of strings), authors (array of names)",
"offset": "offset applied to this page",
"has_more": "true when a full page was returned and a further page may exist",
"next_offset": "offset for the next page, or null when this page was not full"
},
"sample": {
"data": {
"count": 1,
"limit": 1,
"posts": [
{
"url": "https://afterschool.substack.com/p/what-gen-z-got-for-christmas-in-2025",
"slug": "what-gen-z-got-for-christmas-in-2025",
"tags": [],
"type": "newsletter",
"title": "What Gen Z Got for Christmas in 2025",
"authors": [
"Casey Lewis"
],
"post_id": 182638053,
"audience": "only_paid",
"restacks": 23,
"subtitle": "we officially have a new \"it\" water bottle",
"post_date": "2025-12-31T18:01:00.357Z",
"wordcount": 2456,
"cover_image": "https://substackcdn.com/image/fetch/$s_!4Szx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F067925c0-8e6c-4bf0-9768-52d188b6e27b_1958x934.png",
"description": "we officially have a new \"it\" water bottle",
"preview_text": "For the last few years, I’ve watched thousands of Christmas hauls on TikTok, tallying up the most popular gifts of the holiday season.",
"comment_count": 12,
"reaction_count": 385
}
],
"offset": 0,
"has_more": true,
"next_offset": 1
},
"status": "success"
}
}About the Substack API
What the API Returns
The API covers the After School newsletter — a Substack publication tracking Gen Z behavior, platform trends, and youth-culture patterns. list_posts returns an array of post summaries including title, subtitle, post_date (ISO 8601), audience (free vs. paid), word_count, reactions, comments, and restacks. Each summary includes a slug and post_id you can pass directly to get_post.
Searching and Paginating the Archive
list_posts accepts a search parameter matched against post titles and body text — useful for tracking how frequently a specific platform, brand, or trend appears across the archive. Results can be ordered by sort (newest or most popular) and paged with limit (capped at 50) and offset. The response includes has_more and next_offset to drive sequential pagination without tracking state yourself.
Full Post Content and Links
get_post takes a slug from the listing response and returns the full readable body as plain text and as an ordered paragraph array, along with links — every outbound URL in the post body with its anchor text and domain. Metadata returned includes authors, audience, restacks, subtitle, and previous/next post slugs for sequential traversal of the archive. The tags field is present but observed to be empty on current posts.
The Substack API is a managed, monitored endpoint for afterschool.substack.com — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when afterschool.substack.com changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.
This isn't an official afterschool.substack.com API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.
Will this API break when the source site changes?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Track how often specific brands, apps, or platforms appear across the After School archive using the
searchparameter inlist_posts. - Monitor engagement trends over time by comparing
reactions,comments, andrestacksacross posts sorted by date. - Identify which external sources the newsletter cites most often by aggregating the
domainfield fromget_postlink arrays. - Build a timeline of Gen Z trend coverage by filtering posts by
post_dateand reviewingtitleandsubtitlefields. - Distinguish free vs. paid content coverage by segmenting posts on the
audiencefield returned by both endpoints. - Feed post body text into an NLP pipeline for topic modeling or sentiment analysis using the paragraph array from
get_post. - Map sequential reading paths through the archive using
previousandnextpost slugs returned byget_post.
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 200 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
| Team | $300/mo | 20,000 | 300 req/min |
| Company | $1,000/mo | 100,000 | 500 req/min |
Each endpoint has a fixed posted price per successful call — most fall between 1 and 10 credits — shown on this API's page before you run it. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.
Does After School (afterschool.substack.com) have an official developer API?+
What does `list_posts` return for paid posts — is the full body available?+
list_posts endpoint returns a description preview and metadata for all posts regardless of audience value. Full body text is returned by get_post. Posts marked only_paid may return limited body content depending on what the publication makes accessible.Does the API return subscriber counts or revenue data for the newsletter?+
reactions, comments, and restacks — alongside content fields like title, subtitle, word_count, and body text. You can fork this API on Parse and revise it to add an endpoint if such data becomes publicly accessible on the publication page.Are comments or individual reactions retrievable?+
comments count and an aggregate reactions count per post, but individual comment threads and per-emoji reaction breakdowns are not returned as separate records. The API covers post-level aggregates only. You can fork it on Parse and revise to add a comments endpoint if that detail is needed.How does full-text search behave when no term matches?+
search string to list_posts returns an empty posts array with count set to 0 and has_more set to false. The next_offset will be null. Search is matched against both post titles and body text.