Substack API: List newsletter posts
Finds a Substack newsletter by name or web address and returns its latest public posts with dates, authors, access level and engagement counts.
Unofficial. Not affiliated with or endorsed by Substack.
Inputs
Values from the run on Oct 23, 2026, shown under Output.
- Newsletter, required
newsletter The newsletter name or web address, for example Lenny's Newsletter or lenny.substack.com.
Lenny's Newsletter
- Max Results optional
max_results The maximum number of posts to return, for example 20.
20 (default)
Whole number from 1 to 50.
Input as JSON
{ "newsletter": "Lenny's Newsletter"}Output
{ "posts": [ { "likes": 355, "title": "How to tell if a startup has a moat ", "access": "paid", "authors": "Dan Hockenmaier", "post_id": 219482066, "subtitle": "A framework for assessing whether the company you’re considering joining is built to last", "post_link": "https://www.lennysnewsletter.com/p/how-to-tell-if-a-startup-has-a-moat" , "post_type": "article", "comment_count": 2, "published_date": "2026-10-13", "cover_image_link": "https://substackcdn.com/image/fetch/$s_!7Uce!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb5a8d5c-1e31-45f5-b9f8-93426e87a971_1456x970.png" }, { "likes": 291, "title": "All of the Lenny & Friends Summit talks are now online!", "access": "free", "authors": "Lenny Rachitsky", "post_id": 215694124, "subtitle": "Plus, some reflections and takeaways from the day", "post_link": "https://www.lennysnewsletter.com/p/all-of-the-lenny-and-friends-summit" , "post_type": "article", "comment_count": 7, "published_date": "2026-09-29", "cover_image_link": "https://substack-post-media.s3.amazonaws.com/public/images/9fed347c-0e02-4190-a590-1236719b99de_9213x6142.jpeg" }, { "likes": 369, "title": "Advanced evals: How to find (and fix) hidden AI failures in your product", "access": "paid", "authors": "Hamel Husain, Shreya Shankar", "post_id": 216168140, "subtitle": "Why you should never skip error discovery", "post_link": "https://www.lennysnewsletter.com/p/advanced-evals-how-to-find-and-fix" , "post_type": "article", "comment_count": 4, "published_date": "2026-09-22", "cover_image_link": "https://substackcdn.com/image/fetch/$s_!0_d7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f6018cf-fa53-4f40-99d9-b1c9980dd031_1456x970.png" } ], "newsletter_link": "https://www.lennysnewsletter.com" , "newsletter_name": "Lenny's Newsletter"}Code
curl https://api.fous.com/v1/runs \ -H "Authorization: Bearer $FOUS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"method":"substack.com/list_newsletter_posts","input":{"newsletter":"Lenny' \''s Newsletter"}}'# Save as fous.py, then run: python3 fous.pyimport json, os, timefrom urllib.error import HTTPErrorfrom urllib.request import Request, urlopenAPI = "https://api.fous.com/v1/runs" KEY = os.environ["FOUS_API_KEY"]HEADERS = { "Authorization": f"Bearer {KEY}", "Content-Type": "application/json" ,}def call(url, body=None): data = json.dumps(body).encode() if body else None request = Request(url, data, HEADERS) try: with urlopen(request, timeout=120) as r: return r.status, json.load(r) except HTTPError as error: message = json.load(error)["error"]["message"] raise RuntimeError(message) from errorstatus, run = call(API, { "method": "substack.com/list_newsletter_posts" , "input": { "newsletter": "Lenny's Newsletter" }})# 202: still going after 30 s. Ask again until it ends.while status == 202: time.sleep(2) status, run = call(f"{API}/{run['id']}")print(json.dumps(run["data"], indent=2))// Save as fous.mjs, then run: node fous.mjsconst api = "https://api.fous.com/v1/runs" ;const headers = { Authorization: `Bearer ${process.env.FOUS_API_KEY}`, "Content-Type": "application/json" ,};let response = await fetch(api, { method: "POST", headers, body: JSON.stringify({ "method": "substack.com/list_newsletter_posts" , "input": { "newsletter": "Lenny's Newsletter" } }), signal: AbortSignal.timeout(120_000),});let run = await response.json();// 202: still going after 30 s. Ask again until it ends.while (response.status === 202) { await new Promise((done) => setTimeout(done, 2_000)); response = await fetch(`${api}/${run.id}`, { headers }); run = await response.json();}if (!response.ok) throw new Error(run.error.message);console.log(run.data);# Connect your agent to Fous once, over MCP (Streamable HTTP):# https://api.fous.com/mcp# Set FOUS_API_KEY first: create a key at https://app.fous.com/keys.# Claude Codeclaude mcp add \ --scope user \ --transport http fous https://api.fous.com/mcp \ --header "Authorization: Bearer ${FOUS_API_KEY:?Set FOUS_API_KEY to your Fous API key}"# Codexcodex mcp add fous \ --url https://api.fous.com/mcp \ --bearer-token-env-var FOUS_API_KEY# Cursor, VS Code, Windsurf, Cline and morenpx -y add-mcp https://api.fous.com/mcp -g -n fous \ --header "Authorization: Bearer ${FOUS_API_KEY:?Set FOUS_API_KEY to your Fous API key}"# Then ask your agent:# "Use Fous to run substack.com/list_newsletter_posts# with {"newsletter":"Lenny's Newsletter"}"Over HTTP, the answer’s data holds the output. A run still going after 30 seconds answers 202 with its id: ask GET /v1/runs/{id} until it ends.
Schema
Input schema
{ "type": "object", "required": [ "newsletter" ], "properties": { "newsletter": { "type": "string", "description": "The newsletter name or web address, for example Lenny's Newsletter or lenny.substack.com." }, "max_results": { "type": "integer", "default": 20, "maximum": 50, "minimum": 1, "description": "The maximum number of posts to return, for example 20." } }}Output schema
{ "type": "object", "required": [ "newsletter_name", "newsletter_link", "posts" ], "properties": { "posts": { "type": "array", "items": { "type": "object", "required": [ "title", "subtitle", "authors", "published_date", "access", "post_type", "likes", "comment_count", "cover_image_link", "post_link", "post_id" ], "properties": { "likes": { "type": [ "integer", "null" ] }, "title": { "type": "string" }, "access": { "enum": [ "free", "paid" ], "type": "string" }, "authors": { "type": [ "string", "null" ], "description": "Author names, separated by commas." }, "post_id": { "type": "integer" }, "subtitle": { "type": [ "string", "null" ] }, "post_link": { "type": "string" }, "post_type": { "enum": [ "article", "podcast", "video" ], "type": "string" }, "comment_count": { "type": [ "integer", "null" ] }, "published_date": { "type": "string", "format": "date" }, "cover_image_link": { "type": [ "string", "null" ] } }, "additionalProperties": false } }, "newsletter_link": { "type": "string" }, "newsletter_name": { "type": "string" } }, "additionalProperties": false}- Price
- 1 credit per completed run (1¢ with pay as you go); failed runs are free.
- Method
substack.com/list_newsletter_posts- Returns
- newsletter name and link, and per post: title, subtitle, authors, date, access, type, likes, comments, cover image, link and id
Use it from your agent
Give your agent this line. It reads SKILL.md, gets a Fous API key and connects the Fous MCP server. Its run then takes the method substack.com/list_newsletter_posts with the inputs above.
Ask your agent
- Show me the latest 10 posts from Lenny's Newsletter with likes and comment counts
- What are the recent articles on lenny.substack.com, and who wrote them?
- Find the most recent posts from the Stratechery newsletter including dates and whether they are paid
- I want the last 25 public posts from a tech newsletter called Ben's Bites, with cover images and links
- Pull up the newest entries for the Platformer newsletter and tell me which ones are free to read
See more
All Substack methodsFinds public Substack newsletters matching a topic, in Substack's search order. Subscriber counts are null when Substack does not display one.
substack.com/search_newslettersLists the latest public articles by a Medium writer or publication (by username, publication name or Medium link), with dates, reading times, claps, member-only labels and tags.
medium.com/list_author_articlesLists The Verge story headlines, including quick posts, in the order shown on a section page. Archive pages are added when more stories are needed.
theverge.com/list_headlinesLists an account's latest public Bluesky posts, newest first. Replies and reposts are left out unless requested; a repost's time is when the account reposted it.
bsky.app/list_recent_postsLists current Hacker News stories in rank order from the top, newest, best, Ask HN, Show HN or jobs list.
news.ycombinator.com/list_front_pageLists an account's latest public Threads posts, newest first, with text, engagement counts, thumbnails and links. Only posts shown without logging in are returned.
threads.com/list_recent_postsLists a Mastodon account's latest public posts, newest first. Replies and boosts are optional. Private and unlisted posts are left out.
mastodon.social/list_recent_postsLists the latest TechCrunch story headlines in a section, in page order. Skips video, podcast and event promotion cards.
techcrunch.com/list_headlines