Fetch Substack Stats (Chrome automation)
Table of Contents
Related skills: Primary alternative to ingest-substack-csv. Produces the same WeekExport contract so downstream skills (compute-baseline, attribute-performance, per-section-tracking) don't care which path produced the data.
Prerequisites
- Chrome logged in to Substack as the publication owner (one-time manual setup by the writer).
- Claude-in-Chrome MCP tools available:
tabs_context_mcp, tabs_create_mcp, navigate, get_page_text, read_page, optionally javascript_tool.
- Publication URL known:
https://thethinkersnotebook.substack.com/publish/stats (the dashboard URL shape — verify on first run).
Workflow
Per weekly run (Mondays) or on-demand:
- [ ] Step 1: tabs_context_mcp — inspect existing tabs; if a Substack stats tab is already open, reuse; else tabs_create_mcp
- [ ] Step 2: navigate to https://substack.com/publish/stats (or publication-specific dashboard URL)
- [ ] Step 3: get_page_text on the rendered dashboard; parse:
- Total subscribers (headline number)
- Weekly delta
- Posts table with columns: title, date, opens, open rate, clicks, CTR, views, sent
- Activity-tier distribution (free / paid, active / at-risk / churned)
- [ ] Step 4: For each post in the last 7 days, also navigate to the individual post stats page for:
- Referral sources breakdown
- Post-specific engagement
- [ ] Step 5: Normalize into WeekExport object (schema matches ingest-substack-csv's output)
- [ ] Step 6: Archive the scraped stats as CSV in corpus/stats/YYYY-WW.csv (so historical baseline works identically)
- [ ] Step 7: Close the Substack tab (do NOT leave stats pages open in the user's browser)
Step 4 detail — referral sources
Substack exposes referral source breakdowns only on individual post stats pages. The scraper navigates to each outlier post (|z| ≥ 1.0 candidate, determined after baseline compute — so this step may be deferred to attribute-performance) to pull referral data. For non-outliers, per-post referral is skipped.
Output
Same schema as ingest-substack-csv:
{
"subscribers_end": int,
"delta_subscribers": int,
"posts": [
{"slug", "title", "post_date", "views", "opens", "open_rate", "clicks", "sent", ...}
],
"sends_this_week": int,
"free_subs": int,
"paid_subs": int,
"activity_tier_distribution": {...},
"source": "chrome-scrape", # vs. "csv-export" from the other skill
"scraped_at": ISO8601
}
Written to corpus/stats/YYYY-WW.csv (same archive path as CSV imports). The source field marks provenance so the writer can tell at a glance whether a week came from live scrape or manual export.
Worked example
Trigger: Monday morning, Growth Analyst invokes fetch-substack-stats.
tabs_context_mcp — no existing Substack tab.
tabs_create_mcp + navigate → Substack dashboard stats page.
get_page_text — reads:
- Total subscribers: 148
- Weekly delta: +6
- Posts table (last 7 days): 1 post shown, "Attention is a routing problem", 680 views, 48% open, 5% CTR.
- For the one post,
navigate → post-specific stats → referral breakdown shows 60% direct, 20% Notes, 20% search.
- Normalize:
WeekExport{subscribers_end: 148, delta_subscribers: 6, posts: [...], ...}.
- Write
corpus/stats/2026-W17.csv.
- Close the Substack tab.
Downstream pipeline (compute-baseline, attribute-performance, etc.) runs identically whether data came from CSV or scrape — the WeekExport contract is stable.
Guardrails
- Idempotent within a week. If
corpus/stats/{YYYY-WW}.csv already exists for today's ISO week, compare — don't overwrite unless the scrape is strictly more recent and differs meaningfully.
- Close tabs on exit. Do not leave stats pages open in the user's browser; they are distracting.
- Fallback to CSV cleanly. If any step fails (login expired, dashboard URL changed, page structure shifted), emit
fetch-substack-stats FAILED: {reason}; falling back to ingest-substack-csv and halt — let the writer decide whether to retry or drop a manual CSV.
- Never log individual subscriber emails even though the dashboard subscriber list is visible. Aggregate counts and activity-tier distributions only. Matches the CSV path's privacy posture.
- Do not navigate anywhere except Substack dashboard URLs. No side-trips.
- Do not auto-install or update Claude-in-Chrome. If the MCP tools are unavailable, return a specific error ("claude-in-chrome not available; user must enable").
- Respect soft rate limits. One scrape per week is the default; catch-up mode may run up to 4 if the writer missed multiple weeks, but always space them at least 30 seconds apart.
- Never take actions on the dashboard. No clicking "Delete", no editing post metadata, no unsubscribing anyone. Read-only.
- Session state hygiene. If login has expired, do not attempt to log in on the writer's behalf. Halt with
login-required message; writer handles auth manually.
Quick reference
- Browser-automation replacement for manual CSV export.
- Same WeekExport contract → downstream pipeline identical.
- Archives to
corpus/stats/YYYY-WW.csv on success.
- Fallback:
ingest-substack-csv if browser path fails.
- Read-only — never takes actions on the dashboard.
1---2name: fetch-substack-stats3description: Pulls substacker's weekly Substack stats directly from the dashboard via Claude-in-Chrome browser automation. Navigates to substack.com/stats, parses the posts table and subscribers table, and produces the same typed WeekExport object that ingest-substack-csv produces — but without requiring a manual CSV export. The writer keeps Chrome signed in to Substack; this skill opens the dashboard in a new tab, reads the rendered stats, closes the tab. Primary data path for the Growth Analyst; ingest-substack-csv is the fallback when browser automation is unavailable. Trigger keywords — fetch stats, Substack dashboard, auto stats, Chrome stats, dashboard scrape, live stats, no CSV.4---5
6# Fetch Substack Stats (Chrome automation)
7
8## Table of Contents
9
10- [Prerequisites](#prerequisites)
11- [Workflow](#workflow)
12- [Output](#output)
13- [Worked example](#worked-example)
14- [Guardrails](#guardrails)
15
16**Related skills:** Primary alternative to `ingest-substack-csv`. Produces the same `WeekExport` contract so downstream skills (`compute-baseline`, `attribute-performance`, `per-section-tracking`) don't care which path produced the data.
17
18## Prerequisites
19
20- **Chrome logged in to Substack** as the publication owner (one-time manual setup by the writer).
21- **Claude-in-Chrome** MCP tools available: `tabs_context_mcp`, `tabs_create_mcp`, `navigate`, `get_page_text`, `read_page`, optionally `javascript_tool`.
22- Publication URL known: `https://thethinkersnotebook.substack.com/publish/stats` (the dashboard URL shape — verify on first run).
23
24## Workflow
25
26```
27Per weekly run (Mondays) or on-demand:
28- [ ] Step 1: tabs_context_mcp — inspect existing tabs; if a Substack stats tab is already open, reuse; else tabs_create_mcp
29- [ ] Step 2: navigate to https://substack.com/publish/stats (or publication-specific dashboard URL)
30- [ ] Step 3: get_page_text on the rendered dashboard; parse:
31 - Total subscribers (headline number)
32 - Weekly delta
33 - Posts table with columns: title, date, opens, open rate, clicks, CTR, views, sent
34 - Activity-tier distribution (free / paid, active / at-risk / churned)
35- [ ] Step 4: For each post in the last 7 days, also navigate to the individual post stats page for:
36 - Referral sources breakdown
37 - Post-specific engagement
38- [ ] Step 5: Normalize into WeekExport object (schema matches ingest-substack-csv's output)
39- [ ] Step 6: Archive the scraped stats as CSV in corpus/stats/YYYY-WW.csv (so historical baseline works identically)
40- [ ] Step 7: Close the Substack tab (do NOT leave stats pages open in the user's browser)
41```
42
43### Step 4 detail — referral sources
44
45Substack exposes referral source breakdowns only on individual post stats pages. The scraper navigates to each outlier post (|z| ≥ 1.0 candidate, determined after baseline compute — so this step may be deferred to `attribute-performance`) to pull referral data. For non-outliers, per-post referral is skipped.
46
47## Output
48
49Same schema as `ingest-substack-csv`:
50
51```python
52{
53 "subscribers_end": int,
54 "delta_subscribers": int,
55 "posts": [
56 {"slug", "title", "post_date", "views", "opens", "open_rate", "clicks", "sent", ...}
57 ],
58 "sends_this_week": int,
59 "free_subs": int,
60 "paid_subs": int,
61 "activity_tier_distribution": {...},
62 "source": "chrome-scrape", # vs. "csv-export" from the other skill
63 "scraped_at": ISO8601
64}
65```
66
67Written to `corpus/stats/YYYY-WW.csv` (same archive path as CSV imports). The `source` field marks provenance so the writer can tell at a glance whether a week came from live scrape or manual export.
68
69## Worked example
70
71**Trigger**: Monday morning, Growth Analyst invokes `fetch-substack-stats`.
72
731. `tabs_context_mcp` — no existing Substack tab.
742. `tabs_create_mcp` + `navigate` → Substack dashboard stats page.
753. `get_page_text` — reads:
76 - Total subscribers: **148**
77 - Weekly delta: **+6**
78 - Posts table (last 7 days): 1 post shown, "Attention is a routing problem", 680 views, 48% open, 5% CTR.
794. For the one post, `navigate` → post-specific stats → referral breakdown shows 60% direct, 20% Notes, 20% search.
805. Normalize: `WeekExport{subscribers_end: 148, delta_subscribers: 6, posts: [...], ...}`.
816. Write `corpus/stats/2026-W17.csv`.
827. Close the Substack tab.
83
84Downstream pipeline (`compute-baseline`, `attribute-performance`, etc.) runs identically whether data came from CSV or scrape — the WeekExport contract is stable.
85
86## Guardrails
87
881. **Idempotent within a week.** If `corpus/stats/{YYYY-WW}.csv` already exists for today's ISO week, compare — don't overwrite unless the scrape is strictly more recent and differs meaningfully.
892. **Close tabs on exit.** Do not leave stats pages open in the user's browser; they are distracting.
903. **Fallback to CSV cleanly.** If any step fails (login expired, dashboard URL changed, page structure shifted), emit `fetch-substack-stats FAILED: {reason}; falling back to ingest-substack-csv` and halt — let the writer decide whether to retry or drop a manual CSV.
914. **Never log individual subscriber emails** even though the dashboard subscriber list is visible. Aggregate counts and activity-tier distributions only. Matches the CSV path's privacy posture.
925. **Do not navigate anywhere except Substack dashboard URLs.** No side-trips.
936. **Do not auto-install or update Claude-in-Chrome.** If the MCP tools are unavailable, return a specific error ("claude-in-chrome not available; user must enable").
947. **Respect soft rate limits.** One scrape per week is the default; catch-up mode may run up to 4 if the writer missed multiple weeks, but always space them at least 30 seconds apart.
958. **Never take actions on the dashboard.** No clicking "Delete", no editing post metadata, no unsubscribing anyone. Read-only.
969. **Session state hygiene.** If login has expired, do not attempt to log in on the writer's behalf. Halt with `login-required` message; writer handles auth manually.
97
98## Quick reference
99
100- Browser-automation replacement for manual CSV export.
101- Same WeekExport contract → downstream pipeline identical.
102- Archives to `corpus/stats/YYYY-WW.csv` on success.
103- Fallback: `ingest-substack-csv` if browser path fails.
104- Read-only — never takes actions on the dashboard.