---
name: media-plan-to-tracker-sync
description: "Sync a media plan into a tracker (Airtable if connected): campaigns, budgets, flight dates, status — one source of truth the team can see."
license: MIT
compatibility: "Works with any Agent-Skills-compatible AI (Claude, ChatGPT, Cursor, Codex, and more). Ad-account actions run through Adspirer."
metadata:
  author: "Adspirer"
  version: "1.0.0"
  adspirer_category: "agency-ops"
  adspirer_source: "https://www.adspirer.com/skills/media-plan-to-tracker-sync"
  adspirer_connection_url: "https://adspirer.ai/sign-up"
  adspirer_primary_keyword: "campaign tracker"
  adspirer_secondary_keywords: "media plan tracker, advertising campaign tracker"
  adspirer_launch_wave: "1"
  adspirer_kind: "skill"
  adspirer_level: "operator"
  adspirer_platforms: "google,meta"
  adspirer_supported_clients: "claude,claude-code,chatgpt,codex,cursor,gemini,windsurf"
  adspirer_summary: "Sync a media plan into a tracker (Airtable if connected): campaigns, budgets, flight dates, status — one source of truth the team can see."
  adspirer_connections_required: "airtable,adspirer"
  adspirer_connections_optional: ""
---

# Media-Plan-to-Tracker Sync

## Use this when

Use when a media plan for a Google Ads and/or Meta account has been approved (or revised) and the team needs a single, always-current source of truth for campaign structure, budgets, flight dates, and status — visible to strategists, account managers, and clients who don't have platform access. Typical triggers: a new plan was just finalized and needs to hit the tracker before campaigns go live; a plan changed mid-flight (budget shift, paused campaign, extended flight) and the tracker has drifted from reality; or a recurring weekly sync to keep Airtable aligned with what's actually running. Not for building the media plan itself (use the plan-generation skill for that) and not for pushing budget/status changes INTO the ad platforms — this skill is one-directional, platform-to-tracker, reporting what's live, never writing to Google/Meta.

## What you need

1) Adspirer connected to the Google Ads and/or Meta ad accounts covered by the media plan, with read access confirmed on each. 2) An Airtable base designated as the tracker — either an existing base/table the team already uses (get its name or ID from the user) or explicit permission to create a new base named for the client/plan. 3) The source media plan: either the plan doc/spreadsheet the campaigns were built from, or (if no separate doc exists) an agreement that "live campaign structure in the ad accounts" IS the plan of record. 4) Know the tracker's intended schema up front, or agree on a default: Campaign Name, Platform, Ad Account, Objective, Budget (Daily/Lifetime), Flight Start, Flight End, Status (Live/Paused/Ended/Learning/Not Started), Delivery Notes, Last Synced. 5) Clarify sync direction and cadence: one-time push vs. recurring (daily/weekly) refresh, and what happens to rows for campaigns that no longer exist (archive, don't delete).

## Procedure

1. Confirm scope with the user: which ad accounts (Google, Meta, or both), which campaigns count as "in the plan" (all active campaigns in the account, or a named subset — new accounts often have legacy campaigns outside the current plan that shouldn't get swept in), and the target Airtable base/table.
**Connect Airtable:** this step uses Airtable. Add the Airtable MCP/connector to your agent — setup: https://support.airtable.com/docs/using-the-airtable-mcp-server, then continue. Skip only if a step says it's optional.
2. Pull live campaign data for each in-scope account.
**Connect Adspirer (the ads connection):** when you reach a step that touches a live ad account, connect Adspirer first. In Claude Code, run `/plugin marketplace add anthropics/claude-plugins-community` then `/plugin install adspirer-ads-agent@claude-community`. In claude.ai or ChatGPT, add the Adspirer connector from https://www.adspirer.com/docs/ai-clients. Any other agent: follow https://www.adspirer.com/docs/ai-clients for your client. Sign-in link for the user: https://adspirer.ai/sign-up. Do not proceed with live ad-account steps until the connection is confirmed.
For Google Ads: campaign name, campaign ID, campaign status (Enabled/Paused/Removed), campaign type, budget (note whether it's a shared budget — shared budgets need a flag so the tracker doesn't show the same dollar figure duplicated across every campaign sharing it), start date, end date (many campaigns have no end date set — record as "Ongoing," don't leave blank, which reads as missing data). For Meta: campaign name, campaign ID, effective status (distinguish ACTIVE from CAMPAIGN_PAUSED from adset-level PAUSED — a campaign can show ACTIVE while every ad set under it is paused, which will read as "live" in the tracker but isn't actually delivering; check ad-set-level status if the campaign has zero recent spend despite an ACTIVE status), objective, daily or lifetime budget, and learning-phase status per ad set (a campaign that just launched or was just edited may be showing $0 optimized delivery for 3-7 days — flag "Learning Phase" in delivery notes rather than "Underdelivering," which would misread as a problem).
3. Cross-reference against the media plan source (doc or prior tracker state) to classify each live campaign as: (a) matches plan as expected, (b) live but not in the original plan — flag for the user, don't silently add it as if it were planned, (c) in the plan but not yet live — likely upcoming flight, keep as "Not Started" with its planned start date, (d) in the plan and in a prior tracker sync but now missing/removed from the account — never delete this row; mark Status = "Ended/Removed" and set an end-date note, preserving history.
4. Map platform budget fields to the tracker's single Budget column consistently: Google daily budgets and Meta daily budgets both go in Daily Budget; Meta lifetime-budget campaigns go in a separate Lifetime Budget column, not divided into a fake daily number — an invented daily-equivalent is not a real field and will mislead pacing conversations.
5. Write the batch to Airtable: update existing rows matched by Campaign ID (stable identifier — never match on Campaign Name, which gets renamed) rather than creating duplicates, insert new rows for previously-unseen live campaigns, and stamp every touched row's Last Synced field with today's date/time so the team can see freshness at a glance.
6. Reconcile counts before finishing: total rows written/updated should equal (in-scope live campaigns) + (planned-not-started rows) + (previously-tracked rows now ended). If the count doesn't reconcile, stop and report the discrepancy rather than force-writing a mismatched sync.
7. Produce a short sync summary for the user: campaigns added, campaigns updated, campaigns flagged as live-but-unplanned, campaigns marked ended/removed, and any accounts that failed to pull (auth/permission errors) — this is a diagnostic list, not a compliance verdict on the plan's performance.
8. If this is a recurring sync (not one-time), confirm the cadence with the user and note it in the summary; this skill performs one sync run per execution — recurring cadence is a scheduling concern outside this run, not something the skill decides or auto-continues on its own.

## Fixed checks

- Every row written to Airtable is checked against a live pull from the Google Ads / Meta account in the same run — never against the previous tracker state or the agent's memory of the plan, so a stale tracker can't silently perpetuate itself.
- Campaign status shown in the tracker reflects actual delivery signal, not just the top-level status flag: a Meta campaign with effective_status=ACTIVE but zero spend in the last 3+ days (outside learning phase) gets flagged "Active/No Delivery" rather than a bare "Live," since a client scanning the tracker for what's actually running needs that distinction.
- Shared Google Ads budgets are checked against the account's actual shared-budget grouping (not assumed per-campaign) before being written, so the tracker never shows the same budget dollar amount multiple times as if each campaign had its own.
- Row-matching for updates is verified by Campaign ID equality, not name — if a name collision or rename is detected between the pull and the existing tracker row, it's flagged for the user rather than auto-merged.
- Flight dates written to the tracker are the platform's actual start/end date fields, not inferred from when the sync happened to run.

## Stop conditions

- Success: all in-scope campaigns pulled from Google/Meta, reconciled against the plan and prior tracker state, and written to Airtable with a clean row-count reconciliation; summary delivered.
- No-op: a re-run finds zero changes since the last sync (no new campaigns, no status/budget/date changes) — report "tracker already current as of [last sync time]" and write nothing.
- Blocked: Adspirer lacks read access to one or more in-scope ad accounts, the designated Airtable base/table doesn't exist or isn't accessible, or the row-count reconciliation in step 6 fails — stop and report the specific blocker rather than partially syncing.
- Needs-approval: a new Airtable base or table must be created (no existing tracker was designated), or campaigns are found live in the account that don't appear anywhere in the source media plan (possible scope creep or an unapproved launch) — surface these for the user to confirm before writing.

## Approval boundaries

Read-only against the ad platforms — this skill only pulls campaign/budget/status data from Google Ads and Meta via Adspirer and never creates, edits, pauses, or budgets-changes any live ad entity. Writes are confined to the Airtable tracker (a reporting surface, not an ad platform) and are limited to the fields this skill owns: campaign metadata, budget figures, flight dates, status, and sync timestamp — it never touches unrelated tracker columns (e.g., a client-facing notes or invoicing column) that the team maintains manually. Creating a brand-new Airtable base/table, and any row flagged as "live but not in the plan," requires explicit user confirmation before being written per the Needs-approval step above.

## What you get

An Airtable tracker (existing or newly created, per your choice) with one row per campaign in scope, holding: Campaign Name, Platform, Ad Account, Objective, Daily/Lifetime Budget (correctly split, shared budgets flagged), Flight Start/End dates, Status (Live / Active-No-Delivery / Learning Phase / Not Started / Ended-Removed), Delivery Notes, and a Last Synced timestamp per row. Plus a run summary: counts of rows added/updated/ended, any live-but-unplanned campaigns flagged for review, and any accounts that failed to sync — so the team has both the tracker itself and an honest diff of what changed this run.
