{"slug": "show-hn-substack-skill-api-agent-reference", "title": "Show HN: Substack Skill API / Agent Reference", "summary": "A developer released a comprehensive, verified reference for Substack's undocumented internal API, mapping 129 endpoints through 14 rounds of probing. The open-source repository includes an OpenAPI 3.1 spec, authentication guide, and client SDK generators, enabling AI agents and developers to interact with Substack's JSON API for reading posts, creating drafts, and managing publications.", "body_md": "The most complete map of Substack's undocumented internal API.\n\n129 verified endpoints, captured by driving the live API through 14 rounds of probing and write-side capture.\n\nA practical, verified reference for Substack's undocumented internal API. Every endpoint here has been tested against the live API. Designed for humans **and** for AI agents (see [ SKILL.md](/AnthonyDavidAdams/substack-api-reference/blob/main/SKILL.md)).\n\n⚠️ Unofficial.Substack doesn't publish or support this API. Endpoints can change without notice. Treat this as a working notebook, not a contract.\n\nThe Substack web app speaks to a JSON API at `https://substack.com/api/v1/*`\n\nand per-publication subdomains at `https://<sub>.substack.com/api/v1/*`\n\n. The web app uses it for everything: reading posts, creating drafts, publishing, managing subscribers, sending chat threads, configuring recommendations. With a session cookie, you can drive the same API from any script.\n\nExisting community work covers parts of this surface:\n\n[NHagar/substack_api](https://github.com/NHagar/substack_api)— Python, read-focused[ma2za/python-substack](https://github.com/ma2za/python-substack)— Python, full CRUD[jakub-k-slys/substack-api](https://github.com/jakub-k-slys/substack-api)— TypeScript (archived)[JPres-Projects/Substack-API](https://github.com/JPres-Projects/Substack-API)— Python, draft + publish\n\nThis repo is intended to be **the canonical endpoint reference** these clients converge on. Submit a PR with anything new you find.\n\n```\n# 1. Get your session cookie (see AUTH.md for browser-extension and DevTools paths)\nCOOKIE='s%3A...your.connect.sid.value...'\n\n# 2. Verify it's good — returns your profile + every publication you can edit\ncurl -H \"Cookie: connect.sid=$COOKIE; substack.sid=$COOKIE\" \\\n  https://substack.com/api/v1/user/profile/self | jq .handle\n\n# 3. Create a draft on a publication you admin\ncurl -X POST \\\n  -H \"Cookie: connect.sid=$COOKIE; substack.sid=$COOKIE\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"draft_title\":\"Hello\",\"draft_subtitle\":\"From the API\",\"draft_body\":\"<p>Hi.</p>\",\"type\":\"newsletter\"}' \\\n  https://yourname.substack.com/api/v1/drafts\n```\n\n— every verified endpoint with body shapes, query params, and sample responses`ENDPOINTS.md`\n\n—`openapi.yaml`\n\n**OpenAPI 3.1 spec**(125 operations, 51 schemas) — scaffold a typed client in any language— getting the cookie, format, rotation, sending it from server-side code`AUTH.md`\n\n— Claude Agent SDK skill manifest`SKILL.md`\n\n— drop-in curl scripts`examples/curl/`\n\n— minimal typed client`examples/typescript/`\n\nDrop-in commands for popular generators:\n\n```\n# TypeScript fetch client\nnpx openapi-typescript openapi.yaml -o substack-types.ts\n\n# TypeScript axios/full SDK\nnpx @openapitools/openapi-generator-cli generate -i openapi.yaml -g typescript-axios -o ./sdk-ts\n\n# Python SDK\nnpx @openapitools/openapi-generator-cli generate -i openapi.yaml -g python -o ./sdk-py\n\n# Go SDK\nnpx @openapitools/openapi-generator-cli generate -i openapi.yaml -g go -o ./sdk-go\n```\n\nSubstack's endpoints split across two host families (account + per-pub) and the spec models both as `servers`\n\n— set `subdomain`\n\nwhen configuring the client.\n\n**Read side (passive — works with just a cookie):**\n\n- Authentication, account discovery, public profile, blocked users\n- Publication CRUD (read), settings, sections, tags, recommendations, Stripe status, pledge tiers\n- Drafts, scheduled posts, published posts, post management counts\n- Per-post stats (31 engagement fields including open rate, CTR, daily breakdowns, referrers)\n- Publication-wide analytics (subscribers, ARR, network attribution, growth sources/events timeseries)\n- Subscribers list (filter/sort/paginate), DMs/messages inbox, activity feed\n- Reader inbox (\n`/inbox/top`\n\n), Notes feed + tabs, global search, search modules - Categories, reactions catalog, comment moderation enum, per-post mute settings\n\n**Write side (captured by driving the UI through Chrome):**\n\n`PUT /publication`\n\n— single-field saves (`name`\n\n,`hero_text`\n\n,`language`\n\n,`welcome_email_content`\n\n, …)`PUT /publication_settings`\n\n— boolean toggles (high-res video, AI training opt-out, cross-posting, etc.)- Drafts: create, update, delete, publish, schedule, scheduled_release\n- Notes: create (\n`POST /comment/feed`\n\n), delete, mark-seen - Reader comments: create, delete;\n**post reactions**(literal emoji +`surface`\n\n) **Recommendations**: add (`PUT /recommendations`\n\n), remove (`DELETE /recommendations/`\n\n— note trailing slash), search, suggested**Audio/podcast upload**— full S3 presigned multipart sequence (init → S3 PUT → transcode → poll)** Substack Chat**: enable/disable (`/publication_threads_settings`\n\n), send thread (client-generated UUID), delete thread- Co-author invites, subscriber add/remove, post-tag attach/detach, image upload\n- Generic user setting (\n`PUT /user-setting`\n\n)\n\n**Known gates (documented but won't be cracked here):**\n\n`POST /publication`\n\n— captcha-gated by design- Custom domain config — $50 one-time + DNS setup\n- Magic-link verify — at\n`/sign-in?token=...`\n\n, not under`/api/v1/*`\n\n- Moderator-delete-with-reason — needs another user's comment to surface\n\nEach endpoint in [ ENDPOINTS.md](/AnthonyDavidAdams/substack-api-reference/blob/main/ENDPOINTS.md) is marked:\n\n- ✅\n**Verified**— personally tested against the live API - 🟡\n**Reported**— documented by another client / blog post, not independently re-tested - ❓\n**Inferred**— pattern-matched from related endpoints, no successful call observed - ❌\n**Dead**— tested and confirmed 404, documented to save you the time - 🔒\n**Gated**— exists but returns 403 from a plain curl (often works from a real browser; see the \"two-host trick\" and \"browser-vs-curl gap\" notes in`ENDPOINTS.md`\n\n)\n\nPRs welcome to upgrade ❓ → 🟡 → ✅.\n\nThis reference was built over 14 progressive rounds of capture, documented as `Round N`\n\ncommits. The methodology stack:\n\n**Curl probing** for read endpoints + empirical-error field discovery (sending intentionally-invalid POSTs to surface required fields via Substack's error messages — that's how`trigger_at`\n\nwas found, and dozens of others)**Playwright headless capture**— driving Chromium with a session cookie through the admin SPA to log every`api/v1`\n\nrequest that fires**Chrome extension live capture**([Claude in Chrome](https://chromewebstore.google.com/detail/claude/fcoeoabgfenejglbffodgkkbkcdhcgfn)) — monkey-patching`window.fetch`\n\nand`XMLHttpRequest.send`\n\nfrom a`localStorage`\n\n-backed log so request bodies survive React-driven page reloads, then doing add-then-revert cycles on real publication actions to capture the write-side bodies that couldn't be reached passively\n\nWhy all three: passive observation gets you read endpoints and a lot of POST bodies \"for free.\" Empirical probing fills the gaps where errors are descriptive (Substack's are). Live driving the UI catches the cases where the body shape is non-obvious (the literal-emoji reaction value, the client-generated UUID for thread sends, the stringified-ProseMirror welcome email content) and where endpoints 403 from curl but 200 from a real browser session (recommendations was the canonical example).\n\nFound a new endpoint? Test with curl, then PR with:\n\n- Method + path\n- Required host (\n`substack.com`\n\nvs`{sub}.substack.com`\n\n) - Required headers / query params\n- Sample response (sanitize user data)\n- Classification (✅ / 🟡 / ❓)\n- Date you verified it\n\nCapture playbook for body shapes you can't get from curl:\n\n- Open Substack in Chrome → DevTools → Network → filter to\n`Fetch/XHR`\n\n- Perform the action in the UI\n- The matching request appears in Network → right-click →\n**Copy as cURL** - Strip the cookie + headers down to the minimum that still works\n- PR the result here\n\nThis reference was assembled by [Anthony David Adams](https://substack.com/@anthonydavidadams) and the [ EarthPilot.ai Lab](https://earthpilot.ai) — a small applied-AI lab building Mission Support for Spaceship Earth: tools and protocols for sense-making, decision-making, and coordination at planetary scale. Working on Substack-adjacent infrastructure (newsletter platforms, AI editorial pipelines, growth tooling) pushed us to map this surface; sharing it back is the natural thing to do.\n\nIf you're working at the edge of AI × meaning × infrastructure — or you want to be — come hang out at the ** Singularity Playground**. It's our open community for builders, researchers, and writers working on what comes next. Free to join, no pitch deck required.\n\nMIT. See [ LICENSE](/AnthonyDavidAdams/substack-api-reference/blob/main/LICENSE).", "url": "https://wpnews.pro/news/show-hn-substack-skill-api-agent-reference", "canonical_source": "https://github.com/AnthonyDavidAdams/substack-api-reference", "published_at": "2026-06-24 14:49:26+00:00", "updated_at": "2026-06-24 15:11:21.378509+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["Substack", "AnthonyDavidAdams", "Claude Agent SDK", "OpenAPI", "TypeScript", "Python", "Go"], "alternates": {"html": "https://wpnews.pro/news/show-hn-substack-skill-api-agent-reference", "markdown": "https://wpnews.pro/news/show-hn-substack-skill-api-agent-reference.md", "text": "https://wpnews.pro/news/show-hn-substack-skill-api-agent-reference.txt", "jsonld": "https://wpnews.pro/news/show-hn-substack-skill-api-agent-reference.jsonld"}}