{
  "openapi": "3.0.1",
  "info": {
    "title": "Substack Scraper — Posts, Comments & Publications",
    "description": "Scrape Substack as data. One row per post: title, full text, word count, paywall flag, likes, restacks, tags, author. Plus comment threads with reply depth, publication profiles with prices and subscriber hints, and Substack-wide publication search. Public posts only, no login, no API key.",
    "version": "0.1",
    "x-build-id": "4e35EYpz6II48zDlP"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/insight.solutions~substack-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-insight.solutions-substack-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/insight.solutions~substack-scraper/runs": {
      "post": {
        "operationId": "runs-sync-insight.solutions-substack-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/insight.solutions~substack-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-insight.solutions-substack-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "publications": {
            "title": "Publications and post URLs",
            "type": "array",
            "description": "One entry per publication: a **Substack handle** (`astralcodexten`), a **publication address** (`astralcodexten.substack.com` or a custom domain like `https://www.lennysnewsletter.com`), or a **single post URL** (`https://www.astralcodexten.com/p/royce-on-san-francisco`), which returns that one post instead of the archive. Share links (`open.substack.com/pub/…/p/…`) work too, a moved handle is followed to its new domain, and duplicates are read, and billed, once.",
            "items": {
              "type": "string"
            }
          },
          "maxPostsPerPublication": {
            "title": "Posts per publication",
            "minimum": 0,
            "maximum": 100000,
            "type": "integer",
            "description": "How many posts to return for each publication, newest first. Set **0 for the whole archive**, which for a publication running since 2020 is several hundred posts and, with `includeBody` on, several hundred requests. The archive list costs one request per 50 posts whatever you ask for; the bodies are what cost.",
            "default": 50
          },
          "includeBody": {
            "title": "Fetch each post's full text",
            "type": "boolean",
            "description": "**On (default):** one extra request per post, and every row carries the post's full text in `text`. **Off:** the archive listing alone — title, subtitle, date, author, word count, likes, comment count, tags, cover image — at one request per 50 posts and no text column. Turning it off is roughly fifty times fewer requests, so it is the setting to use when you are building an index rather than reading the writing.",
            "default": true
          },
          "sort": {
            "title": "Archive order",
            "enum": [
              "new",
              "top"
            ],
            "type": "string",
            "description": "**Newest first** is the archive as the publication shows it, and the only order in which the `since` cutoff can stop paging early. **Most popular** is Substack's own ranking of the whole archive — useful for \"what does this publication get read for\", not for \"what did they publish last month\".",
            "default": "new"
          },
          "postTypes": {
            "title": "Post types",
            "type": "array",
            "description": "Which kinds of post to keep. Substack labels every post `newsletter`, `podcast`, `thread` or `video`. Leave all four selected for everything. Filtering does not reduce the archive requests — the list has to be read to find them — but it does reduce the rows you are charged for.",
            "items": {
              "type": "string",
              "enum": [
                "newsletter",
                "podcast",
                "thread",
                "video"
              ],
              "enumTitles": [
                "Newsletter posts",
                "Podcast episodes",
                "Discussion threads",
                "Video posts"
              ]
            },
            "default": [
              "newsletter",
              "podcast",
              "thread",
              "video"
            ]
          },
          "searchQueries": {
            "title": "Find publications by keyword",
            "type": "array",
            "description": "Search Substack's own publication directory. Each query returns up to `maxResultsPerQuery` **publication** rows — name, author, address, prices, launch blurb, subscriber order of magnitude — and no posts. This is the input for building a list: \"every Substack about machine learning\", \"who writes about monetary policy\".",
            "items": {
              "type": "string"
            }
          },
          "maxResultsPerQuery": {
            "title": "Publications per query",
            "minimum": 1,
            "maximum": 200,
            "type": "integer",
            "description": "How many publications each query returns. The directory answers twenty per page and stops at 200 for one query, however many more exist.",
            "default": 20
          },
          "search": {
            "title": "Search within the archive",
            "type": "string",
            "description": "Return only posts matching this text, using Substack's own archive search (it reads titles and post text). Leave empty for the whole archive. This searches *inside* the publications you listed; to find publications themselves, use `searchQueries` instead."
          },
          "since": {
            "title": "Only posts published on or after",
            "type": "string",
            "description": "An ISO date (`2026-01-01`) or timestamp. With **Newest first** the walk stops as soon as the archive passes the cutoff, so \"everything since Monday\" costs one or two requests rather than the whole archive — which is what makes this Actor cheap to run on a schedule."
          },
          "includeComments": {
            "title": "Also fetch comments",
            "type": "boolean",
            "description": "Read every post's comment thread as well: one extra request per post that has comments, and one row per comment with `parentId` and `depth` so the thread can be rebuilt. Off by default — a lively post is 76 comment rows, and a hundred posts of them is a different sort of job from reading a hundred posts.",
            "default": false
          },
          "maxCommentsPerPost": {
            "title": "Comments per post",
            "minimum": 0,
            "maximum": 10000,
            "type": "integer",
            "description": "How many comment rows each post may produce, counted depth-first so what you keep is the top of the thread rather than an arbitrary slice. Set 0 for every comment. Ignored unless `includeComments` is on.",
            "default": 100
          },
          "commentSort": {
            "title": "Comment order",
            "enum": [
              "best_first",
              "newest_first",
              "oldest_first"
            ],
            "type": "string",
            "description": "The order Substack returns the thread in — the same three orders the site's own dropdown offers. Replies always follow the comment they answer whichever you pick.",
            "default": "best_first"
          },
          "includeHtml": {
            "title": "Keep the HTML of each post",
            "type": "boolean",
            "description": "Add a `bodyHtml` column holding the post as the publisher wrote it — links, images, footnotes, block quotes. The plain-text `text` column is always there when `includeBody` is on; this is for when you want the markup. It makes rows several times larger and it is off by default.",
            "default": false
          },
          "includePublicationInfo": {
            "title": "Include a publication profile row",
            "type": "boolean",
            "description": "Write one `publication` row per publication: name, author, bio, hero text, logo, created date, first post date, subscription prices, subscriber hint, podcast flag. It costs one request for the publication's home page (a few hundred KB), once per run however many posts you asked for.",
            "default": true
          },
          "maxPagesPerPublication": {
            "title": "Archive pages per publication",
            "minimum": 1,
            "maximum": 200,
            "type": "integer",
            "description": "A hard stop on how deep the archive walk goes: 40 pages is 2,000 posts, which is longer than almost any Substack. It exists so that `maxPostsPerPublication: 0` on a publication with a decade of daily posts cannot run all afternoon.",
            "default": 40
          },
          "maxConcurrency": {
            "title": "Publications in parallel",
            "minimum": 1,
            "maximum": 10,
            "type": "integer",
            "description": "How many publications, post URLs or queries are read at once. Each parallel worker keeps its own proxy session, so one blocked publication burns only its own exit IP. Requests to a *single* publication are always spaced a few hundred milliseconds apart, whatever this is set to.",
            "default": 4
          },
          "maxRunSecs": {
            "title": "Maximum run time (seconds)",
            "minimum": 30,
            "maximum": 3600,
            "type": "integer",
            "description": "Wall-clock budget for the whole run. When it is reached the Actor stops fetching, keeps and bills for every row it already wrote, and files a free diagnostic row for each entry it did not finish. Nothing is charged for work that did not happen.",
            "default": 240
          },
          "proxyConfiguration": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Substack answered datacenter addresses on every endpoint this Actor uses, so the default is Apify's datacenter proxy — much cheaper than residential, and the proxy cost is already inside the per-row price. Switch to residential only if you see `blocked` rows. Sessions rotate automatically when an exit IP is refused.",
            "default": {
              "useApifyProxy": true
            }
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}