A fully functional Python-based Substack API client that enables programmatic creation, management, and publishing of Substack posts with rich formatting support. Available as both command-line tools and HTTP REST API.
- Draft Creation: Create drafts with rich formatting using simple markup syntax
- Draft Publishing: Interactive publishing with email/audience options
- Rich Content: All Substack content types supported (headings, lists, quotes, buttons, etc.)
- User-Friendly Markup: Write formatted content using simple syntax
- Environment Management: Easy credential setup and management
- π HTTP API: REST API server for integration with any programming language
- π Web Interface: Interactive API documentation and testing
- Scheduling: Cannot create new schedules via API (can only update existing ones)
- Media Upload: Images/audio/video require separate upload process
# Install dependencies
pip install -r requirements.txt
# Configure credentials
python change_env.py
# Start API server
python api_server.py
Server URL: http://localhost:8000
Interactive Docs: http://localhost:8000/docs
# Install requirements
pip install requests python-dotenv
# Configure credentials
python change_env.py
You'll need these credentials from your Substack account:
- Publication URL: Your Substack homepage (e.g.,
https://yourname.substack.com
) - User ID: Your Substack username
- Authentication Cookies: Extract from browser (see guide below)
- Login to your Substack account in browser
- Open Developer Tools (F12)
- Go to Application/Storage β Cookies β https://substack.com
- Find and copy these cookie values:
sid
substack.sid
substack.lli
API Method (Recommended):
# Create draft via HTTP API
curl -X POST "http://localhost:8000/drafts/create-markup" \
-H "Content-Type: application/json" \
-d '{
"title": "My First Post",
"markup_content": "Title:: Hello World | Text:: This is **bold** content | Subscribe:: Join Newsletter"
}'
Command Line Method:
python draft_create.py
Choose from these options:
- Simple text draft - Basic text content
- Markup draft - Rich formatted content (reads from
sampleinput/2.txt
) - Test draft - Comprehensive example with all content types
- Rich formatting - Basic formatting example
Create rich Substack content using simple markup:
Title:: Your Main Heading | Text:: Your content with **bold** and *italic* | Subscribe:: Join Newsletter
Title::
- Main heading (H1)Subtitle::
- Secondary heading (H2)H1:: - H6::
- Custom heading levelsText::
- Paragraph with inline formattingQuote::
- Block quotePullQuote::
- Emphasized quoteList::
- Bullet list (β’ separated)NumberList::
- Numbered list (1. 2. 3.)Code::
- Code block (language | code
)Rule::
- Horizontal dividerButton::
- Custom button (text -> url
)Subscribe::
- Subscribe buttonShare::
- Share buttonComment::
- Comment buttonSubscribeWidget::
- Subscribe with description (button >> description
)LaTeX::
- Mathematical equationsBreak::
- Empty paragraph/line break
Within Text::
blocks:
**bold**
- Bold text*italic*
- Italic text~~strikethrough~~
- Strikethrough text`code`
- Inline code[link text](url)
- Links
Title:: Market Analysis Report | Text:: Our analysis shows **strong growth** in tech stocks. Key findings: | List:: β’ Revenue up 25% β’ User growth at 40% β’ New product launches | Quote:: This represents the strongest quarter in company history | Subscribe:: Get Weekly Reports
API Method:
# List drafts
curl "http://localhost:8000/drafts"
# Publish specific draft
curl -X POST "http://localhost:8000/drafts/123456/publish" \
-H "Content-Type: application/json" \
-d '{"send_email": true, "audience": "everyone"}'
Command Line Method:
python draft_publish.py
- Lists all unpublished drafts with previews
- Choose draft and publishing options
- Publishes immediately with confirmation
substack-api/
βββ api_server.py # π HTTP REST API server
βββ requirements.txt # π API dependencies
βββ API_GUIDE.md # π Complete API documentation
βββ draft_create.py # Create drafts with markup support
βββ draft_publish.py # Publish existing drafts
βββ change_env.py # Manage environment credentials
βββ getposts.py # Analyze all posts and drafts
βββ sampleinput/2.txt # Sample markup content
βββ docs/ # Documentation and guides
βββ .env # Your credentials (create this)
βββ README.md # This file
Perfect for:
- Web applications
- Mobile app backends
- CI/CD pipelines
- Third-party integrations
- Remote automation
Perfect for:
- Local automation
- Quick testing
- One-off posts
- Development workflow
Authentication Errors
- Refresh your browser cookies - they expire regularly
- Run
python change_env.py
to update credentials
Draft Creation Fails
- Ensure you have at least one unpublished draft (create manually first)
- Check that your
.env
file has all required values
Content Not Displaying
- Fixed: Was caused by empty text elements in markup parser
- Current version filters out problematic empty elements
Analyze existing posts:
python getposts.py
View markup examples:
python docs/markup_examples.py
import requests
# Create draft via API
response = requests.post("http://localhost:8000/drafts/create-markup", json={
"title": "My Post",
"markup_content": "Title:: Hello World | Text:: This is **bold** content",
"subtitle": "Optional subtitle"
})
draft = response.json()
print(f"Created draft {draft['draft_id']}")
# Publish draft
requests.post(f"http://localhost:8000/drafts/{draft['draft_id']}/publish", json={
"send_email": True,
"audience": "everyone"
})
from draft_create import create_markup_draft
# Create draft from markup
draft = create_markup_draft(
title="My Post",
markup_content="Title:: Hello World | Text:: This is **bold** content",
subtitle="Optional subtitle"
)
Required in .env
file:
PUBLICATION_URL=https://yourname.substack.com
USER_ID=your_username
SID=cookie_value
SUBSTACK_SID=cookie_value
SUBSTACK_LLI=cookie_value
The API client works by:
- Using session-based authentication with browser cookies
- Copying structure from existing unpublished drafts
- Converting markup syntax to Substack's JSON content format
- Making authenticated requests to Substack's internal API endpoints
Key technical notes:
- Empty text elements break Substack's parser (now filtered out)
- Byline IDs must match user IDs for draft creation
- Content uses hierarchical JSON structure with
type
,attrs
,content
,marks
For issues or questions:
- Check the troubleshooting section above
- Review
docs/
folder for detailed technical guides - Ensure your authentication cookies are current
Note: This tool uses Substack's internal API endpoints. While functional, it may break if Substack changes their API structure.