Substack API

Substack Posts API

Pull posts from any Substack publication by its URL, or pass a single post URL (…/p/slug) to fetch just that one article. Returns title, subtitle, publish date, paywall status, word count, engagement (reactions, comments, restacks), author profile, publication details, the full article body as text, HTML and Markdown, and optional comment threads.

$0.439604/1k results, pay per request, no subscription.

What the API returneditems[0]
{
"postId": "148302911",
"title": "Why roadmaps are guesses",
"url": "https://www.clearheaded.blog/p/why-roadmaps-are-guesses",
"slug": "why-roadmaps-are-guesses",
"postType": "newsletter",
"createdUtc": 1758585600,
"wordcount": 2140,
"authorName": "Priya Nakamura",
... 22 more fields
}
Uptime
94.08%
30d · 304 calls
Requests
304
30d · weekly, last 12 wks
Response
3.3s
median · 30d

Pricing

Substack Posts API pricing

You pay
$4.40
$0.439604/1k results, only for what you use. No monthly plan.
One request costs at most $0.0444 (101 results).
The $0.10 starting balance covers at least 2 requests.

Try it

Get Substack posts in one request

Open in
Get a free key
Sample response
Free runs return only the first 3 results. Fund a key to get the full response.

This sample shows 58 of 69 fields. See every field

{
  "found": true,
  "data": {
    "items": [
      {
        "postId": "148302911",
        "title": "Why roadmaps are guesses",
        "url": "https://www.clearheaded.blog/p/why-roadmaps-are-guesses",
        "slug": "why-roadmaps-are-guesses",
        "postType": "newsletter",
        "createdUtc": 1758585600,
        "wordcount": 2140,
        "authorName": "Priya Nakamura",
        "authorHandle": "priyanakamura",
        "contentStatus": "full",
        "text": "Every roadmap I have shipped was a guess with a deadline attached.",
        "publication": {
          "id": "pub_5521",
          "name": "Clearheaded",
          "url": "https://www.clearheaded.blog",
          "subdomain": "clearheaded",
          "customDomain": "www.clearheaded.blog",
          "description": "Notes on building things without lying to yourself.",
          "image": "https://substackcdn.com/image/fetch/clearheaded/logo.png",
          "language": "en",
          "paymentsEnabled": true,
          "subscriberCount": 38400
        },
        "comments": [
          {
            "commentId": "c_88213",
            "text": "This matches every planning meeting I have sat through for a decade.",
            "createdUtc": 1758600000,
            "authorName": "Devon Okafor",
            "authorHandle": "devonokafor",
            "authorUrl": "https://substack.com/@devonokafor",
            "authorImage": "https://substackcdn.com/image/fetch/clearheaded/devon-avatar.png",
            "reactionCount": 44,
            "restackCount": 2,
            "isPinned": true,
            "isAuthor": false,
            "replies": [
              {
                "commentId": "c_88240",
                "text": "Glad it landed. Curious how your team names the guess out loud.",
                "createdUtc": 1758603600,
                "authorName": "Priya Nakamura",
                "authorHandle": "priyanakamura",
                "authorUrl": "https://www.clearheaded.blog/people/priya-nakamura",
                "authorImage": "https://substackcdn.com/image/fetch/clearheaded/priya-avatar.png",
                "reactionCount": 12,
                "restackCount": 0,
                "isPinned": false,
                "isAuthor": true
              }
            ]
          }
        ]
      },
      {
        "postId": "148190442",
        "title": "The three-question standup",
        "url": "https://www.clearheaded.blog/p/the-three-question-standup",
        "slug": "the-three-question-standup",
        "subtitle": "",
        "description": "A shorter standup that actually surfaces risk",
        "postType": "newsletter",
        "createdUtc": 1757980800,
        "isPaid": true,
        "language": "en",
        "wordcount": 1420,
        "reactionCount": 501,
        "commentCount": 29,
        "replyCount": 6,
        "restackCount": 18,
        "authorName": "Priya Nakamura",
        "authorHandle": "priyanakamura",
        "authorUrl": "https://www.clearheaded.blog/people/priya-nakamura",
        "contentStatus": "preview_only",
        "text": "Most standups answer the same three questions badly. Here is a version that takes six minutes and actually works...",
        "publication": {
          "id": "pub_5521",
          "name": "Clearheaded",
          "url": "https://www.clearheaded.blog",
          "subdomain": "clearheaded"
        },
        "comments": []
      },
      {
        "postId": "148075310",
        "title": "Ask me anything: hiring your first PM",
        "url": "https://www.clearheaded.blog/p/ask-me-anything-hiring-your-first-pm",
        "slug": "ask-me-anything-hiring-your-first-pm",
        "subtitle": "Reader questions from last week's thread",
        "description": "Reader questions from last week's thread",
        "postType": "thread",
        "createdUtc": 1757376000,
        "isPaid": false,
        "language": "en",
        "wordcount": 640,
        "reactionCount": 203,
        "commentCount": 41,
        "replyCount": 9,
        "restackCount": 5,
        "authorName": "Priya Nakamura",
        "authorHandle": "priyanakamura",
        "authorUrl": "https://www.clearheaded.blog/people/priya-nakamura",
        "contentStatus": "metadata_only",
        "publication": {
          "id": "pub_5521",
          "name": "Clearheaded",
          "url": "https://www.clearheaded.blog",
          "subdomain": "clearheaded"
        },
        "comments": []
      }
    ]
  }
}

Full parameter and response reference - every field, type, and example for this endpoint.

Reference

One request, one response shape.

Every field you send and every field you get back, each with a real example.

Last verified 2026-10-31 · uptime and latency measured over 30d

Request body

JSON, posted to this endpoint.

  • urlstring

    https://www.clearheaded.blog

    Either a Substack publication URL / custom domain to fetch its recent posts (e.g. https://www.astralcodexten.com), OR a single post URL to fetch just that one article with full content (e.g. https://www.astralcodexten.com/p/your-book-review).

  • contentTypeenum

    Restrict to a single post type, or 'all' (e.g. newsletter).

  • endDatestring

    Only return posts published on or before this date, format YYYY-MM-DD (e.g. 2024-12-31). Applied within the most recent 'limit' posts scanned.

  • includeCommentsboolean

    Include public comment threads and their direct replies on each post (e.g. true).

  • includeContentboolean

    true

    Include the full article body as text, HTML and Markdown. Set false for metadata only, which is faster (e.g. false).

  • limitinteger

    3

    Maximum number of recent posts to return when given a publication URL (1-100, default 25); ignored for a single post URL, which always returns that one post. You are billed per post returned, so a lower limit costs less.

  • maxCommentsinteger

    Maximum comments collected per post when 'includeComments' is true (0-500, default 20). Costs nothing extra (e.g. 50).

  • minCommentsinteger

    Only return posts with at least this many comments (e.g. 10).

  • minReactionsinteger

    Only return posts with at least this many reactions (e.g. 100).

  • minWordCountinteger

    Only return posts with at least this many words, which filters out short notes and announcements (e.g. 1000).

  • onlyFreeboolean

    Return only free (non-paywalled) posts (e.g. true).

  • startDatestring

    Only return posts published on or after this date, format YYYY-MM-DD (e.g. 2024-01-01). Applied within the most recent 'limit' posts scanned, so raise 'limit' to reach older date ranges.

You send. A small JSON body. The values shown are examples to replace with your own.

Response

JSON, one example value per field.

each itemdata.items[]

Post records: title, subtitle, URL, publish date, paywall status, word count, engagement (reactions, comments, restacks), author profile, publication details, the article body as text, HTML and Markdown, and comment threads when requested. Populated whenever the provider has data for the entity.

  • authorBiostring

    Author bio as shown on their Substack profile.

  • authorHandlestring

    priyanakamura

    Substack handle of the post author. Populated whenever the provider has data for the entity.

  • authorImagestring

    Profile photo URL of the post author.

  • authorNamestring

    Priya Nakamura

    Display name of the post author. Populated whenever the provider has data for the entity.

  • authorUrlstring

    Substack profile URL of the post author.

  • commentCountinteger

    Number of top-level comments on the post.

  • contentStatusstring

    full

    How much of the article body this record carries: 'full' for the whole article, 'preview_only' for the public excerpt of a paywalled post, 'metadata_only' when no body was requested or available, or 'failed' when extraction failed. Read this before trusting 'text', 'html', or 'markdown'. Populated whenever the provider has data for the entity.

  • createdUtcnumber

    1758585600

    UTC epoch timestamp in seconds (Unix time). Multiply by 1000 for a JS Date in milliseconds. Populated whenever the provider has data for the entity.

  • descriptionstring

    Short post description, usually the subtitle or an excerpt.

  • hasVoiceoverboolean

    Whether the post carries a narrated audio version.

  • htmlstring

    Article body as HTML. Present when 'includeContent' is true and 'contentStatus' is 'full' or 'preview_only'.

  • imagestring

    Cover image URL.

  • isPaidboolean

    Whether the post is behind a paywall.

  • languagestring

    Two-letter language code of the post.

  • markdownstring

    Article body as Markdown. Present when 'includeContent' is true and 'contentStatus' is 'full' or 'preview_only'.

  • podcastUrlstring

    Audio URL for a podcast post or a narrated voiceover, when the post has one.

  • postIdstring

    148302911

    Substack post identifier. Populated whenever the provider has data for the entity.

  • postTypestring

    newsletter

    Post type (newsletter, podcast, or thread). Populated whenever the provider has data for the entity.

  • reactionCountinteger

    Number of reactions (likes) on the post.

  • replyCountinteger

    Number of replies to comments on the post.

  • restackCountinteger

    Number of times the post was restacked.

  • slugstring

    why-roadmaps-are-guesses

    Post slug, the last path segment of the post URL. Populated whenever the provider has data for the entity.

  • subtitlestring

    Post subtitle or deck.

  • textstring

    Every roadmap I have shipped was a guess with a deadline attached.

    Article body as plain text. Present when 'includeContent' is true and 'contentStatus' is 'full' or 'preview_only'.

  • titlestring

    Why roadmaps are guesses

    Post title. Populated whenever the provider has data for the entity.

  • updatedUtcnumber

    UTC epoch timestamp in seconds (Unix time). Multiply by 1000 for a JS Date in milliseconds.

  • urlstring

    https://www.clearheaded.blog/p/why-roadmaps-are-guesses

    Canonical post URL. Populated whenever the provider has data for the entity.

  • wordcountinteger

    2140

    Approximate word count of the article.

each commentdata.items[].comments[]

Top-level comment threads on the post, each with its direct replies. Empty unless 'includeComments' is true. Replies nested more than one level deep are not returned.

  • authorHandlestring

    devonokafor

    Substack handle of the comment author.

  • authorImagestring

    https://substackcdn.com/image/fetch/clearheaded/devon-avatar.png

    Profile photo URL of the comment author.

  • authorNamestring

    Devon Okafor

    Display name of the comment author.

  • authorUrlstring

    https://substack.com/@devonokafor

    Substack profile URL of the comment author.

  • commentIdstring

    c_88213

    Substack comment identifier.

  • createdUtcnumber

    1758600000

    UTC epoch timestamp in seconds (Unix time). Multiply by 1000 for a JS Date in milliseconds.

  • editedUtcnumber

    UTC epoch timestamp in seconds (Unix time). Multiply by 1000 for a JS Date in milliseconds.

  • isAuthorboolean

    false

    Whether the comment was written by the post author.

  • isPinnedboolean

    true

    Whether the comment is pinned by the publication.

  • reactionCountinteger

    44

    Number of reactions on the comment.

  • restackCountinteger

    2

    Number of times the comment was restacked.

  • textstring

    This matches every planning meeting I have sat through for a decade.

    Comment body text.

each replydata.items[].comments[].replies[]

Direct replies to this comment.

  • authorHandlestring

    priyanakamura

    Substack handle of the reply author.

  • authorImagestring

    https://substackcdn.com/image/fetch/clearheaded/priya-avatar.png

    Profile photo URL of the reply author.

  • authorNamestring

    Priya Nakamura

    Display name of the reply author.

  • authorUrlstring

    https://www.clearheaded.blog/people/priya-nakamura

    Substack profile URL of the reply author.

  • commentIdstring

    c_88240

    Substack comment identifier.

  • createdUtcnumber

    1758603600

    UTC epoch timestamp in seconds (Unix time). Multiply by 1000 for a JS Date in milliseconds.

  • editedUtcnumber

    UTC epoch timestamp in seconds (Unix time). Multiply by 1000 for a JS Date in milliseconds.

  • isAuthorboolean

    true

    Whether the reply was written by the post author.

  • isPinnedboolean

    false

    Whether the reply is pinned by the publication.

  • reactionCountinteger

    12

    Number of reactions on the reply.

  • restackCountinteger

    0

    Number of times the reply was restacked.

  • textstring

    Glad it landed. Curious how your team names the guess out loud.

    Reply body text.

publicationdata.items[].publication

The publication the post belongs to.

  • customDomainstring

    www.clearheaded.blog

    Custom domain the publication is served on, when it has one.

  • descriptionstring

    Notes on building things without lying to yourself.

    Publication tagline or hero text.

  • idstring

    pub_5521

    Substack publication identifier.

  • imagestring

    https://substackcdn.com/image/fetch/clearheaded/logo.png

    Publication logo URL.

  • languagestring

    en

    Two-letter language code of the publication.

  • namestring

    Clearheaded

    Publication name.

  • paymentsEnabledboolean

    true

    Whether the publication sells paid subscriptions.

  • subdomainstring

    clearheaded

    Publication subdomain on substack.com.

  • subscriberCountinteger

    38400

    Subscriber count, when the publication publishes it.

  • urlstring

    https://www.clearheaded.blog

    Publication home URL.

top level
  • foundboolean

    true

    Whether any posts were found for the request.

  • reason"not_found"

    Present only when `found` is false, and says why there is no result. `not_found`: the source states the target does not exist, or returned nothing for it. A `found: false` answer is a successful call, not an error, and `costUsd` is what it actually cost.

You get back. Named, typed fields in the same shape whichever source answers.

FAQ

About the Substack Posts API

The AnyAPI Substack Posts API returns Substack posts data as normalized JSON from one POST call to /v1/run/substack.posts. Pull posts from any Substack publication by its URL, or pass a single post URL (…/p/slug) to fetch just that one article. Returns title, subtitle, publish date, paywall status, word count, engagement (reactions, comments, restacks), author profile, publication details, the full article body as text, HTML and Markdown, and optional comment threads. AnyAPI returns one normalized schema whichever source serves it. It costs $0.44 per 1,000 results, and never more than $0.0444 for a single request, in US dollars with no subscription and no monthly minimum. Over the last 30 days, 94.1% of Substack posts calls through AnyAPI succeeded, with a median response time of 3.3 seconds across 304 measured calls.

It costs $0.44 per 1,000 results, and never more than $0.0444 for a single request, in US dollars with no subscription and no monthly minimum. You fund one USD wallet and each call draws it down. Some failed wallet-funded requests incur processing charges that we pass through at cost, with no markup; the error response shows the amount charged.

Pull posts from any Substack publication by its URL, or pass a single post URL (…/p/slug) to fetch just that one article. Returns title, subtitle, publish date, paywall status, word count, engagement (reactions, comments, restacks), author profile, publication details, the full article body as text, HTML and Markdown, and optional comment threads. The response is normalized JSON with the same envelope every AnyAPI endpoint returns, so parsing a second endpoint is a change of URL and nothing else.

Over the last 30 days, 94.1% of Substack posts calls through AnyAPI succeeded, with a median response time of 3.3 seconds across 304 measured calls. These are AnyAPI's own measurements of traffic through the gateway, recomputed continuously, not a published service-level target.

Yes. A new AnyAPI account starts with $0.10 of free balance and no card required, which covers at least 2 Substack Posts calls at up to $0.0444 each.

Send a POST request to https://api.getanyapi.com/v1/run/substack.posts with your AnyAPI key in an "Authorization: Bearer" header and the input as a JSON body. The same key and the same wallet work on every AnyAPI endpoint.

No. You add US dollars to one prepaid AnyAPI balance and each request draws it down. There is no monthly plan and no minimum, and the same balance pays for every AnyAPI endpoint.

Yes. Install @getanyapi/sdk from npm for TypeScript or getanyapi from pip for Python. Both call every AnyAPI endpoint with the same key, and any HTTP client works too.

Yes. An agent can connect to the AnyAPI MCP server at https://api.getanyapi.com/mcp, or create its own free-trial key with one unauthenticated POST to https://api.getanyapi.com/agent/signup and call this endpoint over HTTP.