substack-skill
v0.1.5
Published
TypeScript SDK + CLI for Substack API — built for AI agents
Maintainers
Readme
substack-skill
TypeScript SDK + CLI + AI SDK tools for Substack. Built for autonomous agents to publish, manage, and analyze newsletters.
This is not a scraping tool. This SDK is built for agents that create and publish their own content, manage their own publications, and interact with the Substack platform as legitimate users. All usage must comply with Substack's Terms of Use and API Terms of Service.
Features
- Pure API authentication — Login via email OTP using EigenMail. No browser required.
- 75+ API methods — Full read/write coverage: posts, drafts, publishing, comments, reactions, restacking, subscriptions, analytics, payments, and more.
- AI SDK tools — Drop-in tools for Vercel AI SDK compatible agents.
- PostBuilder — Fluent builder for Substack's TipTap editor: headings, images, embeds, tables, code blocks, paywall dividers, and more.
- CLI — Command-line access for common auth, reading, and publishing workflows.
- Authentic requests — Browser-grade headers so requests look like a real user session.
Install
npm install substack-skillQuick Start
import { login, SubstackClient, PostBuilder } from "substack-skill";
// Login (pure API, no browser)
const { cookies } = await login({
eigenMailPrivateKey: "0x...",
cookiesPath: ".substack-cookies.json",
});
// Create client
const client = new SubstackClient();
await client.authenticate({ cookies });
// Write and publish
const body = new PostBuilder()
.heading("My First Automated Post", 1)
.paragraph("Published by an autonomous agent.")
.build();
const draft = await client.createDraft({ title: "My First Automated Post", body });
const post = await client.publishDraft(draft.id);
console.log(post.canonical_url);AI SDK Integration
Install your model/runtime packages separately when you use the AI SDK layer, for example:
npm install ai @ai-sdk/anthropicimport { createSubstackTools, SubstackClient } from "substack-skill";
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
const client = new SubstackClient();
await client.authenticate({ cookiesPath: ".substack-cookies.json" });
const { text } = await generateText({
model: anthropic("claude-sonnet-4-6"),
tools: createSubstackTools(client),
prompt: "Create a draft post about the future of AI agents and list my current drafts",
});Tools
| Tool | Description |
|------|-------------|
| getSubstackPosts | Get posts from any newsletter |
| getSubstackPost | Get a single post with full content |
| searchSubstackPosts | Search within a newsletter |
| getSubstackUser | Get user profile |
| searchSubstackPublications | Search for newsletters |
| getSubstackCategories | List all categories |
| getSubstackNewsletterAuthors | Get newsletter authors |
| getSubstackSelf | Get the authenticated account profile |
| listSubstackDrafts | List current drafts |
| createSubstackDraft | Create a draft post |
| publishSubstackDraft | Publish a draft |
| postSubstackNote | Post a note (short-form) |
Authentication
No browser needed. Uses Substack's email OTP flow with EigenMail:
import { login, generateAgentIdentity } from "substack-skill";
// Generate a new agent identity
const { privateKey, address } = generateAgentIdentity();
// → Whitelist `address` in EigenMail, then:
const { cookies, email } = await login({
eigenMailPrivateKey: privateKey,
cookiesPath: ".substack-cookies.json",
});How it works:
POST /api/v1/email-logintriggers an OTP email to the agent's EigenMail address- SDK polls the EigenMail inbox for the 6-digit verification code
POST /api/v1/email-otp-login/completewith the code returns session cookies- Cookies are saved and reused for all subsequent API calls
API Coverage
Reading (no auth)
- Posts, search, podcasts
- User profiles and feeds
- Newsletter discovery, categories, authors
- Global platform search
Writing (auth required)
- Create, update, delete, publish drafts
- Post notes with attachments
- Upload images
- Comments: read, read replies, delete
- Reactions (heart/like)
- Restack and un-restack posts/notes
- Subscribe and unsubscribe to publications
Publication Management (auth required)
- Profile: get and update
- Publication settings: read and write
- Category tags, launch checklist
- Sections, recommendations
- Subscriber listing
Analytics (auth required)
- Dashboard summary
- Traffic: views, timeseries, visitor sources
- Email: stats, open rates, timeseries
- Growth: sources, events, timeseries
- Audience: location, overlap
- Payments: Stripe, pledges, pledge stats
- Network attribution
Draft Options
client.createDraft({
title: "Post Title",
subtitle: "A subtitle",
body: postBuilder.build(),
audience: "everyone", // "everyone" | "only_paid" | "founding" | "only_free"
coverImage: "https://...", // Hero image URL
description: "Preview text",
searchEngineTitle: "SEO title",
searchEngineDescription: "SEO description",
socialTitle: "Social title",
shouldSendEmail: true, // Email subscribers on publish
postDate: "2026-04-01T00:00:00Z", // Schedule
sectionId: 123, // Assign to section
});PostBuilder
import { PostBuilder, text, bold, italic, link, code } from "substack-skill";
const body = new PostBuilder()
.heading("Title", 1)
.paragraph("Plain paragraph.")
.richParagraph(text("Mix "), bold("bold"), text(" and "), link("links", "https://..."))
.divider()
.image("https://...", "alt text", "caption")
.bulletList(["Item 1", "Item 2"])
.orderedList(["Step 1", "Step 2"])
.blockquote("A quote")
.pullquote("A styled pullquote")
.codeBlock("const x = 1;", "typescript")
.table([["Col A", "Col B"], ["1", "2"], ["3", "4"]])
.youtube("dQw4w9WgXcQ")
.embed("https://twitter.com/...")
.button("CTA", "https://...")
.paywall()
.paragraph("Paid content below the paywall.")
.subscribeWidget()
.build();CLI
Covers common workflows. The SDK surface is broader than the CLI.
# Login
substack auth login --eigenmail-key 0x...
substack --cookies ~/.config/substack/work.json auth whoami
substack auth generate-identity
# Read
substack posts list platformer -l 10
substack posts get platformer some-post-slug
substack posts search platformer "search term"
substack newsletter search "technology"
substack categories list
substack user profile someuser
# Write
substack draft create -t "Title" --body "Content" --audience everyone
substack draft create -t "Title" --file article.txt
substack draft publish 12345
substack draft delete 12345
substack note "Hello from the CLI"Responsible Use
This SDK is for legitimate agent-based publishing and newsletter management. It is NOT for:
- Scraping or bulk data extraction
- Accessing paywalled content without authorization
- Spam, harassment, or unsolicited outreach
- Surveillance or profiling of users
- Circumventing rate limits or access controls
- Competing with or replicating Substack's platform
All usage must comply with Substack's Terms of Use and API Terms of Service. You are responsible for your use of this SDK.
License
MIT with Responsible Use Restriction — see LICENSE.
