This section provides comprehensive documentation for the modern SubstackClient entity-based API, including all classes, methods, and types with detailed descriptions and practical examples.
The main class for interacting with the Substack API using the modern entity model. This client provides access to profiles, posts, notes, comments, and social features through an object-oriented interface.
new SubstackClient(config: SubstackConfig)Creates a new SubstackClient instance with cookie-based authentication.
Parameters:
config: Configuration object (required)token: Your substack.sid cookie value (required)publicationUrl(optional): Publication URL (default: 'substack.com')
Example:
import { SubstackClient } from 'substack-api';
const client = new SubstackClient({
token: 'your-connect-sid-cookie-value',
publicationUrl: 'example.substack.com'
});testConnectivity(): Promise<boolean>Tests API connectivity and authentication status.
Returns:
Promise<boolean>-trueif connected successfully,falseotherwise
Example:
const isConnected = await client.testConnectivity();
if (isConnected) {
console.log('✅ Successfully connected to Substack API');
} else {
console.error('❌ Failed to connect - check your authentication');
}ownProfile(): Promise<OwnProfile>Gets your authenticated profile with write capabilities (content creation, following, etc.).
Returns:
Promise<OwnProfile>- Your profile with full read/write access
Example:
const myProfile = await client.ownProfile();
console.log(`Welcome ${myProfile.name}! (@${myProfile.slug})`);
console.log(`Followers: ${myProfile.followerCount}`);
// Create content using builder pattern
const note = await myProfile.newNote().paragraph().text('Just published something amazing! 🚀').publish();
console.log(`Note created: ${note.id}`);profileForSlug(slug: string): Promise<Profile>Gets a profile by their username/slug (read-only access).
Parameters:
slug: The user's slug/handle (without @ symbol)
Returns:
Promise<Profile>- The user's profile (read-only)
Example:
const profile = await client.profileForSlug('example-user');
console.log(`${profile.name}: ${profile.bio || 'No bio'}`);
// Navigate to their posts
for await (const post of profile.posts({ limit: 5 })) {
console.log(`- ${post.title}`);
}profileForId(id: number): Promise<Profile>Gets a profile by their numeric ID.
Parameters:
id: The user's numeric ID
Returns:
Promise<Profile>- The user's profile
Example:
const profile = await client.profileForId(12345);
console.log(`Found: ${profile.name} (@${profile.slug})`);postForId(id: string): Promise<Post>Gets a specific post by its ID or slug.
Parameters:
id: The post's ID or slug
Returns:
Promise<Post>- The post entity
Example:
const post = await client.postForId('my-awesome-post');
console.log(`Title: ${post.title}`);
console.log(`Author: ${post.author.name}`);
console.log(`Published: ${post.publishedAt?.toLocaleDateString()}`);
// Navigate to comments
for await (const comment of post.comments({ limit: 10 })) {
console.log(`💬 ${comment.author.name}: ${comment.body.substring(0, 100)}...`);
}noteForId(id: string): Promise<Note>Gets a specific note by its ID.
Parameters:
id: The note's ID
Returns:
Promise<Note>- The note entity
Example:
const note = await client.noteForId('note-123');
console.log(`Note by ${note.author.name}: ${note.body}`);
// Interact with the note
await note.like();
await note.addComment('Great point!');commentForId(id: string): Promise<Comment>Gets a specific comment by its ID.
Parameters:
id: The comment's ID
Returns:
Promise<Comment>- The comment entity
Example:
const comment = await client.commentForId('comment-456');
console.log(`Comment by ${comment.author.name}: ${comment.body}`);
// Like the comment
await comment.like();
// Navigate to parent post
if (comment.post) {
console.log(`Parent post: ${comment.post.title}`);
}The SubstackClient returns entity objects that provide navigation and interaction capabilities.
Represents a user profile with read-only access to their content.
interface Profile {
id: number;
name: string;
slug: string;
bio?: string;
followerCount: number;
isFollowing: boolean;
// ... other properties
}posts(options?: { limit?: number }): AsyncIterable<Post>Iterate through the profile's posts with automatic pagination.
Example:
// Get all posts
for await (const post of profile.posts()) {
console.log(post.title);
}
// Limit to 10 posts
for await (const post of profile.posts({ limit: 10 })) {
console.log(post.title);
}notes(options?: { limit?: number }): AsyncIterable<Note>Iterate through the profile's notes.
Example:
for await (const note of profile.notes({ limit: 20 })) {
console.log(`📝 ${note.body.substring(0, 80)}...`);
}follow(): Promise<void>Follow this profile (requires authentication).
Example:
await profile.follow();
console.log(`Now following ${profile.name}`);unfollow(): Promise<void>Unfollow this profile (requires authentication).
Extends Profile with write capabilities for content creation and management.
newNote(): NoteBuilderCreate a new note using the builder pattern (recommended approach).
Example:
// Simple note
const note = await myProfile.newNote().paragraph().text('Just shipped a new feature! 🚀').publish();
// Complex note with formatting
const complexNote = await myProfile
.newNote()
.paragraph()
.text('Building something amazing...')
.paragraph()
.bold('Important update: ')
.text('Check out our latest release!')
.publish();
console.log(`Note created: ${note.id}`);Represents a publication post with interaction capabilities.
interface Post {
id: string;
title: string;
body: string;
author: Profile;
publishedAt?: Date;
reactions?: Reaction[];
// ... other properties
}comments(options?: { limit?: number }): AsyncIterable<Comment>Iterate through post comments with pagination.
Example:
for await (const comment of post.comments({ limit: 50 })) {
console.log(`${comment.author.name}: ${comment.body}`);
}like(): Promise<void>Like this post (requires authentication).
Example:
await post.like();
console.log('Post liked!');addComment(body: string): Promise<Comment>Add a comment to this post (requires authentication).
Example:
const comment = await post.addComment('Great insights! Thanks for sharing.');
console.log(`Comment added: ${comment.id}`);Represents a short-form note/post.
interface Note {
id: string;
body: string;
author: Profile;
createdAt: Date;
reactions?: Reaction[];
// ... other properties
}like(): Promise<void>Like this note.
addComment(body: string): Promise<Comment>Add a comment to this note.
Represents a comment on a post or note.
interface Comment {
id: string;
body: string;
author: Profile;
post?: Post;
createdAt: Date;
reactions?: Reaction[];
// ... other properties
}like(): Promise<void>Like this comment.
The entity model uses async iterators for seamless pagination handling.
// Simple iteration - processes all items
for await (const post of profile.posts()) {
console.log(post.title);
}// Limit total items processed
for await (const post of profile.posts({ limit: 10 })) {
console.log(post.title);
}// Break early based on conditions
for await (const post of profile.posts()) {
console.log(post.title);
if (post.title.includes('IMPORTANT')) {
console.log('Found important post, stopping search');
break;
}
}// Collect into array for further processing
const recentPosts = [];
for await (const post of profile.posts({ limit: 20 })) {
recentPosts.push(post);
}
console.log(`Collected ${recentPosts.length} recent posts`);
recentPosts.forEach(post => {
console.log(`- ${post.title} (${post.publishedAt?.toLocaleDateString()})`);
});// Navigate relationships with nested iteration
for await (const post of profile.posts({ limit: 5 })) {
console.log(`\n📄 ${post.title}`);
console.log(` 💖 ${post.reactions?.length || 0} reactions`);
// Get comments for each post
const comments = [];
for await (const comment of post.comments({ limit: 3 })) {
comments.push(comment);
}
console.log(` 💬 ${comments.length} recent comments`);
}Handle errors gracefully in entity operations:
try {
const profile = await client.ownProfile();
// Try to create content using builder pattern
const note = await profile.newNote().paragraph().text('My first note! 🎉').publish();
console.log(`Note created: ${note.id}`);
} catch (error) {
if (error.message.includes('401')) {
console.error('Authentication failed - check your substack.sid cookie');
} else if (error.message.includes('403')) {
console.error('Permission denied - check your account permissions');
} else {
console.error('Unexpected error:', error.message);
}
}interface SubstackConfig {
token: string; // substack.sid cookie value
publicationUrl?: string; // publication URL (optional)
}interface Reaction {
id: string;
type: string;
author: Profile;
}
interface IteratorOptions {
limit?: number; // Maximum total items to retrieve
}- Always use environment variables for your substack.sid cookie
- Test connectivity before performing operations
- Handle authentication errors gracefully
const client = new SubstackClient({
token: process.env.SUBSTACK_TOKEN!
});
const isConnected = await client.testConnectivity();
if (!isConnected) {
throw new Error('Failed to authenticate with Substack API');
}- Use reasonable limits to avoid overwhelming the API
- Consider memory usage when collecting large datasets
- Break early when you find what you need
// Good: Limited and efficient
for await (const post of profile.posts({ limit: 50 })) {
if (post.title.includes('search-term')) {
console.log('Found it!');
break;
}
}- Always wrap API calls in try-catch blocks
- Handle specific error cases appropriately
- Provide meaningful error messages to users
try {
await post.like();
} catch (error) {
if (error.message.includes('429')) {
console.error('Rate limited - please wait before trying again');
} else {
console.error('Failed to like post:', error.message);
}
}- Use async iteration for large datasets
- Consider caching frequently accessed data
- Batch operations when possible
// Efficient: Process items as they arrive
for await (const post of profile.posts()) {
await processPost(post); // Process immediately
}
// Less efficient: Load all then process
const allPosts = [];
for await (const post of profile.posts()) {
allPosts.push(post);
}
allPosts.forEach(processPost); // Delayed processing