ReviewOS

stacks/analyticshq

[feature] Google Search Console + Looker Studio connectors

#25
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Gap

No SEO/BI integrations. Plausible integrates Search Console and a Looker Studio connector; Simple Analytics has Looker/Power BI.

Proposed

Optional Search Console import (keywords/impressions) and a Looker Studio / BI connector on top of the Stats API. Both opt-in and operator-configured — no visitor-side privacy impact.

Priority: P2.


From the 2026-07-20 privacy-first competitive audit (vs. Plausible / Fathom / Simple Analytics / Umami).

9 days ago

Both halves shipped: Search Console import in 3deec3b, Looker Studio connector + BI endpoint in 856194e.

The Google dependency this was blocked on turned out to be avoidable. The concern was real — an app registered with Google, a consent screen, verification for a sensitive scope, refresh tokens held for customers' Google accounts. Neither half needs any of it:

  • Search Console uses a service account the customer creates in their own Cloud project and grants read access to their own property, exactly as the GA4 importer does. We register nothing and hold no long-lived credential; the key is used for one token exchange and dropped.
  • Looker Studio ships as Apps Script source in connectors/looker-studio/ that you deploy into your own account. Publishing to Google's gallery would mean submitting this product for their review and maintaining a relationship to stay listed — a strange dependency for a reporting convenience. Auth is the existing share token: per-site, read-only, revocable, and rotation kills every connected report immediately.

A few decisions worth recording:

  • The BI endpoint is dedicated, not share-token access to the Stats API. Teaching the existing endpoints to accept a token would loosen auth on every site-scoped route including those that write settings, add members and delete data. This one route has no auth middleware, validates the token itself, and cannot reach anything that writes because it is not one.
  • visitor_id and session_id are not dimensions and cannot become ones. Asking for one is refused by name with a reason, not as an unknown field — a BI tool is where a per-visitor export would be most tempting and least visible, so the restriction lives in the API rather than the connector.
  • Search Console data sits outside the erasure path, deliberately. Rows describe searches, not visits; there is no visitor dimension in the API or the table, and adding it to EVENT_TABLES would mean inventing a visitor_id to erase by. It also needs no disclosure floor — Google withholds low-volume queries before we see them, and the count of what was withheld is reported rather than silently missing.
  • Average position is impression-weighted and CTR is derived, never stored. A plain AVG(position) treats a query with 2 impressions like one with 20,000; a stored CTR is a second source for one number that disagrees after a partial import.

One thing worth flagging: the mutation that replaced the connector's token comparison with if (false) — turning the endpoint into an open data export — passed the entire unit suite while that check was inline in the handler. The decision is now a pure function with every branch tested by calling it. Second time this shape has bitten.

Verified: 105 new unit tests (583 total, tsc + lint clean), 30 checks against a fake Google that verifies our RS256 assertion with the matching public key, 25 against the running server for the connector's auth boundary, and 41 mutations each turning a test red.

Sign in to comment on this issue.