A small TypeScript toolkit for inspecting and managing Substack recommendations.
This uses undocumented, authenticated Substack web endpoints. It may stop working if Substack changes its UI/API. Review the list carefully before confirming.
The script needs the browser Cookie header, not a bearer token or API key. In your browser, open the Substack publication dashboard, copy the relevant cookies from the Recommendations request in Developer Tools, and provide them through an environment variable or a file. Do not commit or share this value; session cookies grant access to your account.
cp .env.example .env
# Edit .env and set SUBSTACK_HOST and SUBSTACK_COOKIE.SUBSTACK_HOST and SUBSTACK_COOKIE are required and must be set in .env or the environment. They are not accepted as command-line arguments.
Requires Node.js 20+.
npm install
npm run build
# Remove non-reciprocal recommendations (asks for confirmation)
npm run remove -- --dry-run
npm run remove
# Show all publications you recommend, sorted by subscriber count
npm run listFor development without compiling:
npm run dev -- remove --dry-runCommands:
npm run list: show every publication you recommend, sorted by subscriber countnpm run remove: remove recommendations that do not recommend you back
Options:
--dry-run: fetch and display candidates but never send a DELETE request (applies toremove)
The remove command prints every non-reciprocal outgoing recommendation and requires you to type DELETE before making any changes. It deletes candidates one at a time and reports individual failures. The list command prints each publication's own freeSubscriberCount from Substack, rather than the number of subscribers your recommendation generated.
The implementation follows the request patterns captured from the Recommendations page. The host and cookie are always configured through environment variables:
GET /api/v1/recommendations/stats/tofor incoming recommendationsGET /api/v1/recommendations/stats/fromfor outgoing recommendations
The API currently returns a rows array and accepts limit=10; the script paginates until all rows are loaded.
DELETE /api/v1/recommendations/withrecommending_publication_id,recommended_publication_id, andsource: "recommendation-stats"
The DELETE request uses the payload observed in the Recommendations UI:
{
"recommending_publication_id": 123,
"recommended_publication_id": 456,
"source": "recommendation-stats"
}The command always displays candidates and requires explicit confirmation before deletion.