"# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Create note\nPublish a Substack Note (feed comment). Optional link attachment is created first, then the note is posted.\n\nPython: create_note()\nCLI: create-note\nMCP: create_note\nRate limit: 1 request per second\n\n# Create note — Request\nPublish a Substack Note (feed comment). Optional link attachment is created first, then the note is posted.\n\nPOST (+ optional POST) https://substack.com/api/v1/comment/attachment (optional) → https://substack.com/api/v1/comment/feed\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): https://substack.com/ (same as get_profile_self)\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n- Content-Type (string, required): application/json\n\nPath params: None\n\nQuery params: None\n\nBody:\n- bodyJson (object, required): ProseMirror doc object. SDK reuses ensure_draft_body() / ensureDraftBody(), then sets attrs.schemaVersion=v1.\n- attachmentIds (string[], optional): Ids from POST /comment/attachment. SDK fills this when attachment_url is passed.\n- tabId (string, required): Captured default: \"for-you\"\n- surface (string, required): Captured default: \"permalink\"\n- replyMinimumRole (string, required): Captured default: \"everyone\"\n- (attachment) (object, optional): Optional first call POST /comment/attachment: { \"url\": string, \"type\": \"link\" }\n\n# Create note — Response\nType: object\n\nFields:\n- id (number, required): Note / feed comment id\n- type (string, required): Captured value: \"feed\"\n- status (string, required): Captured value: \"published\"\n- body (string, optional): Plain-text body\n- body_json (object, optional): ProseMirror bodyJson echo\n- attachments (array, optional): Attached posts/links when attachment_url was used\n\n# Create note — Python example\nMethod: create_note()\n\nnote = client.create_note(\n body=\"nice\",\n attachment_url=\"https://yourname.substack.com/p/your-post\",\n)\nprint(note[\"id\"], note[\"status\"])\n\n# Create note — CLI example\nCommand: create-note\n\nsubstack-api create-note \\\n --body \"nice\" \\\n --attachment-url \"https://yourname.substack.com/p/your-post\""
Create note create_note() CLI: create-note MCP: create_note
Request Copy for LLM POST https://substack.com/api/v1/comment/attachment (optional) → https://substack.com/api/v1/comment/feed
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes https://substack.com/ (same as get_profile_self) User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default) Content-Type string yes application/json
Body Name Type Required Description bodyJson object yes ProseMirror doc object. SDK reuses ensure_draft_body() / ensureDraftBody(), then sets attrs.schemaVersion=v1. attachmentIds string[] no Ids from POST /comment/attachment. SDK fills this when attachment_url is passed. tabId string yes Captured default: "for-you" surface string yes Captured default: "permalink" replyMinimumRole string yes Captured default: "everyone" (attachment) object no Optional first call POST /comment/attachment: { "url": string, "type": "link" }
Response Copy for LLM object
Fields Name Type Required Description id number yes Note / feed comment id type string yes Captured value: "feed" status string yes Captured value: "published" body string no Plain-text body body_json object no ProseMirror bodyJson echo attachments array no Attached posts/links when attachment_url was used
Python example Copy for LLM note = client.create_note(
body="nice",
attachment_url="https://yourname.substack.com/p/your-post",
)
print(note["id"], note["status"])
CLI example Copy for LLM substack-api create-note \
--body "nice" \
--attachment-url "https://yourname.substack.com/p/your-post""# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Delete note\nDelete an existing Substack Note (feed comment) by id.\n\nPython: delete_note()\nCLI: delete-note\nMCP: delete_note\nRate limit: 1 request per second\n\n# Delete note — Request\nDelete an existing Substack Note (feed comment) by id.\n\nDELETE https://substack.com/api/v1/comment/{id}\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): https://substack.com/ (same as get_profile_self)\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n\nPath params:\n- id (number | string, required): Note / feed comment id in the URL path\n\nQuery params: None\n\nBody: No request body\n\n# Delete note — Response\nType: object (normalized by SDK)\n\nFields:\n- status (\"deleted\", required): SDK-normalized status\n- note_id (number | string, required): Deleted note id\n- response (object, required): Raw Substack body (may be empty {})\n\n# Delete note — Python example\nMethod: delete_note()\n\nresult = client.delete_note(123456)\nprint(result[\"status\"])\n\n# Delete note — CLI example\nCommand: delete-note\n\nsubstack-api delete-note --note-id 123456"
Delete note delete_note() CLI: delete-note MCP: delete_note
Request Copy for LLM DELETE https://substack.com/api/v1/comment/{id}
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes https://substack.com/ (same as get_profile_self) User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default)
Path params Name Type Required Description id number | string yes Note / feed comment id in the URL path
Response Copy for LLM object (normalized by SDK)
Fields Name Type Required Description status "deleted" yes SDK-normalized status note_id number | string yes Deleted note id response object yes Raw Substack body (may be empty {})
Python example Copy for LLM result = client.delete_note(123456)
print(result["status"])
CLI example Copy for LLM substack-api delete-note --note-id 123456"# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Create draft / publish / schedule\nHigh-level SDK flow: create draft → update → optional tags → publish or schedule.\n\nPython: create_post()\nCLI: create\nMCP: create_post\nRate limit: 1 request per second\n\n# Create draft / publish / schedule — Request\nHigh-level SDK flow: create draft → update → optional tags → publish or schedule.\n\nPOST + PUT (+ optional) {publication}/api/v1/drafts → {publication}/api/v1/drafts/{id} → tags / publish / schedule\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): Usually {publication}/publish or the draft editor URL\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n- Content-Type (string, required): application/json\n\nPath params: None\n\nQuery params: None\n\nBody:\n- title (string, required): Post title (SDK maps to draft_title on Substack)\n- body (string | object, required): Plain text or ProseMirror doc. Client converts plain text to draft_body JSON\n- subtitle (string, optional): Optional subtitle (default \"\")\n- audience (string, optional): Who can read the post (default \"everyone\")\n- section_id (number | null, optional): Optional publication section id\n- should_send_email (boolean, optional): Email subscribers on publish (default true)\n- schedule_at (string | null, optional): ISO-8601 UTC datetime. If set, schedules instead of publishing\n- publish (boolean, optional): If true and schedule_at is null → publish now. CLI --draft-only / MCP draft_only=true sets this false\n- tags (string[], optional): Tag names to ensure and attach after draft update\n\n# Create draft / publish / schedule — Response\nType: object\n\nFields:\n- draft_id (number | string, required): Created draft id\n- draft (object, required): Latest draft object from update_draft\n- publication_url (string, required): Normalized publication origin\n- edit_url (string, required): {publication}/publish/post/{draft_id}\n- status (\"draft\" | \"published\" | \"scheduled\", optional): Outcome of the flow\n- post_url (string, optional): Public post URL when published\n- tags (array, optional): Attach results when tags were requested\n\n# Create draft / publish / schedule — Python example\nMethod: create_post()\n\nresult = client.create_post(\n title=\"Hello from API\",\n body=\"Plain text becomes ProseMirror draft_body.\",\n subtitle=\"Optional\",\n tags=[\"api-test\"],\n publish=True,\n)\nprint(result[\"draft_id\"], result.get(\"post_url\"))\n\n# Create draft / publish / schedule — CLI example\nCommand: create\n\nsubstack-api create \\\n --title \"Hello from API\" \\\n --body \"Plain text body\" \\\n --tags \"api-test,newsletter\""
Create draft / publish / schedule create_post() CLI: create MCP: create_post
Request Copy for LLM POST UPDATE {publication}/api/v1/drafts → {publication}/api/v1/drafts/{id} → tags / publish / schedule
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes Usually {publication}/publish or the draft editor URL User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default) Content-Type string yes application/json
Body Name Type Required Description title string yes Post title (SDK maps to draft_title on Substack) body string | object yes Plain text or ProseMirror doc. Client converts plain text to draft_body JSON subtitle string no Optional subtitle (default "") audience string no Who can read the post (default "everyone") section_id number | null no Optional publication section id should_send_email boolean no Email subscribers on publish (default true) schedule_at string | null no ISO-8601 UTC datetime. If set, schedules instead of publishing publish boolean no If true and schedule_at is null → publish now. CLI --draft-only / MCP draft_only=true sets this false tags string[] no Tag names to ensure and attach after draft update
Response Copy for LLM object
Fields Name Type Required Description draft_id number | string yes Created draft id draft object yes Latest draft object from update_draft publication_url string yes Normalized publication origin edit_url string yes {publication}/publish/post/{draft_id} status "draft" | "published" | "scheduled" no Outcome of the flow post_url string no Public post URL when published tags array no Attach results when tags were requested
Python example Copy for LLM result = client.create_post(
title="Hello from API",
body="Plain text becomes ProseMirror draft_body.",
subtitle="Optional",
tags=["api-test"],
publish=True,
)
print(result["draft_id"], result.get("post_url"))
CLI example Copy for LLM substack-api create \
--title "Hello from API" \
--body "Plain text body" \
--tags "api-test,newsletter""# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Create draft (low-level)\nCreate an empty/partial draft without publishing.\n\nPython: create_draft()\nCLI: create --draft-only\nMCP: create_post (draft_only=true)\nRate limit: 1 request per second\n\n# Create draft (low-level) — Request\nCreate an empty/partial draft without publishing.\n\nPOST {publication}/api/v1/drafts\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): Usually {publication}/publish or the draft editor URL\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n- Content-Type (string, required): application/json\n\nPath params: None\n\nQuery params: None\n\nBody:\n- draft_title (string, required): Title (SDK arg: title)\n- draft_subtitle (string, required): Subtitle (SDK arg: subtitle)\n- draft_body (string, required): ProseMirror JSON string (SDK accepts plain text or object)\n- audience (string, required): Default \"everyone\"\n- type (string, required): Post type, default \"newsletter\"\n- draft_bylines (array, required): [{ \"id\": <user_id>, \"is_guest\": false }]\n- draft_section_id (number | null, optional): Section id when section_chosen is true\n- section_chosen (boolean, required): Whether a section was selected\n- detect_language (boolean, required): Language detection flag (default true on create)\n- translations (array, required): Usually []\n- draft_podcast_url (null, required): Sent as null for newsletter posts\n- draft_podcast_duration (null, required): Sent as null for newsletter posts\n\n# Create draft (low-level) — Response\nType: object\n\nFields:\n- id (number | string, required): New draft id\n- publication_id (number | string, optional): Publication id\n- draft_updated_at (string, required): Timestamp required for later PUT update_draft\n\n# Create draft (low-level) — Python example\nMethod: create_draft()\n\ndraft = client.create_draft(\n title=\"Draft only\",\n subtitle=\"\",\n body=\"Hello\",\n audience=\"everyone\",\n)\nprint(draft[\"id\"], draft[\"draft_updated_at\"])\n\n# Create draft (low-level) — CLI example\nCommand: create --draft-only\n\nsubstack-api create \\\n --title \"Draft only\" \\\n --body \"Hello\" \\\n --draft-only"
Create draft (low-level) create_draft() CLI: create --draft-only MCP: create_post (draft_only=true)
Request Copy for LLM POST {publication}/api/v1/drafts
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes Usually {publication}/publish or the draft editor URL User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default) Content-Type string yes application/json
Body Name Type Required Description draft_title string yes Title (SDK arg: title) draft_subtitle string yes Subtitle (SDK arg: subtitle) draft_body string yes ProseMirror JSON string (SDK accepts plain text or object) audience string yes Default "everyone" type string yes Post type, default "newsletter" draft_bylines array yes [{ "id": <user_id>, "is_guest": false }] draft_section_id number | null no Section id when section_chosen is true section_chosen boolean yes Whether a section was selected detect_language boolean yes Language detection flag (default true on create) translations array yes Usually [] draft_podcast_url null yes Sent as null for newsletter posts draft_podcast_duration null yes Sent as null for newsletter posts
Response Copy for LLM object
Fields Name Type Required Description id number | string yes New draft id publication_id number | string no Publication id draft_updated_at string yes Timestamp required for later PUT update_draft
Python example Copy for LLM draft = client.create_draft(
title="Draft only",
subtitle="",
body="Hello",
audience="everyone",
)
print(draft["id"], draft["draft_updated_at"])
CLI example Copy for LLM substack-api create \
--title "Draft only" \
--body "Hello" \
--draft-only"# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Delete draft\nDelete an existing draft.\n\nPython: delete_draft()\nCLI: delete-draft\nMCP: delete_draft\nRate limit: 1 request per second\n\n# Delete draft — Request\nDelete an existing draft.\n\nDELETE {publication}/api/v1/drafts/{id}\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): Usually {publication}/publish or the draft editor URL\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n\nPath params:\n- id (number | string, required): Draft id in the URL path\n\nQuery params: None\n\nBody: No request body\n\n# Delete draft — Response\nType: object (normalized by SDK)\n\nFields:\n- status (\"deleted\", required): SDK-normalized status\n- draft_id (number | string, required): Deleted draft id\n- response (object, required): Raw Substack body (may be empty {})\n\n# Delete draft — Python example\nMethod: delete_draft()\n\nresult = client.delete_draft(123456)\nprint(result[\"status\"])\n\n# Delete draft — CLI example\nCommand: delete-draft\n\nsubstack-api delete-draft --draft-id 123456"
Delete draft delete_draft() CLI: delete-draft MCP: delete_draft
Request Copy for LLM DELETE {publication}/api/v1/drafts/{id}
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes Usually {publication}/publish or the draft editor URL User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default)
Path params Name Type Required Description id number | string yes Draft id in the URL path
Response Copy for LLM object (normalized by SDK)
Fields Name Type Required Description status "deleted" yes SDK-normalized status draft_id number | string yes Deleted draft id response object yes Raw Substack body (may be empty {})
Python example Copy for LLM result = client.delete_draft(123456)
print(result["status"])
CLI example Copy for LLM substack-api delete-draft --draft-id 123456"# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Pangram AI / AI-slop check\nRun Substack's Pangram AI-text detection on a draft body (human / AI-assisted / AI fractions).\n\nPython: pangram_detection()\nCLI: pangram-detection\nMCP: pangram_detection\nRate limit: 1 request per second\n\n# Pangram AI / AI-slop check — Request\nRun Substack's Pangram AI-text detection on a draft body (human / AI-assisted / AI fractions).\n\nGET {publication}/api/v1/drafts/{id}/pangram_detection\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): Usually {publication}/publish or the draft editor URL\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n\nPath params:\n- id (number | string, required): Draft id in the URL path\n\nQuery params: None\n\nBody: No request body\n\n# Pangram AI / AI-slop check — Response\nType: object\n\nFields:\n- type (string, optional): Detection result type from Substack / Pangram\n- header (string, optional): Human-readable summary header\n- fraction_ai (number, optional): Estimated AI-generated fraction\n- fraction_ai_assisted (number, optional): Estimated AI-assisted fraction\n- fraction_human (number, optional): Estimated human-written fraction\n- details (any, optional): Additional Pangram detail payload when present\n- disclosure (any, optional): Disclosure / availability metadata when present\n\n# Pangram AI / AI-slop check — Python example\nMethod: pangram_detection()\n\nresult = client.pangram_detection(123456)\nprint(result.get(\"header\"), result.get(\"fraction_ai\"), result.get(\"fraction_human\"))\n\n# Pangram AI / AI-slop check — CLI example\nCommand: pangram-detection\n\nsubstack-api pangram-detection --draft-id 123456"
Pangram AI / AI-slop check pangram_detection() CLI: pangram-detection MCP: pangram_detection
Request Copy for LLM GET {publication}/api/v1/drafts/{id}/pangram_detection
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes Usually {publication}/publish or the draft editor URL User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default)
Path params Name Type Required Description id number | string yes Draft id in the URL path
Response Copy for LLM object
Fields Name Type Required Description type string no Detection result type from Substack / Pangram header string no Human-readable summary header fraction_ai number no Estimated AI-generated fraction fraction_ai_assisted number no Estimated AI-assisted fraction fraction_human number no Estimated human-written fraction details any no Additional Pangram detail payload when present disclosure any no Disclosure / availability metadata when present
Python example Copy for LLM result = client.pangram_detection(123456)
print(result.get("header"), result.get("fraction_ai"), result.get("fraction_human"))
CLI example Copy for LLM substack-api pangram-detection --draft-id 123456"# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Verify auth / profile\nTest session cookie and resolve current user profile.\n\nPython: get_profile_self()\nCLI: profile\nMCP: test_connection\nRate limit: 1 request per second\n\n# Verify auth / profile — Request\nTest session cookie and resolve current user profile.\n\nGET https://substack.com/api/v1/user/profile/self\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): https://substack.com/ (global profile endpoint)\n- Referer (string, required): https://substack.com/ (global profile endpoint)\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n\nPath params: None\n\nQuery params: None\n\nBody: No request body\n\n# Verify auth / profile — Response\nType: object\n\nFields:\n- id (number, required): Substack user id (used in draft_bylines)\n- ... (any, optional): Additional profile fields from Substack\n\n# Verify auth / profile — Python example\nMethod: get_profile_self()\n\nfrom substack_api_client import SubstackClient, SubstackAuth\n\nclient = SubstackClient(\n publication_url=\"https://yourname.substack.com\",\n auth=SubstackAuth(sid=\"YOUR_SUBSTACK_SID\"),\n)\nprofile = client.get_profile_self()\nprint(profile[\"id\"])\n\n# Verify auth / profile — CLI example\nCommand: profile\n\nsubstack-api profile \\\n --publication-url \"https://yourname.substack.com\" \\\n --sid \"$SUBSTACK_SID\""
Verify auth / profile get_profile_self() CLI: profile MCP: test_connection
Request Copy for LLM GET https://substack.com/api/v1/user/profile/self
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes https://substack.com/ (global profile endpoint) Referer string yes https://substack.com/ (global profile endpoint) User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default)
Response Copy for LLM object
Fields Name Type Required Description id number yes Substack user id (used in draft_bylines) ... any no Additional profile fields from Substack
Python example Copy for LLM from substack_api_client import SubstackClient, SubstackAuth
client = SubstackClient(
publication_url="https://yourname.substack.com",
auth=SubstackAuth(sid="YOUR_SUBSTACK_SID"),
)
profile = client.get_profile_self()
print(profile["id"])
CLI example Copy for LLM substack-api profile \
--publication-url "https://yourname.substack.com" \
--sid "$SUBSTACK_SID""# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Get draft\nFetch an existing draft by id.\n\nPython: get_draft()\nCLI: get-draft\nMCP: get_draft\nRate limit: 1 request per second\n\n# Get draft — Request\nFetch an existing draft by id.\n\nGET {publication}/api/v1/drafts/{id}\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): Usually {publication}/publish or the draft editor URL\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n\nPath params:\n- id (number | string, required): Draft id in the URL path\n\nQuery params: None\n\nBody: No request body\n\n# Get draft — Response\nType: object\n\nFields:\n- id (number | string, required): Draft id\n- draft_title (string, optional): Title\n- draft_subtitle (string, optional): Subtitle\n- draft_body (string, optional): ProseMirror JSON string\n- draft_updated_at (string, optional): Optimistic-concurrency stamp for PUT\n\n# Get draft — Python example\nMethod: get_draft()\n\ndraft = client.get_draft(123456)\nprint(draft[\"draft_title\"], draft[\"draft_updated_at\"])\n\n# Get draft — CLI example\nCommand: get-draft\n\nsubstack-api get-draft --draft-id 123456"
Get draft get_draft() CLI: get-draft MCP: get_draft
Request Copy for LLM GET {publication}/api/v1/drafts/{id}
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes Usually {publication}/publish or the draft editor URL User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default)
Path params Name Type Required Description id number | string yes Draft id in the URL path
Response Copy for LLM object
Fields Name Type Required Description id number | string yes Draft id draft_title string no Title draft_subtitle string no Subtitle draft_body string no ProseMirror JSON string draft_updated_at string no Optimistic-concurrency stamp for PUT
Python example Copy for LLM draft = client.get_draft(123456)
print(draft["draft_title"], draft["draft_updated_at"])
CLI example Copy for LLM substack-api get-draft --draft-id 123456"# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Update draft\nUpdate title, subtitle, body, audience, section, email flag, or cover_image URL.\n\nPython: update_draft()\nCLI: update-draft\nMCP: update_draft\nRate limit: 1 request per second\n\n# Update draft — Request\nUpdate title, subtitle, body, audience, section, email flag, or cover_image URL.\n\nPUT {publication}/api/v1/drafts/{id}\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): Usually {publication}/publish or the draft editor URL\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n- Content-Type (string, required): application/json\n\nPath params:\n- id (number | string, required): Draft id in the URL path\n\nQuery params: None\n\nBody:\n- last_updated_at (string, required): From latest get_draft/create_draft (client fetches it if omitted in SDK)\n- draft_bylines (array, required): [{ \"id\": <user_id>, \"is_guest\": false }]\n- detect_language (boolean, required): Usually false on update\n- translations (array, required): Usually []\n- draft_title (string, optional): SDK arg: title\n- draft_subtitle (string, optional): SDK arg: subtitle\n- draft_body (string, optional): ProseMirror JSON. SDK arg: body (plain text or object)\n- audience (string, optional): Audience string\n- draft_section_id (number | null, optional): SDK arg: section_id\n- section_chosen (boolean, optional): Set true when draft_section_id is set\n- should_send_email (boolean, optional): SDK arg: should_send_email\n- write_comment_permissions (string, optional): e.g. \"everyone\"\n- cover_image (string | null, optional): Cover image URL only — not a file upload\n\n# Update draft — Response\nType: object\n\nFields:\n- id (number | string, required): Draft id\n- draft_updated_at (string, required): New concurrency stamp\n- draft_title (string, optional): Updated title\n\n# Update draft — Python example\nMethod: update_draft()\n\nupdated = client.update_draft(\n 123456,\n title=\"Updated title\",\n body=\"New body\",\n cover_image=\"https://cdn.example.com/cover.jpg\",\n)\nprint(updated[\"draft_updated_at\"])\n\n# Update draft — CLI example\nCommand: update-draft\n\nsubstack-api update-draft \\\n --draft-id 123456 \\\n --title \"Updated title\" \\\n --body \"New body\""
Update draft update_draft() CLI: update-draft MCP: update_draft
Request Copy for LLM UPDATE {publication}/api/v1/drafts/{id}
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes Usually {publication}/publish or the draft editor URL User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default) Content-Type string yes application/json
Path params Name Type Required Description id number | string yes Draft id in the URL path
Body Name Type Required Description last_updated_at string yes From latest get_draft/create_draft (client fetches it if omitted in SDK) draft_bylines array yes [{ "id": <user_id>, "is_guest": false }] detect_language boolean yes Usually false on update translations array yes Usually [] draft_title string no SDK arg: title draft_subtitle string no SDK arg: subtitle draft_body string no ProseMirror JSON. SDK arg: body (plain text or object) audience string no Audience string draft_section_id number | null no SDK arg: section_id section_chosen boolean no Set true when draft_section_id is set should_send_email boolean no SDK arg: should_send_email write_comment_permissions string no e.g. "everyone" cover_image string | null no Cover image URL only - not a file upload
Response Copy for LLM object
Fields Name Type Required Description id number | string yes Draft id draft_updated_at string yes New concurrency stamp draft_title string no Updated title
Python example Copy for LLM updated = client.update_draft(
123456,
title="Updated title",
body="New body",
cover_image="https://cdn.example.com/cover.jpg",
)
print(updated["draft_updated_at"])
CLI example Copy for LLM substack-api update-draft \
--draft-id 123456 \
--title "Updated title" \
--body "New body""# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Publish now\nPublish an existing draft immediately.\n\nPython: publish_now()\nCLI: publish\nMCP: publish_post\nRate limit: 1 request per second\n\n# Publish now — Request\nPublish an existing draft immediately.\n\nPOST {publication}/api/v1/drafts/{id}/publish\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): Usually {publication}/publish or the draft editor URL\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n- Content-Type (string, required): application/json\n\nPath params:\n- id (number | string, required): Draft id in the URL path\n\nQuery params: None\n\nBody:\n- send (boolean, required): Email subscribers (SDK arg: send_email, default true)\n- share_automatically (boolean, required): Default false\n- audience (string, required): Default \"everyone\"\n\n# Publish now — Response\nType: object\n\nFields:\n- id (number | string, required): Post id\n- slug (string, optional): Public slug\n- is_published (boolean, optional): Publish flag\n\n# Publish now — Python example\nMethod: publish_now()\n\npublished = client.publish_now(123456, send_email=True, audience=\"everyone\")\nprint(published[\"slug\"], published[\"is_published\"])\n\n# Publish now — CLI example\nCommand: publish\n\nsubstack-api publish --draft-id 123456"
Publish now publish_now() CLI: publish MCP: publish_post
Request Copy for LLM POST {publication}/api/v1/drafts/{id}/publish
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes Usually {publication}/publish or the draft editor URL User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default) Content-Type string yes application/json
Path params Name Type Required Description id number | string yes Draft id in the URL path
Body Name Type Required Description send boolean yes Email subscribers (SDK arg: send_email, default true) share_automatically boolean yes Default false audience string yes Default "everyone"
Response Copy for LLM object
Fields Name Type Required Description id number | string yes Post id slug string no Public slug is_published boolean no Publish flag
Python example Copy for LLM published = client.publish_now(123456, send_email=True, audience="everyone")
print(published["slug"], published["is_published"])
CLI example Copy for LLM substack-api publish --draft-id 123456"# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Schedule\nSchedule a draft: optional prepublish GET, then scheduled_release POST.\n\nPython: schedule_release()\nCLI: schedule\nMCP: schedule_post\nRate limit: 1 request per second\n\n# Schedule — Request\nSchedule a draft: optional prepublish GET, then scheduled_release POST.\n\nGET then POST {publication}/api/v1/drafts/{id}/prepublish → {publication}/api/v1/drafts/{id}/scheduled_release\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): Usually {publication}/publish or the draft editor URL\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n- Content-Type (string, required): application/json\n\nPath params:\n- id (number | string, required): Draft id in the URL path\n\nQuery params:\n- publish_date (string, required): ISO-8601 UTC — query param on GET .../prepublish (same value as trigger_at)\n\nBody:\n- trigger_at (string, required): ISO-8601 UTC schedule time (POST scheduled_release body)\n- post_audience (string, required): Default \"everyone\"\n- email_audience (string, required): Default \"everyone\"\n\n# Schedule — Response\nType: any\n\nFields:\n- (body) (object | empty, optional): Substack often returns empty or a small object. Prepublish may include errors[]\n\n# Schedule — Python example\nMethod: schedule_release()\n\nfrom substack_api_client import utc_iso\nfrom datetime import datetime, timedelta, timezone\n\nwhen = utc_iso(datetime.now(timezone.utc) + timedelta(hours=2))\nclient.schedule_release(123456, trigger_at=when)\n\n# Schedule — CLI example\nCommand: schedule\n\nsubstack-api schedule \\\n --draft-id 123456 \\\n --at \"2026-08-10T15:00:00.000Z\""
Schedule schedule_release() CLI: schedule MCP: schedule_post
Request Copy for LLM GET POST {publication}/api/v1/drafts/{id}/prepublish → {publication}/api/v1/drafts/{id}/scheduled_release
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes Usually {publication}/publish or the draft editor URL User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default) Content-Type string yes application/json
Path params Name Type Required Description id number | string yes Draft id in the URL path
Query params Name Type Required Description publish_date string yes ISO-8601 UTC - query param on GET .../prepublish (same value as trigger_at)
Body Name Type Required Description trigger_at string yes ISO-8601 UTC schedule time (POST scheduled_release body) post_audience string yes Default "everyone" email_audience string yes Default "everyone"
Response Copy for LLM any
Fields Name Type Required Description (body) object | empty no Substack often returns empty or a small object. Prepublish may include errors[]
Python example Copy for LLM from substack_api_client import utc_iso
from datetime import datetime, timedelta, timezone
when = utc_iso(datetime.now(timezone.utc) + timedelta(hours=2))
client.schedule_release(123456, trigger_at=when)
CLI example Copy for LLM substack-api schedule \
--draft-id 123456 \
--at "2026-08-10T15:00:00.000Z""# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# List tags\nList publication post tags.\n\nPython: list_post_tags()\nCLI: list-tags\nMCP: list_tags\nRate limit: 1 request per second\n\n# List tags — Request\nList publication post tags.\n\nGET {publication}/api/v1/publication/post-tag\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): Usually {publication}/publish or the draft editor URL\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n\nPath params: None\n\nQuery params: None\n\nBody: No request body\n\n# List tags — Response\nType: array<object>\n\nFields:\n- id (string, required): Tag id\n- name (string, required): Tag display name\n- slug (string, optional): Tag slug\n\n# List tags — Python example\nMethod: list_post_tags()\n\ntags = client.list_post_tags()\nfor tag in tags:\n print(tag[\"id\"], tag[\"name\"])\n\n# List tags — CLI example\nCommand: list-tags\n\nsubstack-api list-tags"
List tags list_post_tags() CLI: list-tags MCP: list_tags
Request Copy for LLM GET {publication}/api/v1/publication/post-tag
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes Usually {publication}/publish or the draft editor URL User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default)
Response Copy for LLM array<object>
Fields Name Type Required Description id string yes Tag id name string yes Tag display name slug string no Tag slug
Python example Copy for LLM tags = client.list_post_tags()
for tag in tags:
print(tag["id"], tag["name"])"# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Create tag\nCreate a new publication post tag.\n\nPython: create_post_tag()\nCLI: create-tag\nMCP: create_tag\nRate limit: 1 request per second\n\n# Create tag — Request\nCreate a new publication post tag.\n\nPOST {publication}/api/v1/publication/post-tag\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): Usually {publication}/publish or the draft editor URL\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n- Content-Type (string, required): application/json\n\nPath params: None\n\nQuery params: None\n\nBody:\n- name (string, required): New tag name\n\n# Create tag — Response\nType: object\n\nFields:\n- id (string, required): Created tag id\n- name (string, required): Tag name\n- slug (string, optional): Tag slug\n\n# Create tag — Python example\nMethod: create_post_tag()\n\ntag = client.create_post_tag(\"product of the day\")\nprint(tag[\"id\"], tag[\"slug\"])\n\n# Create tag — CLI example\nCommand: create-tag\n\nsubstack-api create-tag --name \"product of the day\""
Create tag create_post_tag() CLI: create-tag MCP: create_tag
Request Copy for LLM POST {publication}/api/v1/publication/post-tag
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes Usually {publication}/publish or the draft editor URL User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default) Content-Type string yes application/json
Body Name Type Required Description name string yes New tag name
Response Copy for LLM object
Fields Name Type Required Description id string yes Created tag id name string yes Tag name slug string no Tag slug
Python example Copy for LLM tag = client.create_post_tag("product of the day")
print(tag["id"], tag["slug"])
CLI example Copy for LLM substack-api create-tag --name "product of the day""# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Attach tags\nEnsure tags exist (create missing) and attach them to a post/draft.\n\nPython: set_post_tags()\nCLI: set-tags\nMCP: set_tags\nRate limit: 1 request per second\n\n# Attach tags — Request\nEnsure tags exist (create missing) and attach them to a post/draft.\n\nGET/POST + POST {publication}/api/v1/publication/post-tag → {publication}/api/v1/post/{id}/tag/{tag_id}\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): Usually {publication}/publish or the draft editor URL\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n- Content-Type (string, required): application/json\n\nPath params:\n- id (number | string, required): Post/draft id in attach URL\n- tag_id (string, required): Tag id in attach URL\n\nQuery params: None\n\nBody:\n- (create tag) (object, optional): When creating missing tags: { \"name\": string }\n- (attach tag) (object, required): Empty JSON object {} on POST /post/{id}/tag/{tag_id}\n\n# Attach tags — Response\nType: array<object> (SDK normalized)\n\nFields:\n- tag (object, required): Resolved/created tag\n- status (\"attached\" | \"already_attached\", required): Attach result\n- link (object, optional): Raw attach response when newly attached\n\n# Attach tags — Python example\nMethod: set_post_tags()\n\nattached = client.set_post_tags(123456, [\"api-test\", \"newsletter\"])\nprint(attached)\n\n# Attach tags — CLI example\nCommand: set-tags\n\nsubstack-api set-tags \\\n --post-id 123456 \\\n --tags \"api-test,newsletter\""
Attach tags set_post_tags() CLI: set-tags MCP: set_tags
Request Copy for LLM GET POST {publication}/api/v1/publication/post-tag → {publication}/api/v1/post/{id}/tag/{tag_id}
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes Usually {publication}/publish or the draft editor URL User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default) Content-Type string yes application/json
Path params Name Type Required Description id number | string yes Post/draft id in attach URL tag_id string yes Tag id in attach URL
Body Name Type Required Description (create tag) object no When creating missing tags: { "name": string } (attach tag) object yes Empty JSON object {} on POST /post/{id}/tag/{tag_id}
Response Copy for LLM array<object> (SDK normalized)
Fields Name Type Required Description tag object yes Resolved/created tag status "attached" | "already_attached" yes Attach result link object no Raw attach response when newly attached
Python example Copy for LLM attached = client.set_post_tags(123456, ["api-test", "newsletter"])
print(attached)
CLI example Copy for LLM substack-api set-tags \
--post-id 123456 \
--tags "api-test,newsletter""# Install first\nIf the Python client, the CLI, or the MCP server is already installed, skip this section and continue with the method below.\nIf nothing is installed yet, install one product, then continue.\n\nPython client and CLI (one repo): https://github.com/alxgntv/substack-api-client\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\npip install -e .\nAfter install the CLI command is: substack-api\n\nMCP server (separate product): https://github.com/alxgntv/substack-api-mcp\ncd /path/to/Substack-API-MCP\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -e .\nAfter install the MCP command is: substack-api-mcp\n\n# Get post tags\nRead tags currently attached to a post.\n\nPython: get_post_tags()\nCLI: get-post-tags\nMCP: get_post_tags\nRate limit: 1 request per second\n\n# Get post tags — Request\nRead tags currently attached to a post.\n\nGET {publication}/api/v1/post/{id}/tag\n\nRate limit: 1 request per second\n\nHeaders:\n- Cookie (string, required): substack.sid=<SUBSTACK_SID> (browser session cookie)\n- Accept (string, required): application/json\n- Origin (string, required): Publication origin, e.g. https://yourname.substack.com\n- Referer (string, required): Usually {publication}/publish or the draft editor URL\n- User-Agent (string, required): Browser User-Agent string (client sends a Chrome UA by default)\n\nPath params:\n- id (number | string, required): Post/draft id in the URL path\n\nQuery params: None\n\nBody: No request body\n\n# Get post tags — Response\nType: array<object>\n\nFields:\n- id (string, optional): Tag or link id\n- post_tag_id (string, optional): Attached tag id (used by set_post_tags dedupe)\n- name (string, optional): Tag name when present\n\n# Get post tags — Python example\nMethod: get_post_tags()\n\ntags = client.get_post_tags(123456)\nprint(tags)\n\n# Get post tags — CLI example\nCommand: get-post-tags\n\nsubstack-api get-post-tags --post-id 123456"
Get post tags get_post_tags() CLI: get-post-tags MCP: get_post_tags
Request Copy for LLM GET {publication}/api/v1/post/{id}/tag
Rate limit: 1 request per second
Headers Name Type Required Description Cookie string yes substack.sid=<SUBSTACK_SID> (browser session cookie) Accept string yes application/json Origin string yes Publication origin, e.g. https://yourname.substack.com Referer string yes Usually {publication}/publish or the draft editor URL User-Agent string yes Browser User-Agent string (client sends a Chrome UA by default)
Path params Name Type Required Description id number | string yes Post/draft id in the URL path
Response Copy for LLM array<object>
Fields Name Type Required Description id string no Tag or link id post_tag_id string no Attached tag id (used by set_post_tags dedupe) name string no Tag name when present
Python example Copy for LLM tags = client.get_post_tags(123456)
print(tags)
CLI example Copy for LLM substack-api get-post-tags --post-id 123456