Skip to content

Latest commit

 

History

History
628 lines (463 loc) · 12.4 KB

File metadata and controls

628 lines (463 loc) · 12.4 KB

API Reference

This section provides comprehensive documentation for the modern SubstackClient entity-based API, including all classes, methods, and types with detailed descriptions and practical examples.

SubstackClient Class

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.

Constructor

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'
});

Core Methods

testConnectivity()

testConnectivity(): Promise<boolean>

Tests API connectivity and authentication status.

Returns:

  • Promise<boolean> - true if connected successfully, false otherwise

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()

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()

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()

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()

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()

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()

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}`);
}

Entity Classes

The SubstackClient returns entity objects that provide navigation and interaction capabilities.

Profile Entity

Represents a user profile with read-only access to their content.

Properties

interface Profile {
  id: number;
  name: string;
  slug: string;
  bio?: string;
  followerCount: number;
  isFollowing: boolean;
  // ... other properties
}

Methods

posts()
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()
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()
follow(): Promise<void>

Follow this profile (requires authentication).

Example:

await profile.follow();
console.log(`Now following ${profile.name}`);
unfollow()
unfollow(): Promise<void>

Unfollow this profile (requires authentication).

OwnProfile Entity

Extends Profile with write capabilities for content creation and management.

Additional Methods

newNote()
newNote(): NoteBuilder

Create 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}`);

Post Entity

Represents a publication post with interaction capabilities.

Properties

interface Post {
  id: string;
  title: string;
  body: string;
  author: Profile;
  publishedAt?: Date;
  reactions?: Reaction[];
  // ... other properties
}

Methods

comments()
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()
like(): Promise<void>

Like this post (requires authentication).

Example:

await post.like();
console.log('Post liked!');
addComment()
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}`);

Note Entity

Represents a short-form note/post.

Properties

interface Note {
  id: string;
  body: string;
  author: Profile;
  createdAt: Date;
  reactions?: Reaction[];
  // ... other properties
}

Methods

like()
like(): Promise<void>

Like this note.

addComment()
addComment(body: string): Promise<Comment>

Add a comment to this note.

Comment Entity

Represents a comment on a post or note.

Properties

interface Comment {
  id: string;
  body: string;
  author: Profile;
  post?: Post;
  createdAt: Date;
  reactions?: Reaction[];
  // ... other properties
}

Methods

like()
like(): Promise<void>

Like this comment.

Async Iteration Patterns

The entity model uses async iterators for seamless pagination handling.

Basic Iteration

// Simple iteration - processes all items
for await (const post of profile.posts()) {
  console.log(post.title);
}

Limited Iteration

// Limit total items processed
for await (const post of profile.posts({ limit: 10 })) {
  console.log(post.title);
}

Manual Control

// 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;
  }
}

Collecting Results

// 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()})`);
});

Nested Iteration

// 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`);
}

Error Handling

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);
  }
}

Type Definitions

SubstackConfig

interface SubstackConfig {
  token: string;             // substack.sid cookie value
  publicationUrl?: string;   // publication URL (optional)
}

Common Types

interface Reaction {
  id: string;
  type: string;
  author: Profile;
}

interface IteratorOptions {
  limit?: number;        // Maximum total items to retrieve
}

Best Practices

Authentication

  • 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');
}

Pagination

  • 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;
  }
}

Error Handling

  • 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);
  }
}

Performance

  • 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