Write and safely manage Substack drafts from Markdown with Python, CLI, and MCP.
Project description
Python Substack
Write Substack posts in Markdown and safely create, inspect, schedule, and publish them through Python, a command-line interface, or MCP.
[!IMPORTANT] Creating and publishing are separate operations.
substack drafts createalways creates an unpublished draft. It never schedules, sends, publishes, or deletes content.
From Markdown to a Substack draft
Install the package:
pip install python-substack
Check the selected account and publication:
substack status
Create a safe unpublished draft, then publish only when it is ready:
substack drafts create post.md
substack drafts publish 12345 --no-send
Publishing and deletion require confirmation. Noninteractive and JSON
workflows must pass --yes explicitly.
Markdown source:
Substack result:
What it supports
- Create rich Substack drafts from Markdown.
- Upload local images referenced by Markdown.
- Set audience, comment permissions, SEO metadata, slug, sections, and tags.
- List and inspect publications and drafts.
- Schedule, unschedule, publish, and delete drafts with explicit safeguards.
- Use stable JSON envelopes in scripts and automation.
- Authenticate with browser cookies or email and password.
- Use the same publishing workflow from Python or an optional MCP server.
Setup
Copy .env.example to .env and configure one authentication method:
EMAIL=
PASSWORD=
PUBLICATION_URL=
COOKIES_PATH=
COOKIES_STRING=
Cookie authentication is usually more reliable when Substack requires captcha or magic-link sign-in. See Authentication for cookie export instructions and account-selection details.
Verify the installation without authenticating:
substack --version
substack --help
CLI
Create a draft with metadata:
substack --json drafts create post.md \
--title "My Post" \
--subtitle "Optional subtitle" \
--tag python \
--tag substack \
--slug my-post \
--search-engine-title "SEO title" \
--search-engine-description "SEO description"
Inspect publications and drafts:
substack publications list
substack drafts list --limit 10
substack drafts get 12345
substack --publication-url https://example.substack.com drafts list
Manage scheduling:
substack drafts schedule 12345 --at 2026-08-01T09:00:00+03:00
substack drafts unschedule 12345
Publish or delete intentionally:
substack drafts publish 12345 --no-send
substack drafts delete 12345 --yes
Global options such as --json, --cookies, and --publication-url must
appear before the command:
substack --json drafts list
substack --cookies cookies.json --json status
The original standalone commands remain supported. See Legacy CLI commands.
Python
import os
from dotenv import load_dotenv
from substack import Api
load_dotenv()
api = Api(
email=os.getenv("EMAIL"),
password=os.getenv("PASSWORD"),
publication_url=os.getenv("PUBLICATION_URL"),
)
result = api.create_draft_from_markdown(
title="Shipping with Python",
subtitle="A short note from a script",
markdown="""
# Hello
This draft was created from **Markdown**.

""",
tags=["python", "automation"],
slug="shipping-with-python",
)
print(result["draft"]["id"])
create_draft_from_markdown creates a draft by default. It publishes only when
publish=True is passed.
For direct ProseMirror node construction, see the low-level Python API. YAML workflows are documented in YAML drafts.
Markdown
Supported Markdown includes headings, paragraphs, bold, italic, inline code, strikethrough, superscript, subscript, links, images, linked images, image captions, code blocks, blockquotes, ordered and unordered lists, horizontal rules, footnotes, LaTeX math, pull quotes, and callouts.
from substack.post import Post
post = Post("Title", "Subtitle", user_id=1)
post.from_markdown(
"""
# Heading
Paragraph with **bold**, *italic*, `code`, and [links](https://example.com).
"""
)
Pass api= to upload local images while rendering:
post.from_markdown(markdown_content, api=api)
See the complete Markdown reference.
MCP
Install and run the optional MCP server:
pip install "python-substack[mcp]"
substack-mcp
The MCP tools use the same environment variables and SDK behavior as the CLI. See MCP server for the tool list and safety notes.
Project documentation
- Authentication
- Markdown reference
- Legacy CLI commands
- Low-level Python API
- YAML drafts
- MCP server
- Compatibility policy
- Contributing
- Security policy
- Changelog
Compatibility
The project preserves existing Python APIs, console commands, CLI behavior, environment variables, JSON keys, and MCP tool signatures through the 1.x series. Additive capabilities may be introduced. See the compatibility policy.
Disclaimer
This project is not affiliated with Substack. It uses undocumented Substack interfaces that may change without notice.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python_substack-0.2.0.tar.gz.
File metadata
- Download URL: python_substack-0.2.0.tar.gz
- Upload date:
- Size: 27.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1aa5e2ede01688c2ad26b87fab95d7edb58b98b4c0f2ef010959d97191d354c5
|
|
| MD5 |
cef7b58c68a6910a26638c2e67657ffe
|
|
| BLAKE2b-256 |
dcbac4c6176c479913b33a3b7cbfd92e8fe73eea2eda6f163751ef189cd79f07
|
Provenance
The following attestation bundles were made for python_substack-0.2.0.tar.gz:
Publisher:
ci_publish.yml on ma2za/python-substack
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_substack-0.2.0.tar.gz -
Subject digest:
1aa5e2ede01688c2ad26b87fab95d7edb58b98b4c0f2ef010959d97191d354c5 - Sigstore transparency entry: 2282230307
- Sigstore integration time:
-
Permalink:
ma2za/python-substack@991ed9b1d9abf89d0144ec2cec97a46cd8ce7721 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ma2za
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci_publish.yml@991ed9b1d9abf89d0144ec2cec97a46cd8ce7721 -
Trigger Event:
release
-
Statement type:
File details
Details for the file python_substack-0.2.0-py3-none-any.whl.
File metadata
- Download URL: python_substack-0.2.0-py3-none-any.whl
- Upload date:
- Size: 28.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ddd5f3e4af77850d68f8d376447379fb8df52475d7e5b5ca375f941c53530d7a
|
|
| MD5 |
acd5b16540af9bb25c1e6bd8ec3e569d
|
|
| BLAKE2b-256 |
04333f5e6866c9ecfb9d965ae2411901a6e65e1c6aa29a41e09f8feca02759c6
|
Provenance
The following attestation bundles were made for python_substack-0.2.0-py3-none-any.whl:
Publisher:
ci_publish.yml on ma2za/python-substack
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_substack-0.2.0-py3-none-any.whl -
Subject digest:
ddd5f3e4af77850d68f8d376447379fb8df52475d7e5b5ca375f941c53530d7a - Sigstore transparency entry: 2282230382
- Sigstore integration time:
-
Permalink:
ma2za/python-substack@991ed9b1d9abf89d0144ec2cec97a46cd8ce7721 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ma2za
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci_publish.yml@991ed9b1d9abf89d0144ec2cec97a46cd8ce7721 -
Trigger Event:
release
-
Statement type: