Marketing Data Hub
Your marketing data, on your machine, free. An open-source Windsor.ai alternative: pulls Google Analytics 4, Search Console (plus Google Ads, Meta Ads, YouTube) into one local database — queryable via a REST API, scheduled CSV exports, and by AI assistants like Claude (MCP). No hosted service, no subscription, your tokens never leave your computer.
Quick start
Windows: download the installer from https://growthbybhargava.com/tools/marketing-data-hub (direct link: MarketingDataHub-Setup.exe) and run it — the setup page opens in your browser.
Anywhere with Python 3.11+:
python3 -m pip install marketing-data-hub
python3 -m hub.cli setup
(Use python3 -m pip/python3 -m hub.cli rather than bare pip/hub —
on Mac especially, pip on your PATH can point at a different Python than
python3, so a bare pip install can succeed while hub still isn't found.
python3 -m ... always installs into and runs from the same interpreter.)
(Developers: git clone https://github.com/Bhargava-R-dev/marketing-data-hub
and pip install -e ".[dev]" instead.)
The setup page walks you through: sign in to Google → tick the GA4 properties /
Search Console sites you want → watch the first sync load, account by account →
connect Claude (one click) → done, with the daily 6am sync scheduled for you and
a Marketing Data Hub icon on your Desktop / Start Menu. That icon is the
everyday entry point: add or remove accounts, sync now, open the dashboard —
no command window (hub shortcut recreates it).
Your hub lives in %LOCALAPPDATA%\MarketingDataHub (Windows) or
~/.marketing-data-hub (Mac/Linux); nothing to configure. A config.yaml in
the current folder takes precedence, so existing checkouts keep working.
Prefer your own Google Cloud project over the bundled sign-in? Drop your
google_client.json in that folder's secrets/ — the setup page shows which
one is in use (SETUP.md step 2).
Never used a terminal? → GUIDE.md is a complete, plain-English walkthrough from installing Python through asking your first question — written for non-technical teammates, and made to be shared.
Then ask Claude things like "How did organic traffic do in June vs May?" or "Top non-branded search queries this month?" — or automate a daily 6am sync (SETUP.md, step 8).
Reports (analysis shapes)
Each source syncs several named reports — different dimensional shapes of the same data, stored side by side and never mixed (mixing granularities would double-count):
| Source | Report | Answers |
|---|---|---|
| ga4 | core |
daily campaign totals (sessions, users, conversions, revenue) |
| ga4 | channels |
traffic mix: organic vs paid vs direct, engagement, pageviews |
| ga4 | landing_pages |
entry-page performance per channel |
| ga4 | pages |
page behaviour: views, engagement time, events per path |
| ga4 | audience |
device × country segmentation |
| ga4 | visitors |
new vs returning (cohort-lite) |
| gsc | core |
exact daily search totals per site |
| gsc | queries |
per-query performance (branded split = string-match) |
| gsc | pages |
per-URL search performance |
| gsc | devices / countries |
mobile/desktop and geo splits |
| ga4 | events |
per-event counts by name (brand-specific: form_submit, call_click...) |
GA4 breakdown reports exclude GA4's unattributable (other) bucket, so they
sum to slightly under the topline (on very large properties, well under
for high-cardinality dims like landing pages) — use core for exact totals,
breakdowns for composition/ranking. Same idea as GSC query anonymisation.
Pass report=<name> to the API/MCP query_metrics; default is core.
MCP query_metrics also supports compare= (prev_period / prev_day / prev_week /
prev_month / prev_year — returns value, previous, and %-change per metric for any
date range) and filters= (exact match on any dimension incl. report extras,
e.g. {"event": "form_submit"} or {"device": "MOBILE"}).
Rates are computed, not stored: engagement rate = engaged_sessions/sessions,
ctr = clicks/impressions, avg engagement time = engagement_seconds/pageviews.
GSC breakdown reports undercount totals slightly (Google anonymises rare
queries) — use core for toplines. True user-level cohorts need the GA4
BigQuery export; visitors + the live tools cover cohort-lite analysis.
For anything the synced reports don't cover, the MCP tools query_ga4_live
and query_gsc_live pass arbitrary dimension/metric combinations straight to
the APIs on demand.
Setup
New here / installing on another machine? Follow SETUP.md — a step-by-step guide including the Google Cloud OAuth setup. Quick version:
python -m pip install -e ".[dev]"- Copy
config.yaml.example→config.yaml; fill in your GA4property_idand Search Consolesite_url. Have multiple GA4 properties or Search Console sites under the same Google login? Useproperty_ids: [...]/site_urls: [...]instead — all of them sync, and every row is tagged with its ownaccount_idso they stay distinguishable downstream. - Copy
.env.example→.env; set a randomHUB_API_KEY. - Google Cloud Console → create a project → enable Google Analytics Data API,
Google Analytics Admin API, Search Console API, YouTube Analytics
API → create an OAuth client (Desktop app) → download JSON to
secrets/google_client.json. (See SETUP.md for the OAuth consent-screen steps and the 7-day token-expiry gotcha.) hub doctor— first run opens a browser to authorize; then all checks go green.hub accounts --add— pick which GA4 properties / GSC sites to sync from everything your Google login can see.
Daily use
| Command | What it does |
|---|---|
hub sync all |
sync every configured source (rolling 30-day window) |
hub backfill ga4 --from 2024-01-01 |
load history in 90-day chunks |
hub status |
row counts + last sync per source |
hub serve |
query API on 127.0.0.1:8000 + cron scheduler |
hub export all |
write configured CSVs to exports/ |
hub mcp |
MCP server (stdio) for Claude |
Query API
GET /connectors/all/data?fields=date,source,clicks,spend&date_preset=last_30d
X-API-Key: <HUB_API_KEY>
format=csv for CSV, report=<name> for a breakdown report. /connectors
lists sources; /connectors/{source}/reports lists report shapes;
/connectors/{source}/fields?report=<name> lists fields.
Claude MCP
claude mcp add marketing-hub -- python -m hub.cli mcp --config <absolute-path>/config.yaml
Then ask Claude: "How did my campaigns do last week?"
Note: use an absolute path for --config; the MCP process may be launched from a different working directory.
trigger_sync starts the sync in the background and returns immediately
(output goes to logs/mcp_sync.log); poll sync_status to see when it
finishes. While a sync holds the write lock, query tools return a readable
"database is busy" error instead of hanging.
Activating the ad connectors
- Google Ads: apply for a developer token (API Center), then uncomment
google_adsin config.yaml and fill options. - Meta Ads: create a Meta app, generate a long-lived token with
ads_read, uncommentmeta_adsand fill options.
Known limitations
- DuckDB allows one writer: run
hub mcpORhub serve, not both at once (trigger_sync from MCP spawns the CLI, which needs the write lock free). While any sync runs, MCP query tools report "database is busy" until it finishes (~3 min forsync all). - Extras fields (e.g. position, ctr, views) are returned as strings by the query API — cast numerically as needed.
Metadata
Release files for marketing-data-hub 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| marketing_data_hub-0.6.0.tar.gz | 525.5 kB | With provenance | Details for marketing_data_hub-0.6.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| marketing_data_hub-0.6.0-py3-none-any.whl | Python 3 | none | any | Details for marketing_data_hub-0.6.0-py3-none-any.whl |
Total release size: 742.6 kB
Release files / marketing_data_hub-0.6.0.tar.gz
| Download URL | marketing_data_hub-0.6.0.tar.gz |
|---|---|
| Size | 525.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
476e727b5761736c1270e242ec4e7ede99e54a80a5d82c2b12c7d8cce0141abb
|
|
BLAKE2b-256 checksum How to use checksums |
7f4c5e6c1fda90a491c561c8183f33780d6d5bc1e44a68688ff34fbe81b3f870
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency logRelease files / marketing_data_hub-0.6.0-py3-none-any.whl
| Download URL | marketing_data_hub-0.6.0-py3-none-any.whl |
|---|---|
| Size | 217.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
95d8e0d6a4576e3e4eea96ac2e6f56b94867892a813280f3b9f2f189b0e4775d
|
|
BLAKE2b-256 checksum How to use checksums |
156b8df714fa04b1295fb0b0285e1b7a52a5b52897fd256408377911a22e2a6b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency logSecurity for marketing-data-hub 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for marketing_data_hub-0.6.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.