Six platform APIs, 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, built each into a role-modular report, and put them all on one encrypted portal that refreshes itself.
The CEO asked for one place to see the numbers
The ask was specific and reasonable: 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. Cost and dependency 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.
One portal, five parts
The portal is a static site assembled from platform APIs, rebuilt by a cron job, encrypted before it ships, and read by two kinds of consumers: people and agents.
Six sources, each pulled through its own API
Each source is pulled through its own API or MCP, because every one of these platforms now exposes one. Five are wired today: HubSpot, Google Search Console, Ahrefs, LinkedIn via Zernio, 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
Reports are split 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. The design intent: each role gets its own set.
Live cards: hubspot-funnel · page-performance · linkedin-performance · sales · competitor-content · sem-engine, plus my own weekly-todo board · 11 totalWhy 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.
Judgment goes to the models, scheduled retrieval goes to a cron. 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. An agent reads the same structured data to do what a static dashboard can't: scan across the numbers, spot the problem, pull an insight. That second reader is the bridge into the agent-native operating model.
That is what the portal actually buys a manager. Their agent can be pointed 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
Every report is AES-256-GCM encrypted with a PBKDF2-derived key at build time; the deployed file is a decryptor plus ciphertext, so viewing source without the password shows nothing. Decryption happens client-side via Web Crypto. The hub also carries a marketing-request form that posts through a Vercel serverless function to a Lark group bot, so the team asks for reports inside the portal.
build-public.js encrypts · api/request.js → Lark webhookAnyone can open the numbers without asking me
The reports helped. Being visible helped more. 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; the analysis layer that reads them back to me is still being built. The audience for an internal report turned out to be half people and half software. That is a fairly 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, drill-down underneath it, a refresh that needs no human, and an agent that reads the same data on its own schedule. Anyone in the company can now check the reporting directly; nobody assembles it.
Centralized, drilled down, self-updating, and readable by both a person and an agentCalls I made on purpose
Every platform in our stack now has an API or MCP. That makes the paid-aggregator category optional. I chose to assemble the layer myself and keep it.
A sales lead should open the portal and see pipeline; a marketer should see funnel and pages. Three tabs, per-role card sets. Relevance beats completeness.
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 asks land in a queue, not my DMs.
I structured the reports so an agent can reason over the same data a manager scans. That single decision is what connects this system to the operating model it serves.
What's real today
The portal runs, a defined set refreshes itself daily, and 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.
Reusability
The unit transfers: source API → generator → encrypted static build → Vercel + a daily cron → a tabbed, role-modular hub. Any team whose tools expose APIs can own this layer.
Related work
GitHub Actions
Vercel
HubSpot API
GA4 + BigQuery
Search Console
Ahrefs
Google Ads
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.