Five platform APIs wired, one portal, no aggregator
Most of the stack ships an API or an MCP. A Funnel.io-type aggregator would have charged us to do what those APIs already do, so I pulled each source myself and built a report per audience on one encrypted portal that refreshes itself.
The CEO asked for one place to see the numbers
The ask was specific: a centralized dashboard covering website, sales, marketing and some operational reporting, in one place. The numbers all existed. They lived in six different logins, and nobody saw them side by side.
I evaluated the two normal answers before building anything. HubSpot was first, and it broke on seats and scope: I hold a marketing core seat, covering the sales side properly means buying more licenses, the non-HubSpot platforms do not centralize into it cleanly, and customizing the reports past a point gets hard. Funnel.io was the second look and it is a decent product, but it is another third-party platform to depend on and pay for, and it still left the everything-in-one-place problem unsolved. So I built it, as a Vercel site with each report wired to its own source.
- Every number in a different platform. HubSpot, GA4, Search Console, Ahrefs, LinkedIn, Google Ads. Each with its own login, its own dashboard, no single view.
- Aggregating meant paying for it. A subscription or a BI team stitching connectors. We'd pay a vendor for what is now an API call.
- Reports were stale and human-only. Point-in-time exports a person made, readable by a person. Nothing an agent could scan to reason over.
The aggregator is the part I kept
A cron job rebuilds the portal from platform APIs, encrypts it before it ships, and serves two kinds of readers: people and agents.
Six sources, five wired, each through its own API
I pull each source through its own API or MCP, because every platform now exposes one. Five are wired today: HubSpot, Google Search Console, Ahrefs, LinkedIn via Zernio because the official LinkedIn Ads API needs partner approval, and Google Ads. GA4 with BigQuery export is built and waiting on credentials from IT. That set is the aggregator, and I own it.
HubSpot via REST Service Key + MCP · GA4 Data API + BigQuery export · Ahrefs via MCPRole-modular reports
I split the reports by audience, so nobody scrolls past someone else's numbers. The hub has three tabs (Marketing, Sales, Personal), and each report is a self-contained card in the right tab. I wanted each role to get its own set.
Live cards include hubspot-funnel · page-performance · linkedin-performance · sales · competitor-content · sem-engine, part of 10 stakeholder cards, plus my own weekly-todo boardWhy an agent does not run the daily update
The obvious design is to let an agent rebuild the reports on a schedule. I tried to reason it through and it fails on two counts. These pulls carry a lot of rows, so a daily agent run keeps hitting context limits and burns expensive tokens to re-derive the same tables. Push it down to a cheaper model and the cost improves while a new problem appears: something has to check every morning that it actually ran and that the output is sane. Now the automation has a daily babysitter, which is the thing I was trying to remove.
So the refresh is deliberately boring. GitHub Actions runs the job on a cron, pulls the fresh data, rebuilds the report, and tells Vercel to deploy the result. Deterministic work stays deterministic and costs almost nothing. Each auto-dated card syncs its “last updated” to the real pulled date, so a stale card looks stale. Adding a stakeholder's report means updating a template once; after that it maintains itself and I never look at it again.
The honest split: the HubSpot funnel and the page- and LinkedIn-performance cards run on the daily cron; other reports are agent-refreshed on demand. I'm not claiming every card auto-updates daily.
Models judge. A cron fetches on schedule. Mixing the two is what makes agent automation expensive and fragileBuilt for humans and agents
Two consumers by design. A manager reads the boards to see performance, and an agent scans the same structured data across cards to surface the anomalies. That second reader is the bridge into the agent-native operating model.
That is what the portal buys a manager. They can point their agent at these reports whenever they want an answer, and it can also run on a schedule while nobody is looking, reading the day's numbers and surfacing anomalies before someone notices a chart. I run mine the same way. Nobody has to open a dashboard for the dashboard to be doing work.
Same centralized data, two readers · this portal is the delivery surface for Agent-Native ReportingEncrypted, gated and self-serve
The build encrypts every report with AES-256-GCM and a PBKDF2 key. What deploys is a decryptor plus ciphertext, so viewing source without the password shows nothing. The browser decrypts with Web Crypto. I also wired a request form on the hub that posts through a Vercel function into a Lark group, so the team asks for reports where they read them.
build-public.js encrypts · api/request.js → Lark webhookThe people with access open the numbers without asking me
Publishing the numbers did more than the reports themselves. Everything marketing runs is now published in one place, on a daily cadence, at a level of detail anyone can drill into. Another team no longer has to ask what we are working on or take our word for how a channel is performing, and neither do I when I need to answer for it. Their agents read the published reports directly, which I had not designed for. I'm still building the analysis layer that reads them back to me. The audience for an internal report turned out to be half people and half software. That is an exposing way to operate, and it is the point: numbers that update themselves are harder to present selectively.
The combination is what I had not had before: one place to look, with drill-down underneath it. The refresh needs no human, and an agent reads the same data on its own schedule. Anyone with the portal password can now open the reports; nobody assembles them.
What's real today
The portal runs and a defined set refreshes itself daily. The whole layer is ours.
The limits
I'm not claiming every card auto-updates daily, and this is no polished commercial BI product or finished insight-to-execution loop. I also publish no cost-comparison number, because I haven't measured one. Usage is not instrumented either, so I cannot say how many people open the portal in a week.
Reusability
The unit transfers: source API → generator → encrypted static build → Vercel + a daily cron → a hub with one tab per audience. Any team whose tools expose APIs can own this layer.
Related work
GitHub Actions
Vercel
HubSpot API
GA4 + BigQuery
Search Console
Ahrefs
Google Ads
Calls I made on purpose
Every platform in our stack now has an API or MCP. That makes the paid-aggregator category optional. I assembled the layer myself and kept it.
A sales lead should open the portal and see pipeline; a marketer should see funnel and pages. Three tabs, per-role card sets.
A dashboard with a vague date is a trap. The auto-dated cards sync “last updated” to the actual data-pull date, so a stale card looks stale.
Customer data never sits readable in the deployed source. AES-256-GCM with a PBKDF2 key, decrypted client-side by Web Crypto after the password.
The hub's Lark-wired form takes report requests, so every ask lands in one queue.
I structured the reports so an agent can reason over the same data a manager scans. That one choice ties this system to the operating model it serves.
Want to own your marketing BI layer?
This portal exists so a way of working can run on top of it. Agents read these reports and hand back findings.