No official APIāļø No problemā¼ļø How I reverse-engineered Substack API (and so can you)
You donāt need official docs to create real tools - just curiosity and patience
If youāve ever tried to automate or analyze data on Substack, youāve probably hit a wall. There is no official Substack API or documentation for developers ā a fact that has frustrated many of us. I ran into this roadblock when I wanted to fetch and analyze Substack Notes data (Substackās Twitter-like feature) as part of an automation workflow. My goal was to use the automation tool n8n1 to gather insights from Notes. But without an API, it felt impossible. As one user plainly put it, āSubstack currently doesnāt provide an API for automated postingā ā and by extension, no easy way to fetch data like Notes. This was the spark: a mix of personal need and irritation with the lack of a solution.
n8n
is like LEGO for automation - a visual way to connect tools, APIs, and data without writing full-blown apps. Iāve used it to auto-like Substack Notes, sync posts to Notion, and even monitor trends in comments. Want a deep dive on how n8n
works and how it streamlines API-based workflows?
š Leave a comment and let me know.
I initially felt stuck. Scraping the Substack websiteās HTML was an option, but a messy and brittle one. Substackās interface is dynamic and interactive, which makes pure HTML scraping unreliable for complex data (like paginated lists of notes or comments). I didnāt want a half-baked workaround; I wanted a real, robust solution. Instead of giving up, this frustration lit a fire in me: what if I could find a hidden Substack API by reverse-engineering how the web app works? š
The missing tool
The absence of an official API wasnāt just a minor inconvenience ā it was a glaring gap. I realized I wasnāt alone in this; many Substack writers and developers wished for a way to programmatically interact with Substack, whether for analytics, content management, or custom integrations. Some had resorted to hacks: copy-pasting subscriber info, manually adding emails from external forms, or building custom scrapers. These approaches were tedious and error-prone. It felt like having a powerful sports car (Substackās platform) but no steering wheel to control it programmatically.
Rather than accept defeat or continue with brittle scrapers, I decided to build the missing tool myself. The idea was to create an unofficial Substack API client ā something that could fetch data and perform actions just like an official API would, but without Substackās direct support. This wasnāt about āhackingā or breaking rules; it was about using publicly available web traffic to create a tool that should exist. I wanted to bridge the gap between Substackās rich functionality and the developer community that craved to use it. In short, if Substack wasnāt going to give us an API, Iād figure out how to make one.
Why I built it
So why take on this challenge? First, I had a personal need: I wanted to automate my Substack workflows (like analyzing Notes engagement, consolidating data, and maybe even posting or scheduling content) via n8n. Doing this manually or with unreliable scrapers was not sustainable. Second, curiosity ā Iāve always enjoyed peeking under the hood of web apps to see how they work. Reverse-engineering can be a fun puzzle, and I had a hunch that Substackās frontend had some secrets to reveal.
Lastly, Iām a TypeScript enthusiast, and I saw an opportunity to create typed domain models for Substackās world (think classes like Profile
, Post
, Comment
, Note
, etc.). In other words, I wanted a clean, type-safe interface I could use in my Node.js projects. This would make it easier to work with Substack data in a structured way, with the safety of TypeScriptās compile-time checks. The end goal was a library that feels nice to use ā something modern and developer-friendly. (In fact, the library I ended up building is exactly that: āa modern, type-safe TypeScript client for the Substack APIā with classes representing Substack entities).
How it began
To reverse-engineer Substackās private API, I had to change how I looked at the website. I asked myself: how does Substackās web app actually fetch content?
Before modern web apps, websites were simple: when you visited a page, your browser sent a request to the server, which grabbed the data from a database, rendered a complete HTML document, and sent it back - all in one go. Everything you needed was already embedded in the response. No background API calls, no separate fetches. This āserver-renderedā model left no APIs for us to explore or reverse-engineer. But todayās apps - like Substack - load data dynamically using JavaScript, making separate requests in the background.
When you load the site, the browser gets a minimal HTML shell, and then fetches real content - posts, profiles, comments - asynchronously through background API calls. The diagram above shows exactly how that works: your browser loads the page, then sends separate requests like GET /api/v1/profile
or GET /api/v1/posts
to the backend server, which pulls data from the database and returns it as JSON. That shift is exactly what made it possible for me to discover and document Substackās hidden API. My job was to eavesdrop on that conversation between the browser and the server.
How do you eavesdrop on your browserās conversations? With the browserās developer tools! Every modern browser has developer tools (usually opened by right-clicking and choosing āInspect Elementā).
I opened Safari DevTools and clicked on the Network tab. This magical panel shows every request the browser makes ā images, scripts, and importantly, API calls. To focus on the API calls, I filtered the network log (e.g., looking for URLs containing /api/
or watching for requests labeled XHR/fetch). Then I started using Substack normally: I navigated through my Substack dashboard, opened the Notes feed, clicked around on my profile. Sure enough, calls began appearing in the Network panel. It was like discovering footprints of a hidden creature ā each API call URL was a clue to how Substackās private endpoints worked.
Cracking the undocumented API
Once I could see the network calls, the real fun began: figuring out what each call did and how to use it myself. I approached this systematically, almost like a detective gathering evidence. Hereās the process I followed.
Identify interesting requests. In the DevTools Network tab, I looked for requests that likely contained the data I wanted. For example, when I loaded the Substack Notes page, I saw a request URL that had notes
in it (bingo!). There were others for things like posts
, comments
, etc. The presence of familiar keywords made it clear these were Substackās internal API endpoints.
Examine the details. I clicked on each interesting request to inspect its details. DevTools lets you see the URL, the method (GET, POST, etc.), query parameters, and even the response data. The responses were in JSON ā human-readable data structures. For instance, one endpoint returned a list of notes with fields like id
, content
, publish_date
, etc. I was basically looking at the data Substackās own web interface uses.
Copy the request (for replaying). Unlike Chrome, Safari doesnāt let you āCopy as cURLā, so I had to reconstruct the request myself. I copied the full request URL, noted the necessary headers (including auth cookies), and then rewrote it as a curl
command line by hand.
Replicate the call with cURL. I opened my terminal and pasted the cURL command. This let me call the same endpoint outside of the browser. Initially, I got a bunch of data back ā success! I was careful to include my authentication cookies (Substack uses a cookie called connect.sid
for logged-in sessions) in the cURL request. Without authentication, some endpoints would return an error or empty data. With my session cookie, however, I could pull my private data (like my own profile info or subscriber-only posts) just like the browser did.
Generalize and experiment. Once I verified a single request worked, I started tweaking it. For example, if I found an endpoint like /api/v1/notes?cursor=<some_position>
, Iād try changing parameters like cursor=<some_position>
to see if I get more results, or maybe fetch older notes. I also tried endpoints on different publications: e.g., replacing my Substackās domain with another publicationās domain to see if I could fetch their public data. Little by little, I mapped out what endpoints exist and what they return.
Document the endpoints. I kept notes on each discovered endpoint ā the URL pattern, required parameters, and what data it returns. It felt like drawing a treasure map. For instance, I learned there were endpoints for listing posts, getting a single postās details, listing comments on a post, retrieving my own profile and the profiles I follow, and yes, endpoints to read and write Notes.
At this point, I essentially had the pieces of an unofficial REST API for Substack. It was undocumented, sure, but it was real and working. I could now fetch my Substack data on demand. Just to illustrate, hereās an example of what one of those calls might look like using cURL:
# Example: Fetch the latest notes from your Substack (requires auth cookie)
curl 'https://<your-substack>.substack.com/api/v1/notes' \
-H 'Cookie: connect.sid=<your_session_cookie>' \
-H 'Accept: application/json'
This isnāt an official documented call, but itās exactly what the Substack web app does internally. The result is a JSON payload containing the most recent Notes, which I could then parse in code. I repeated this kind of exercise for various features ā posts, comments, profiles ā each time using DevTools to find the call and cURL (or a REST client) to replay it myself.
Throughout this cracking process, two things became clear:
modern web apps like Substack are built on these kinds of JSON APIs (even if theyāre private), and
with some patience, anyone can uncover them.
Itās not magic ā itās just observing and reproducing what our browser is already doing. This realization was empowering, especially for an āunofficialā project like mine.
Open sourcing it
Finding those endpoints was just the first step. The end goal was to create a usable tool ā a code library ā so that I (and others) wouldnāt have to repeat this detective work every time. I rolled up my sleeves and started coding what would become the substack-api
package: an unofficial Substack API client built in TypeScript. My approach was to design the library in a domain-driven way, modeling the key concepts of Substack (users, posts, comments, notes, etc.) as classes or functions in a logical structure.
I wanted the library to feel intuitive. For example, if you have a Profile
object (representing a user), you might call .posts()
on it to get their posts, or .notes()
to get their notes. If you have a Post
object, you could call .comments()
to fetch its comments. This mirrors real-life relationships: profiles have posts; posts have comments. Under the hood, those methods would hit the corresponding endpoints I discovered (e.g., GET /api/v1/posts/...
or GET /api/v1/comments/...
), but as a user of the library you wouldnāt have to worry about the URL details ā itās all nicely abstracted.
To give you a taste, hereās a short example in TypeScript using the library:
import { SubstackClient } from 'substack-api';
const client = new SubstackClient({
apiKey: '<your connect.sid cookie value>', // authentication
hostname: 'yourpublication.substack.com' // your Substack domain
});
console.log('š Substack API Client Example\n')
// Fetch your own profile
const myProfile = await client.ownProfile();
console.log(`Hello, ${myProfile.name}! Your slug is ${myProfile.slug}.`);
// Iterate over your recent posts
for await (const post of myProfile.posts({ limit: 3 })) {
console.log(`- "${post.title}" (published on ${post.publishedAt?.toLocaleDateString()})`);
}
The code above initializes the client with your session cookie (acting as an API key) and your Substack domain. Then it retrieves your profile and prints your slug, and fetches your latest 3 posts with their titles and dates.
š Substack API Client Example
ā
Using API key from environment variables
Hello, Jakub Slys šļø! Your slug is jakubslys.
- "Sharding vs partitioning vs replication: Embrace the key differences" (published on 7/25/2025)
- "Get Your Cert" (published on 7/19/2025)
- "Understanding caching: a beginnerās guide" (published on 7/18/2025)
The nice thing is that everything is strongly typed ā post.title
and post.publishedAt
are real properties with proper types. No need to dig through JSON manually each time.
I open-sourced this project on GitHub2. I also published it on npm as substack-api
so that anyone can npm install
it easily. The library ended up with a robust feature set beyond just reading data. For example, it supports content creation (yes, you can publish a new Note through the API client, just like the web interface does). In other words, itās not just read-only ā it lets you interact with Substack in many of the same ways you can via the website.
The response from the community was encouraging. The library is already being used in other projects ā for example, an n8n Substack integration node relies on substack-api
under the hood for all its operations.
š Curious what happens when this API meets automation? Iāve already hooked it up to n8n
- the open-source ⨠visual automation tool.
š” Imagine:
š Auto-saving your Substack Notes to Notion
š¬ Getting real-time alerts when your favorite writer posts
š Tracking follower growth without ever opening a browser
š Want to see how it works? Drop a comment below!š
Knowing that my work enabled an n8n integration (so people can now plug Substack into their automation workflows) is incredibly satisfying. It feels like we collectively unlocked Substackās data and made it available for creative use. And because itās open source, others can audit the code, contribute improvements, or adapt it for their own needs.
What I learned
This journey taught (or reinforced) several lessons, both technical and personal.
The browser is your friend. I gained a much deeper appreciation for browser dev tools. Learning to use the Network inspector to watch API calls is an incredibly useful skill. Itās like having x-ray vision into any web app. Now, whenever I use a web service that doesnāt have an API, I instinctively pop open DevTools to see what I can find. For anyone new to this, remember: if you can do something in a web app, you can probably script it ā you just need to observe how the app talks to the server.
Copilot agents are powerful, but they need precision. One surprising insight was just how useful GitHub Copilot (or other AI code agents) can be when reverse-engineering and writing wrappers around undocumented APIs. From scaffolding API request code to generating type definitions from sample payloads, it accelerated a lot of my work. But I quickly learned that the quality of output depends entirely on the clarity of the prompt. Vague questions led to vague code. On the other hand, if I gave Copilot a clear goal ā for example, āconvert this JSON into a strongly typed TypeScript interfaceā or āgenerate a wrapper for this REST endpoint using fetchā ā the results were much better. The tool is powerful, but only when you are precise. It's like working with a junior dev who's lightning-fast but needs clear specs. That mindset made me a better prompt-writer and taught me to think more deliberately about what I was trying to build.
Patience and attention to details. Reverse-engineering isnāt instant. I had to methodically try different interactions in Substack, log lots of requests, and piece together how things work. Missing a header or a small query parameter could mean the difference between an endpoint working or not. I learned to be meticulous ā documenting every finding, comparing responses, and double-checking assumptions.
Code design and documentation matter. When building the library, I wanted it to be approachable for others. I invested time in writing clear documentation, with examples and an API reference. This made the project more welcoming and also forced me to understand my own code better. I found that writing docs often highlights edge-cases or API quirks that you might otherwise gloss over. A well-documented project is more likely to attract users and collaborators.
Respect and responsibility. An important takeaway is the ethics of reverse engineering. I was careful that all my work stayed within legal bounds ā I used my own credentials, accessed only data I was allowed to (like my own and public data), and did not attempt to break any protections. Reverse engineering web APIs is generally legal when youāre just mimicking normal browser behavior, but itās important to use such powers responsibly.
In a nutshell, this project was a crash course in web API sleuthing, and it left me with sharper skills and a broader perspective on web development.
What is next
The project doesnāt end here. There are more endpoints and features on my wishlist for the substack-api
library. Here are a few things on the horizon.
Integration with N8n. Now that this unofficial API exists, Iām excited to integrate Substack with other parts of my tech stack. One immediate plan is deeper n8n integration ā maybe a set of n8n nodes that can, not only read, but also write (e.g. publish a note to Substack as part of a workflow). I also think of things like creating custom analytics dashboards, or even hooking Substack up to an AI (imagine an AI summarizing your Substack stats daily ā the skyās the limit when an API is available).
š¤ Are you into automation? Or curious about it?
š§© Want to learn how to automate with n8n
?
š ļø Interested in building your own workflows like this š one?
Collaboration and contributions. Iām hoping more developers will join in to contribute code or report issues. Being open source means anyone can suggest improvements. Even simple contributions like improving documentation or writing examples for specific workflows (say, using the API to back up your posts, or to cross-post content) can be super helpful for new users.
Keeping up with Substack. As Substack evolves, the API might change. I plan to keep an eye on it ā that means if Substack changes how their web app communicates (say they introduce GraphQL, or change endpoints), I will update the library accordingly so it continues to work. In a sense, maintaining an unofficial API is an ongoing reverse-engineering effort.
In short, I see this project as alive and growing. If you have ideas or needs, you can always open an issue on the GitHub repo. Iām excited to see where it goes next, and Iām open to collaboration.
It is your time now!
By now, I hope youāre feeling inspired to tinker with your own projects. Whether itās Substack or another platform with an unofficial API, the process is accessible to anyone with curiosity and a bit of patience. Give it a try ā you might be surprised at what you can unlock. And if you do, share your story! We learn together as a community when we exchange these experiences.
š Reverse engineering isn't about breaking the rules - it's about understanding the systems we already use every day. I built this tool to automate my work and share the knowledge. You can do the same.
If this post helped or inspired you - drop a star āļø in GitHub , file an issue, or tell me what youāre building. š
Reverse-engineering APIs is a tech band-aid, not a strategy. It's like trying to tune a piano with a hammer. Legal ramifications aside, it's an unsustainable approach. Seen too many startups crash this way.
Great breakdown! I used to use this technique for some of my old projects. The only problem with this approach is that if we want to create apps or workflows to sell, like in your example to help manage Substack: users need to provide a cookie, which they may be afraid to do due to security risks. But aside from that concern, we can create a lot of useful things to help grow Substack newsletters more effectively.