Work · Reports & Insights Portal · ThinkingAI · Senior Marketing Manager · 2026

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.

5 sourceswired, 1 waiting on credentials
11 cardslive on the portal
No aggregatorself-hosted, self-refreshing
Before

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 system · five parts

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.

Part 01 · The sources

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 MCP
Data sourceseach pulled through its own API/MCP6 built · 5 wired
HubSpotfunnel · deals · contacts
GA4 + BigQuerypage journeys · creds pending
GSC · Ahrefssearch + SEO
Zernio · Google AdsLinkedIn + SEM
Part 02 · The hub

Role-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 board
Hub indextabbed by audience, one card per report3 tabs
Marketingfunnel · pages · LinkedIn · SEO
Salespipeline dashboard
Personalweekly to-do board
Part 03 · The refresh

Why 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 fragile
Refresh designwhy cron, and what runs on itdaily cron
Agent on a schedulecontext limits + token cost
Cheaper sub-agentneeds daily monitoring
GitHub Actions cron → build → Vercel deploy
hubspot-funneldaily · auto-dated
page / linkedin perfdaily · auto-dated
sales · otherson demand
Part 04 · Two readers

Built 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 Reporting
Two consumerssame reports, different readersdual
Humanscans the boards
Agent, on requestanswers a question
Agent, on a schedulefinds anomalies unwatched
Bridge→ agent-native reporting
Part 05 · The gate

Encrypted, 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 webhook
Gate + request flowencrypted at rest, self-serve requestsAES-256-GCM
Buildencrypt with PBKDF2 key
Deploydecryptor + ciphertext only
ViewWeb Crypto decrypts in browser
Requesthub form → serverless → Lark
What it changed

The 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 the team getsthe part that outlived the dashboardthe payoff
One place for every marketing number
Drill-down under every summary
Daily refresh with no human step
Cross-team visibility, on the record
Agent-readable for scheduled monitoring
Status

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.

The pieces · honest status
Portal · hub · gate · request formRUNNING
Daily cron · defined report setRUNNING
GA4 / BigQuery page-performanceBLOCKED ON IT
Agent-insight layerEMERGING
GitHub Actions Vercel HubSpot API GA4 + BigQuery Search Console Ahrefs Google Ads
Decisions I'd make again

Calls I made on purpose

01 Own the aggregator

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.

02 Modular by role

A sales lead should open the portal and see pipeline; a marketer should see funnel and pages. Three tabs, per-role card sets.

03 Freshness must be visible

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.

04 Encrypt at rest, decrypt in the browser

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.

05 Requests go to the portal

The hub's Lark-wired form takes report requests, so every ask lands in one queue.

06 One dataset, two readers

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.

He is a professional who brings his capability to learn things quickly to get work done successfully. His professional ethic and ease to adapt to any team make him a good element to any organization.
Bruce Au Bruce AuGeneral Manager
PRO

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.