sophiamyang/media-content-skills/substack-publishing
Claude Code and agent skill by sophiamyang/media-content-skills
- community
Checking that the SKILL.md file is still in the repository.
Use when publishing or drafting posts to Substack programmatically — creating drafts from Markdown, automating a newsletter, or debugging why a Substack script returns "Just a moment...", 403, or works locally but fails from CI or a server.
Install
npx vskill@latest install sophiamyang/media-content-skills/substack-publishingSKILL.md
Publishing to Substack programmatically
Substack has no sanctioned publishing API, and its Cloudflare protection makes automation succeed or fail based on where the request comes from rather than how it is made. That one fact explains almost every confusing failure here.
Established August 2026. Cloudflare rules and Substack's internal API both change without notice.
The source IP decides everything
Verified against the same publication, same code, same cookie:
| Origin | Result | |
|---|---|---|
| GitHub Actions runner | Cloudflare challenge on HTML and the JSON API | |
| Cloud container (e.g. an agent sandbox) | Clean auth responses — works | |
| Residential IP (a laptop) | Works |
The tell is an HTTP response whose body is <title>Just a moment...</title>, often surfacing as a confusing 403 or an "Invalid JSON" parse error from a client library.
Do not host this on GitHub Actions. It cannot work, regardless of browser versus API, and it fails on every scheduled run. Before building anywhere new, spend one curl confirming the environment is not challenged:
Never attempt to defeat the challenge, and never disable certificate verification to get past a related TLS error.
The official API does not publish
Substack shipped a Developer API in 2026, but it covers profile search, not post creation. There is no supported publishing endpoint. Everything below is unofficial and may break.
Authenticate with a session cookie
Log into Substack in a browser, then copy the substack.sid cookie (DevTools → Application → Cookies → https://substack.com).
Treat it as a full-access credential — it can publish, change settings, and read your subscriber list. Store it in an environment variable or secret store, never in code, logs, or a repository. It expires without warning on logout, password change, or "sign out of all devices", so build for re-export rather than assuming permanence.
Drafting over the API
Substack stores post bodies as ProseMirror JSON, not Markdown. Do not hand-roll that encoding — the python-substack package handles the conversion:
Gotchas worth knowing in advance, each of which cost a failed run:
- Argument order.
create_draft_from_markdowntakes(title, markdown, ...). Passing the body positionally puts the entire post in the title field, and the call still succeeds. Pass everything by keyword. - The returned draft is nested. The result carries the draft under a
draftkey alongside tag and prepublish results, not as a top-levelid. - Module shadowing, and it is subtler than it looks. The package imports as
substack. If your own project has asubstack.pyonsys.path— likely, given the domain — yours wins and the import silently resolves to the wrong module. The fix is to drop your script's directory fromsys.pathand evict any cachedsubstack*modules before importing the library.
The trap: it is not enough to do that around the import and restore immediately. The library defers from substack.post import Post and from substack import mdrender until draft-creation time, and those resolve through sys.modules when they run — so restoring your module too early makes draft creation fail. Hold the swap across every call on the Api, e.g. with a context manager, and restore on exit so later imports elsewhere still get your module.
- Title and subtitle are separate fields. Strip the leading
# Headingout of the Markdown body or it appears twice in the published post. - Pin the version. The API surface changed across releases; pin to the minor you tested (
python-substack>=0.6,<0.7at time of writing) rather than an open floor.
Runnable reference
create_draft.py in this folder implements the above, standalone — no project imports, configured by argument and environment variable.
Needs python-substack>=0.6,<0.7. It creates a draft, never publishes, and detects the Cloudflare challenge to tell you the IP is the problem rather than failing obscurely.
Draft, do not publish
Default to creating a draft and letting a human review and publish. The body encoding is undocumented and can render subtly wrong — literal asterisks, an empty body, truncation — and a malformed post reaching real subscribers cannot be recalled. Prove the pipeline over several editions before considering direct publishing.
Browser automation, and when it applies
Driving the editor with Playwright works from a residential IP and is a reasonable local path. It does not work from challenged IPs, and it carries a second trap: scripts written for interactive use tend to contain input() fallbacks ("click the title field and paste this"). Unattended, those hang or die on EOFError. Route every such prompt through a helper that fails loudly with the step name when no terminal is attached.
When a headless run fails, capture page.url, page.title(), a screenshot, and the page HTML. Without them you cannot distinguish an expired session from a stale selector from a Cloudflare wall — they all present as "the field wasn't found".
Always keep a manual path
Whatever automation you build, make failure end with the post reaching a human — emailed, or saved as an artifact. Publishing is a two-minute paste; a pipeline that fails silently costs an entire edition.
Security
- Security scanPassed, 90 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/substack-publishing/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/sophiamyang/media-content-skills/substack-publishing)