---
# === IDENTITY ===
id: software/startup-dashboard/marketing-operations-dashboard/2026
canonical_question: "How do I build a marketing ops dashboard — multi-channel aggregation, attribution, budget tracking, content management?"
aliases:
  - "marketing operations dashboard setup"
  - "multi-channel marketing dashboard build"
  - "marketing attribution dashboard implementation"
  - "startup marketing analytics dashboard"
entity_type: execution_recipe
domain: software > startup-dashboard > marketing-operations-dashboard
region: global
jurisdiction: global
temporal_scope: 2025-2026

# === VERIFICATION ===
last_verified: 2026-03-12
confidence: 0.88
version: 1.0
first_published: 2026-03-12

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: evolving
  last_breaking_change: "Google Analytics 4 fully replaced Universal Analytics (July 2024); Meta Conversions API required alongside pixel (Q1 2025)"
  next_review: 2026-09-08
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "Google Analytics 4 Data API quota: 10,000 requests/day per property. Batch requests and cache responses for dashboard refresh."
  - "Meta/Facebook Marketing API rate limits: 200 calls/hour per ad account. Use batch endpoints for multi-campaign data."
  - "Attribution windows differ across platforms — Google default 30-day click, Meta default 7-day click / 1-day view. Normalize before comparing."
  - "Cookie consent laws (GDPR, ePrivacy) reduce tracked attribution data by 20-40% in EU markets. Account for unattributed conversions."
  - "Budget data must reconcile with accounting within 48 hours — stale budget figures cause overspend decisions."

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs a marketing strategy, not a dashboard"
    use_instead: "business/startup-marketing/marketing-strategy-framework-for-startups/2026"
  - condition: "User wants only Google Analytics setup without multi-channel aggregation"
    use_instead: "software/landing-pages/analytics-implementation/2026"
  - condition: "User is building an enterprise marketing data warehouse"
    use_instead: "software/system-design/data-warehouse-data-lake/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: ad_platforms
    question: "Which advertising platforms does the team use?"
    type: choice
    options: ["Google Ads only", "Google + Meta", "Google + Meta + LinkedIn", "Google + Meta + TikTok", "all major platforms"]
  - key: technical_skill
    question: "What is the user's technical skill level?"
    type: choice
    options: ["non-technical (no-code only)", "semi-technical (can edit code)", "developer (can write code)"]
  - key: budget_for_tools
    question: "What's the dashboard tool budget?"
    type: choice
    options: ["free tier only", "up to $50/month", "up to $200/month", "no limit"]
  - key: monthly_ad_spend
    question: "What's the approximate monthly ad spend?"
    type: choice
    options: ["< $1,000/month", "$1,000-$10,000/month", "$10,000-$50,000/month", "$50,000+/month"]

# === EXECUTION METADATA ===
execution:
  required_inputs:
    - name: "Ad platform accounts with API access"
      source: "user's existing ad accounts"
      format: "API credentials or OAuth tokens"
    - name: "Google Analytics 4 property"
      source: "user's GA4 setup"
      format: "GA4 property ID and service account key"
    - name: "Monthly marketing budget"
      source: "user's financial plan"
      format: "budget breakdown by channel"

  outputs:
    - name: "Marketing operations dashboard"
      format: "deployed web application or embedded BI dashboard"
      description: "Live dashboard showing spend vs budget by channel, ROAS, CAC, attribution by touchpoint, content performance, and funnel conversion"
    - name: "Multi-channel data pipeline"
      format: "scheduled ETL jobs"
      description: "Automated data extraction from ad platforms and analytics to unified data store"
    - name: "Attribution analysis view"
      format: "dashboard component"
      description: "Multi-touch attribution showing first-touch, last-touch, and linear attribution models"

  tools_required:
    - name: "Retool or Metabase"
      purpose: "Dashboard UI builder"
      tier: "free"
      cost: "$0 (free tier) to $10/user/month"
      alternatives: ["Google Looker Studio", "Grafana", "Preset (hosted Superset)"]
    - name: "PostgreSQL"
      purpose: "Data warehouse for multi-channel marketing data"
      tier: "free"
      cost: "$0 (Supabase free tier) to $25/month"
      alternatives: ["BigQuery", "ClickHouse", "DuckDB"]
    - name: "n8n or Airbyte"
      purpose: "ETL for ad platform data extraction"
      tier: "free"
      cost: "$0 (self-hosted) to $30/month"
      alternatives: ["Fivetran", "Supermetrics", "Funnel.io"]

  credentials_needed:
    - service: "Google Analytics 4"
      type: "Service account JSON key"
      where_to_get: "https://console.cloud.google.com (APIs & Services > Credentials)"
      free_tier_limits: "10,000 API requests/day"
    - service: "Google Ads"
      type: "OAuth2 + developer token"
      where_to_get: "https://ads.google.com (Tools > API Center)"
      free_tier_limits: "Basic access: 15,000 requests/day"
    - service: "Meta Marketing API"
      type: "System user token"
      where_to_get: "https://business.facebook.com (Business Settings > System Users)"
      free_tier_limits: "200 calls/hour per ad account"

  estimated_duration: "6-10 hours for initial setup; 2-3 hours per additional ad platform"
  estimated_cost: "$0 (all free tiers) to $100/month (paid tiers with multiple connectors)"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/startup-dashboard/marketing-operations-dashboard/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-03-12)"

# === RELATED UNITS ===
related_kos:
  depends_on:
    - id: "software/startup-dashboard/sales-operations-dashboard/2026"
      label: "Sales dashboard for pipeline attribution"
  feeds_into:
    - id: "software/startup-dashboard/financial-operations-dashboard/2026"
      label: "Finance dashboard consumes marketing spend data"
  related_to:
    - id: "business/marketing-ops/marketing-metrics-benchmarks/2026"
      label: "Marketing KPI benchmarks — CAC by channel, conversion rates, MQL:SQL ratios by industry"

# === SOURCES ===
sources:
  - id: src1
    title: "Google Analytics 4 Data API Documentation"
    author: Google
    url: https://developers.google.com/analytics/devguides/reporting/data/v1
    type: official_docs
    published: 2025-11-01
    reliability: authoritative
  - id: src2
    title: "Meta Marketing API Reference"
    author: Meta
    url: https://developers.facebook.com/docs/marketing-apis
    type: official_docs
    published: 2025-12-01
    reliability: authoritative
  - id: src3
    title: "Multi-Channel Attribution Tools Guide 2026"
    author: Cometly
    url: https://www.cometly.com/post/multi-channel-attribution-tools
    type: industry_guide
    published: 2025-11-15
    reliability: high
  - id: src4
    title: "Marketing Dashboard Software Platforms 2026"
    author: monday.com
    url: https://monday.com/blog/project-management/marketing-dashboard-software/
    type: industry_guide
    published: 2025-10-20
    reliability: high
  - id: src5
    title: "Marketing Attribution Models Guide"
    author: KEO Marketing
    url: https://keomarketing.com/marketing-analytics-attribution-guide-150191-3
    type: industry_guide
    published: 2025-09-01
    reliability: high
  - id: src6
    title: "Ruler Analytics: Marketing Attribution Software"
    author: Ruler Analytics
    url: https://www.ruleranalytics.com/blog/analytics/marketing-attribution-software/
    type: industry_guide
    published: 2025-10-15
    reliability: high
---

# Marketing Operations Dashboard

## Purpose

This recipe produces a multi-channel marketing operations dashboard that aggregates spend, impressions, clicks, and conversions from all active ad platforms, overlays Google Analytics 4 session and event data, calculates channel-level ROAS and blended CAC, visualizes attribution across touchpoints, and tracks budget consumption vs plan. The output is a deployed dashboard giving marketing leadership real-time visibility into which channels drive revenue and where budget is being wasted. [src1]

## Prerequisites
<!-- Agents: verify ALL prerequisites before executing. Missing prerequisites = failed execution. -->

- [ ] **Google Analytics 4 property** with data collection active — [GA4 setup guide](https://support.google.com/analytics/answer/9304153)
- [ ] **Ad platform accounts with API access** — Google Ads (developer token), Meta Business (system user token), LinkedIn/TikTok as applicable
- [ ] **PostgreSQL database** — free tier at [Supabase](https://supabase.com) or [Neon](https://neon.tech) (1GB sufficient for <$50K/month spend)
- [ ] **Dashboard tool account** — [Retool](https://retool.com), [Metabase](https://www.metabase.com), or [Google Looker Studio](https://lookerstudio.google.com)
- [ ] **Monthly marketing budget breakdown** by channel — needed for budget vs actual tracking
- [ ] **Conversion tracking configured** in GA4 — at least sign_up, purchase, or lead events firing correctly

## Constraints
<!-- Hard rules. Agents: enforce throughout execution. Violating these = broken output or legal risk. -->

- Google Analytics 4 Data API: 10,000 requests/day per property. Batch dimensions/metrics into single requests. Cache responses for 15+ minutes. [src1]
- Meta Marketing API: 200 calls/hour per ad account. Use batch endpoints for multi-campaign data pulls. [src2]
- Attribution windows vary by platform — Google Ads default 30-day click, Meta default 7-day click / 1-day view. Normalize attribution windows before cross-channel comparison. [src3]
- Cookie consent reduces trackable data by 20-40% in EU markets (GDPR/ePrivacy). Build "dark traffic" adjustment factor into attribution models. [src5]
- Budget figures must reconcile with accounting within 48 hours. Use platform-reported spend (not estimated) for budget tracking accuracy.

## Tool Selection Decision

```
Which path?
├── User is non-technical AND budget = free
│   └── PATH A: No-Code Free — Google Looker Studio + native connectors
├── User is non-technical AND budget > $0
│   └── PATH B: No-Code Paid — Looker Studio + Supermetrics ($39/mo)
├── User is semi-technical or developer AND budget = free
│   └── PATH C: Code + Free — Metabase + PostgreSQL + n8n
└── User is developer AND budget > $0
    └── PATH D: Code + Paid — Retool + PostgreSQL + Airbyte + dbt
```

| Path | Tools | Cost | Speed | Output Quality |
|------|-------|------|-------|---------------|
| A: No-Code Free | Looker Studio + GA4/Ads connectors | $0 | 3-4 hours | Basic — Google channels only, no custom attribution |
| B: No-Code Paid | Looker Studio + Supermetrics | $39/mo | 4-6 hours | Good — multi-channel, limited custom models |
| C: Code + Free | Metabase + PostgreSQL + n8n | $0 | 8-10 hours | Good — full SQL control, custom attribution |
| D: Code + Paid | Retool + PostgreSQL + Airbyte + dbt | $50-100/mo | 6-8 hours | Excellent — automated transforms, custom components |

## Execution Flow

### Step 1: Design the Multi-Channel Data Model

**Duration**: 45-90 minutes
**Tool**: SQL client

Create a unified marketing data schema that normalizes data from all ad platforms into comparable metrics.

```sql
-- Marketing channel performance data model
CREATE TABLE channel_spend (
  id SERIAL PRIMARY KEY,
  date DATE NOT NULL,
  platform TEXT NOT NULL, -- 'google_ads', 'meta', 'linkedin', 'tiktok', 'organic'
  campaign_id TEXT,
  campaign_name TEXT,
  ad_group TEXT,
  impressions INTEGER DEFAULT 0,
  clicks INTEGER DEFAULT 0,
  spend NUMERIC(10,2) DEFAULT 0,
  conversions NUMERIC(10,2) DEFAULT 0,
  conversion_value NUMERIC(12,2) DEFAULT 0,
  currency TEXT DEFAULT 'USD',
  synced_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE ga4_events (
  id SERIAL PRIMARY KEY,
  date DATE NOT NULL,
  event_name TEXT NOT NULL, -- 'session_start', 'page_view', 'sign_up', 'purchase'
  source TEXT,
  medium TEXT,
  campaign TEXT,
  event_count INTEGER DEFAULT 0,
  users INTEGER DEFAULT 0,
  sessions INTEGER DEFAULT 0,
  revenue NUMERIC(12,2) DEFAULT 0
);

CREATE TABLE marketing_budget (
  id SERIAL PRIMARY KEY,
  month DATE NOT NULL, -- first day of month
  platform TEXT NOT NULL,
  budget_amount NUMERIC(10,2) NOT NULL,
  notes TEXT
);

CREATE TABLE attribution_touches (
  id SERIAL PRIMARY KEY,
  conversion_id TEXT NOT NULL,
  touch_order INTEGER NOT NULL,
  channel TEXT NOT NULL,
  campaign TEXT,
  touch_date TIMESTAMPTZ NOT NULL,
  conversion_date TIMESTAMPTZ,
  conversion_value NUMERIC(12,2) DEFAULT 0,
  -- Attribution credit per model
  first_touch_credit NUMERIC(5,4) DEFAULT 0,
  last_touch_credit NUMERIC(5,4) DEFAULT 0,
  linear_credit NUMERIC(5,4) DEFAULT 0
);

CREATE TABLE content_performance (
  id SERIAL PRIMARY KEY,
  date DATE NOT NULL,
  content_url TEXT NOT NULL,
  content_title TEXT,
  page_views INTEGER DEFAULT 0,
  unique_users INTEGER DEFAULT 0,
  avg_engagement_time NUMERIC(8,2) DEFAULT 0,
  conversions INTEGER DEFAULT 0,
  source TEXT
);

-- Indexes
CREATE INDEX idx_spend_date_platform ON channel_spend(date, platform);
CREATE INDEX idx_spend_campaign ON channel_spend(campaign_id);
CREATE INDEX idx_ga4_date_event ON ga4_events(date, event_name);
CREATE INDEX idx_budget_month ON marketing_budget(month, platform);
CREATE INDEX idx_attribution_conversion ON attribution_touches(conversion_id);
CREATE INDEX idx_content_date ON content_performance(date);
```

**Verify**: All tables created without errors. `\dt` shows 5 tables.
**If failed**: Check PostgreSQL permissions. Supabase free tier may require enabling tables via SQL Editor.

### Step 2: Build Multi-Platform Data Sync

**Duration**: 2-4 hours (1 hour per platform)
**Tool**: n8n, Airbyte, or custom scripts
**Rate limit**: Google Ads = 15,000/day, Meta = 200/hour, GA4 = 10,000/day

Google Ads sync (n8n):

```javascript
// n8n HTTP Request: Google Ads API via REST
// POST https://googleads.googleapis.com/v16/customers/{customer_id}/googleAds:searchStream
// Headers: Authorization: Bearer {ACCESS_TOKEN}, developer-token: {DEV_TOKEN}
// Body: { "query": "SELECT campaign.name, campaign.id, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions, metrics.conversions_value, segments.date FROM campaign WHERE segments.date DURING LAST_7_DAYS" }

const rows = items.map(item => ({
  date: item.json.campaign.segments.date,
  platform: 'google_ads',
  campaign_id: item.json.campaign.campaign.id,
  campaign_name: item.json.campaign.campaign.name,
  impressions: item.json.campaign.metrics.impressions,
  clicks: item.json.campaign.metrics.clicks,
  spend: item.json.campaign.metrics.costMicros / 1000000,
  conversions: item.json.campaign.metrics.conversions,
  conversion_value: item.json.campaign.metrics.conversionsValue
}));
```

Meta Marketing API sync:

```javascript
// GET https://graph.facebook.com/v19.0/act_{ad_account_id}/insights
// Params: fields=campaign_name,campaign_id,impressions,clicks,spend,actions,action_values
//         &time_range={"since":"2026-03-05","until":"2026-03-12"}&level=campaign
// Headers: Authorization: Bearer {META_TOKEN}

const rows = items.map(item => ({
  date: item.json.date_start,
  platform: 'meta',
  campaign_id: item.json.campaign_id,
  campaign_name: item.json.campaign_name,
  impressions: parseInt(item.json.impressions),
  clicks: parseInt(item.json.clicks),
  spend: parseFloat(item.json.spend),
  conversions: item.json.actions?.find(a => a.action_type === 'purchase')?.value || 0,
  conversion_value: item.json.action_values?.find(a => a.action_type === 'purchase')?.value || 0
}));
```

GA4 Data API sync:

```javascript
// POST https://analyticsdata.googleapis.com/v1beta/properties/{property_id}:runReport
// Body: { "dateRanges": [{"startDate": "7daysAgo", "endDate": "today"}],
//         "dimensions": [{"name": "date"}, {"name": "sessionSource"}, {"name": "sessionMedium"}, {"name": "sessionCampaignName"}, {"name": "eventName"}],
//         "metrics": [{"name": "eventCount"}, {"name": "activeUsers"}, {"name": "sessions"}] }

// Schedule: daily at 6:00 AM (GA4 data typically finalizes 24-48h after collection)
```

**Expected output**: channel_spend populated with last 7 days from all active platforms. ga4_events populated with session and conversion data.
**Verify**: `SELECT platform, SUM(spend) FROM channel_spend WHERE date > CURRENT_DATE - 7 GROUP BY platform;` returns rows for each configured platform.
**If failed**: Check OAuth tokens haven't expired. Google tokens expire every 60 minutes — use refresh token flow.

### Step 3: Build Attribution and Analytics Queries

**Duration**: 1-2 hours
**Tool**: SQL

```sql
-- 1. Channel performance summary (spend, ROAS, CPA)
SELECT
  platform,
  SUM(spend) as total_spend,
  SUM(clicks) as total_clicks,
  SUM(conversions) as total_conversions,
  SUM(conversion_value) as total_revenue,
  ROUND(SUM(conversion_value) / NULLIF(SUM(spend), 0), 2) as roas,
  ROUND(SUM(spend) / NULLIF(SUM(conversions), 0), 2) as cpa,
  ROUND(SUM(clicks)::NUMERIC / NULLIF(SUM(impressions), 0) * 100, 2) as ctr
FROM channel_spend
WHERE date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY platform
ORDER BY total_spend DESC;

-- 2. Budget vs actual by channel
SELECT
  b.platform,
  b.budget_amount,
  COALESCE(s.actual_spend, 0) as actual_spend,
  ROUND((COALESCE(s.actual_spend, 0) / b.budget_amount) * 100, 1) as pct_consumed,
  b.budget_amount - COALESCE(s.actual_spend, 0) as remaining
FROM marketing_budget b
LEFT JOIN (
  SELECT platform, SUM(spend) as actual_spend
  FROM channel_spend
  WHERE date >= DATE_TRUNC('month', CURRENT_DATE)
  GROUP BY platform
) s ON b.platform = s.platform
WHERE b.month = DATE_TRUNC('month', CURRENT_DATE);

-- 3. Daily spend trend with 7-day moving average
SELECT
  date,
  SUM(spend) as daily_spend,
  AVG(SUM(spend)) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as moving_avg_7d
FROM channel_spend
WHERE date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY date
ORDER BY date;

-- 4. Funnel conversion rates (GA4 events)
SELECT
  source,
  medium,
  SUM(CASE WHEN event_name = 'session_start' THEN event_count ELSE 0 END) as sessions,
  SUM(CASE WHEN event_name = 'sign_up' THEN event_count ELSE 0 END) as signups,
  SUM(CASE WHEN event_name = 'purchase' THEN event_count ELSE 0 END) as purchases,
  ROUND(SUM(CASE WHEN event_name = 'sign_up' THEN event_count ELSE 0 END)::NUMERIC /
    NULLIF(SUM(CASE WHEN event_name = 'session_start' THEN event_count ELSE 0 END), 0) * 100, 2) as signup_rate,
  ROUND(SUM(CASE WHEN event_name = 'purchase' THEN event_count ELSE 0 END)::NUMERIC /
    NULLIF(SUM(CASE WHEN event_name = 'sign_up' THEN event_count ELSE 0 END), 0) * 100, 2) as purchase_rate
FROM ga4_events
WHERE date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY source, medium
ORDER BY sessions DESC;

-- 5. Content performance ranking
SELECT
  content_title,
  content_url,
  SUM(page_views) as total_views,
  SUM(unique_users) as total_users,
  ROUND(AVG(avg_engagement_time), 1) as avg_engagement_sec,
  SUM(conversions) as total_conversions,
  ROUND(SUM(conversions)::NUMERIC / NULLIF(SUM(unique_users), 0) * 100, 2) as conversion_rate
FROM content_performance
WHERE date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY content_title, content_url
ORDER BY total_conversions DESC
LIMIT 20;
```

**Verify**: Channel performance query returns rows with non-zero spend for each platform.
**If failed**: Verify platform names match between channel_spend and marketing_budget tables.

### Step 4: Assemble the Dashboard UI

**Duration**: 1-2 hours
**Tool**: Retool, Metabase, or Looker Studio

```
Dashboard Layout:
┌─────────────────────────────────────────────────────┐
│  KPI Row: Total Spend | Blended ROAS | Blended CAC  │
│           Conversions (30d) | Budget Remaining       │
├─────────────────────────┬───────────────────────────┤
│  Channel ROAS Bar Chart │  Budget vs Actual by       │
│  (horizontal, colored)  │  Channel (grouped bar)     │
├─────────────────────────┴───────────────────────────┤
│  Daily Spend Trend Line (with 7d moving average)     │
├─────────────────────────┬───────────────────────────┤
│  Funnel Conversion by   │  Content Performance       │
│  Source/Medium (table)  │  Top 10 (table)            │
├─────────────────────────┴───────────────────────────┤
│  Campaign Detail Table (sortable, filterable)        │
└─────────────────────────────────────────────────────┘
```

Date range selector at top: Last 7 days, Last 30 days, This month, Custom range. Platform filter: All, Google Ads, Meta, LinkedIn, TikTok.

**Verify**: All sections render. KPI values match platform-reported totals within 5%.
**If failed**: Check query bindings and date range parameter passing.

### Step 5: Configure Budget Alerts

**Duration**: 30-60 minutes
**Tool**: n8n or Pipedream + Slack/Email

```javascript
// Daily budget check (run at 9:00 AM)
// Trigger alert if:
// 1. Any channel has consumed > 80% of monthly budget with > 10 days remaining
// 2. ROAS for any channel dropped below 1.0 (losing money)
// 3. Daily spend anomaly: > 2x the 7-day average

const alerts = [];
for (const channel of channelData) {
  const daysRemaining = daysInMonth - currentDay;
  const pctConsumed = channel.actual_spend / channel.budget_amount;

  if (pctConsumed > 0.8 && daysRemaining > 10) {
    alerts.push(`${channel.platform}: ${(pctConsumed*100).toFixed(0)}% budget consumed with ${daysRemaining} days left`);
  }
  if (channel.roas < 1.0 && channel.total_spend > 100) {
    alerts.push(`${channel.platform}: ROAS ${channel.roas} — spending more than earning`);
  }
}
```

**Verify**: Test alert fires with sample data. Slack channel receives notification.
**If failed**: Check Slack webhook URL. For email, verify SMTP configuration.

### Step 6: Deploy and Share Access

**Duration**: 30 minutes
**Tool**: Dashboard platform deployment settings

- Share dashboard URL with marketing team
- Set viewer permissions for content creators, editor for marketing leads
- Configure email subscription for weekly marketing report summary
- Enable mobile access for on-the-go budget monitoring

**Verify**: Non-admin marketing team member can view dashboard with current data.

## Output Schema

```json
{
  "output_type": "marketing_operations_dashboard",
  "format": "deployed web application",
  "components": [
    {"name": "channel_performance", "type": "table", "description": "Platform-level spend, ROAS, CPA, CTR metrics", "required": true},
    {"name": "budget_tracker", "type": "chart", "description": "Grouped bar chart of budget vs actual spend by channel", "required": true},
    {"name": "spend_trend", "type": "chart", "description": "Daily spend line chart with 7-day moving average", "required": true},
    {"name": "funnel_analysis", "type": "table", "description": "Source/medium funnel from sessions to conversions", "required": true},
    {"name": "content_ranking", "type": "table", "description": "Top content by conversion rate and engagement", "required": true},
    {"name": "kpi_cards", "type": "metrics", "description": "Total spend, blended ROAS, blended CAC, conversions", "required": true}
  ],
  "refresh_interval": "60 minutes (ad platform data lags 3-6 hours)",
  "data_source": "PostgreSQL staging database synced from ad platforms and GA4"
}
```

## Quality Benchmarks

| Quality Metric | Minimum Acceptable | Good | Excellent |
|---------------|-------------------|------|-----------|
| Data freshness | < 24 hour lag from platforms | < 6 hour lag | < 3 hour lag |
| Spend accuracy | Within 10% of platform totals | Within 5% | Within 1% (full reconciliation) |
| Channel coverage | Google Ads + 1 channel | All paid channels | + organic + referral |
| Attribution model | Last-click only | First + last touch | Multi-touch linear/positional |
| Budget reconciliation | Weekly | Daily | Real-time with accounting sync |

**If below minimum**: Check ETL job execution logs. Spend discrepancies usually indicate timezone mismatches (UTC vs local) or currency conversion issues.

## Error Handling

| Error | Likely Cause | Recovery Action |
|-------|-------------|----------------|
| Google Ads API 403 (permission denied) | Developer token not approved or account not linked | Apply for basic access at Google Ads API Center, link MCC account |
| Meta API 190 (token expired) | System user token expired (60-day default) | Generate new long-lived token via Business Settings > System Users |
| GA4 API quota exceeded | Too many report requests in 24-hour window | Reduce report frequency, batch dimensions into fewer requests |
| Spend data missing for a platform | ETL job failed silently or platform API changed | Check n8n/Airbyte execution logs, verify API endpoint versions |
| Attribution totals exceed actual conversions | Double-counting from overlapping attribution windows | Deduplicate conversion_id in attribution_touches table |
| Budget vs actual shows negative remaining | Month boundary issue in date filter | Verify DATE_TRUNC('month') alignment with billing cycle |

## Cost Breakdown

| Component | Free Tier | Paid Tier | At Scale ($50K+/mo spend) |
|-----------|-----------|-----------|----------|
| Dashboard tool (Retool) | $0 (5 users) | $10/user/mo | $100/mo (10 users) |
| Database (Supabase) | $0 (500MB) | $25/mo | $25/mo |
| ETL (n8n self-hosted) | $0 | $20/mo (cloud) | $50/mo |
| Supermetrics (if Path B) | N/A | $39/mo | $99/mo (more connectors) |
| **Total** | **$0** | **$55-95/mo** | **$175-275/mo** |

## Anti-Patterns

### Wrong: Comparing ROAS across platforms without normalizing attribution windows
Google's 30-day click attribution and Meta's 7-day click / 1-day view attribution measure fundamentally different things. Comparing raw ROAS numbers leads to systematically underfunding the platform with the shorter window. [src3]

### Correct: Normalize to a consistent attribution window
Set all platforms to the same window (e.g., 7-day click) for apples-to-apples comparison, or use a separate multi-touch attribution model that credits all touchpoints consistently.

### Wrong: Using platform-reported conversions as source of truth
Each platform claims credit for the same conversion. Summing Google + Meta conversions double-counts 20-40% of actual conversions. [src5]

### Correct: Use GA4 or server-side tracking as the single source of truth
Attribute conversions from one system (GA4 or your database), then map platform-reported metrics to that baseline for each channel's contribution.

## When This Matters

Use when a startup is spending on two or more marketing channels and needs a unified view of performance, attribution, and budget consumption. Requires at least one ad platform with active campaigns and GA4 collecting conversion events. This recipe builds the dashboard — for marketing strategy or channel selection, use a playbook card instead.

## Related Units

- [Sales Operations Dashboard](/software/startup-dashboard/sales-operations-dashboard/2026) — receives lead attribution data from marketing
- [Financial Operations Dashboard](/software/startup-dashboard/financial-operations-dashboard/2026) — consumes marketing spend for P&L
- [Marketing Benchmarks](/business/marketing-ops/marketing-benchmarks/2026) — benchmark targets for ROAS, CAC, CTR
