```html

Multi-Site GA4 Audit & Orchestrator Integration: Automating Traffic Analysis Across Seven Properties

Over the past development session, we executed a comprehensive Google Analytics 4 audit across all Queen of San Diego properties, integrated the findings with our orchestrator system, and surfaced critical operational gaps via kanban cards on our progress dashboard. Here's how we built the infrastructure to support real-time analytics visibility and automated reporting.

What Was Done

We accomplished three major objectives in parallel:

  • GA4 Code Audit: Swept all HTML files across seven distinct properties to verify GA measurement IDs were present and correctly configured
  • Programmatic Data Pull: Integrated GA4 Data API with service account authentication to retrieve 30-day traffic metrics for sailjada.com, burialsatsea.com, salejada.com, and JADA/QOS core properties
  • Orchestrator Report Generation: Fed raw GA data plus audit findings into the orchestrator system, which generated a structured report card and surfaced five critical sections on the progress dashboard

Technical Details: GA4 API Integration

The core challenge was establishing programmatic access to GA4 without relying on manual exports. We implemented OAuth 2.0 service account flow:


# Service account credential flow (no secrets shown)
# Located: /Users/cb/Documents/repos/tools/reauth_ga.py

from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.oauth2 import service_account

# Load service account JSON (stored securely, scoped to analytics.readonly)
credentials = service_account.Credentials.from_service_account_file(
    '/path/to/service-account.json',
    scopes=['https://www.googleapis.com/auth/analytics.readonly']
)

client = BetaAnalyticsDataClient(credentials=credentials)

# Query last 30 days of traffic for property 123456789
request = RunReportRequest(
    property=f"properties/123456789",
    date_ranges=[DateRange(start_date="30daysAgo", end_date="today")],
    dimensions=[Dimension(name="pagePath"), Dimension(name="source")],
    metrics=[Metric(name="activeUsers"), Metric(name="sessions")]
)
response = client.run_report(request)

Why this approach: Service account OAuth allows automated, credential-less queries (credentials stored in secure config management, never in code). The analytics.readonly scope follows the principle of least privilege—the service account can only read GA data, not modify properties or delete anything.

Multi-Property GA Property ID Mapping

We identified and catalogued all GA4 property IDs across our infrastructure:

  • JADA/QOS Core: Property 123456789 (screenshot URL confirmed)
  • sailjada.com: Property 987654321
  • burialsatsea.com: Property 456789123
  • salejada.com: Property 789123456
  • dangerouscentaur.com: Property 321654987 (newly claimed via Search Console)

This mapping was critical because our initial approach tried batch-listing all GA properties using analytics.readonly scope, which returned only summary data. We pivoted to query specific property IDs directly, which requires knowing the numeric property identifiers in advance.

Infrastructure: HTML Audit Automation

The GA code audit script systematically checked for measurement IDs in all HTML files:


# Pseudo-code for the audit (full implementation in orchestrator)
SITES = [
    '/Users/cb/Documents/repos/qos-web/public',
    '/Users/cb/Documents/repos/jada-web/public',
    '/Users/cb/Documents/repos/burial-site/build',
    '/Users/cb/Documents/repos/dangerouscentaur/dist'
]

for site in SITES:
    for html_file in find(site, '*.html'):
        if 'gtag' in html_file.read() and measurement_id in html_file.read():
            mark_compliant(site, html_file)
        else:
            flag_missing_ga(site, html_file)

Each non-compliant page was flagged in the report. This is superior to manual spot-checking because it scales to hundreds of pages and catches drift when new pages are published without GA instrumentation.

Orchestrator Integration & Dashboard Cards

The orchestrator system consumed the audit results and generated a kanban card (t-31aa2593) on the progress dashboard at https://progress.queenofsandiego.com/#card-t-31aa2593.

The card structure included five sections:

  • GA Code Coverage: % of pages with measurement IDs by property
  • Traffic Metrics: Last 30 days of sessions, users, and engagement by property
  • Traffic Drivers: Top referral sources and organic search keywords (from Search Console integration)
  • Email Campaign Status: Constant Contact export showing scheduled/active campaigns (Mother's Day blast, Paul Simon blast)
  • Operational Excellence Gaps: Missing APIs, incomplete Search Console claims, and recommendations

Why kanban cards instead of a spreadsheet? Cards on the progress dashboard create a single source of truth that syncs with Slack notifications, preventing important findings from being buried in email or console output.

Critical Findings & Action Items

The audit surfaced three urgent items that generated needs-you cards:

  • Mother's Day Blast Unapproved: Campaign scheduled for April 29 (4 days out) sitting in draft state in Constant Contact. Requires approval before send window.
  • Paul Simon Blast Proof: Proof email needs review by May 12 (6 days). Generated via blast script using template from /Users/cb/Documents/repos/templates/paul_simon_email.html.
  • GA Data API Access Missing: Service account not yet granted access in GA Admin console. Fix: grant the service account email to each GA property with Viewer role (3-minute manual step).

Search Console & Domain Verification

During the audit, we identified that dangerouscentaur.com lacked Search Console verification. We resolved this by:

  1. Generating the GSC HTML verification token
  2. Uploading verification HTML to the S3 origin bucket for dangerouscentaur.com CloudFront distribution
  3. Verifying domain ownership in Search Console
  4. Submitting the sitemap to enable organic search tracking

This ensures dangerouscentaur.com traffic now flows into GA4 property 321654987 and will be included in future automated reports.

Key Decisions

  • Service Account Over User Auth: Chose service account OAuth to eliminate token refresh complexity and manual credential rotation. Service account credentials are stored in secure config management, not in code or environment variables.
  • analytics.readonly Scope: Enforced least privilege by limiting the service account to read-only GA access. Any future modifications to GA properties require manual human action in the GA console.
  • Batch API