From toprank
Google Ads account audit and business context setup. Run this first — it gathers business information, analyzes account health, and saves context that all other ads skills reuse. Trigger on "audit my ads", "ads audit", "set up my ads", "onboard", "account overview", "how's my account", "ads health check", "what should I fix in my ads", or when the user is new to NotFair and hasn't run an audit before. Also trigger proactively when other ads skills detect that business-context.json is missing.
How this skill is triggered — by the user, by Claude, or both
Slash command
/toprank:audit <account name or 'audit my ads'><account name or 'audit my ads'>The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Diagnose account health and persist business context for downstream skills (`/google-ads`, `/google-ads-copy`, `/google-ads-landing`). **Read-only** — never mutates the account. The user runs `/google-ads` to execute fixes you recommend.
Diagnose account health and persist business context for downstream skills (/google-ads, /google-ads-copy, /google-ads-landing). Read-only — never mutates the account. The user runs /google-ads to execute fixes you recommend.
Follow ../shared/preamble.md — MCP detection, API key, account selection.
| Artifact | Path | When |
|---|---|---|
| Business context | {data_dir}/business-context.json | First full audit, or refresh when audit_date is >90 days old. Skip on scoped audits if file is fresh. |
| Personas | {data_dir}/personas/{accountId}.json | Every full audit. |
These are the handoff to every other ads skill — write them even if the report itself is short. Otherwise /google-ads-copy and /google-ads-landing operate without business context and produce generic output.
business-context.json schema: business_name, industry, website, services[], locations[], target_audience, brand_voice{tone, words_to_use[], words_to_avoid[]}, differentiators[], competitors[], seasonality{peak_months[], slow_months[], seasonal_hooks[]}, keyword_landscape{high_intent_terms[], competitive_terms[], long_tail_opportunities[]}, social_proof[], offers_or_promotions[], landing_pages{}, notes, audit_date, account_id.
personas JSON schema: {account_id, saved_at, personas: [{name, demographics, primary_goal, pain_points[], search_terms[], decision_trigger, value}]}. See references/persona-discovery.md.
Read ../shared/policy-registry.json. For each entry where last_verified + stale_after_days < today:
area for recent Google Ads changes; compare to assumption. If drift, banner the report and suggest registry update.Use a single runScript call with ads.gaqlParallel to fan out the queries an audit needs. The server's adsagent://playbooks/audit-account resource has a battle-tested baseline; extend it with what this rubric needs that isn't already there.
A complete audit needs at minimum:
customer resource) — totalSpend, conversions, conv-value, clicks, impressions for the audit window.campaign) — id, name, status, advertising_channel_type, bidding_strategy_type, network settings, search/top/abs-top impression share, budget-lost-IS, rank-lost-IS, status-rich metrics. Cap window at 90 days (impression-share data limit).ad_group) — to attach top-line metrics under each campaign.keyword_view) — text, match type, status, quality score (and components if needed), cost, clicks, conversions. Surfaces zombie keywords (0 impressions 30d), low-QS-with-spend, zero-converters.search_term_view) — for waste detection (clicks > 10, conv = 0) and brand-leakage detection (brand terms triggered by non-brand campaigns).campaign_criterion where type='KEYWORD' and negative=TRUE) — for negative-conflict detection and coverage.conversion_action) — count, status, primary-vs-secondary, counting type. STOP-condition input.ad_group_ad) — RSA headlines/descriptions for asset-coverage scoring and topAds extraction.campaign_criterion where type IN ('LOCATION','PROXIMITY')) — for multi-location structure scoring. radius_units: 0=meters, 1=km, 2=miles.change_event, max 30 days) — to flag recent edits that might explain regressions.Compute aggregates in the script, return summarized JSON. Don't return all rows — rank, slice, summarize. The agent narrates the result, the script does the math.
getRecommendations is a useful cross-check for Google's own recommendation surface — call it as a separate tool after the runScript pass if you want to compare your findings to Google's.
If a critical query errors out (auth, schema), surface the error and stop — don't fall back to a degraded audit.
Skip scoring entirely if totalSpend == 0 or activeCampaigns == 0. Go straight to business context.
If the user narrows the audit ("focus on one campaign", "campaign X", "just check waste"):
business-context.json is fresh.Score each of the 7 dimensions 0–5 using references/account-health-scoring.md. Overall = round(sum × 100 / 35).
| Score | Label | Meaning |
|---|---|---|
| 0 | Critical | Broken or missing — actively losing money |
| 1 | Poor | Major waste or missed opportunity |
| 2 | Needs Work | Several clear issues |
| 3 | Acceptable | Functional, room to improve |
| 4 | Good | Well-managed, minor opportunities |
| 5 | Excellent | Best-practice |
Scope-aware: campaign-level dimensions reflect in-scope data; account-level dimensions (conversion tracking) score account-wide with a note on scope impact.
network_settings.target_content_network = TRUE for a SEARCH channel campaign.ONE per click; e-commerce should use EVERY. Wrong setting silently inflates or deflates conversions.| Rank-Lost < 30% | Rank-Lost 30–50% | Rank-Lost > 50% | |
|---|---|---|---|
| Budget-Lost < 20% | Healthy | QS / bid problem | Quality crisis |
| Budget-Lost 20–40% | Budget problem | Mixed (fix quality first) | Structural — too-competitive keywords |
| Budget-Lost > 40% | Severe budget gap (highest-ROI fix if CPA is good) | Fix rank first, then add budget | Fundamental misalignment — pause and restructure |
Derive what you can from the data already pulled:
| Field | Source |
|---|---|
business_name | Account name (from customer.descriptive_name) |
services | Campaign + ad-group names, top converting keywords |
locations | campaign_criterion LOCATION + PROXIMITY |
brand_voice | Top-performing RSA headlines / descriptions |
keyword_landscape.high_intent_terms | Converting keywords with strong CVR |
keyword_landscape.competitive_terms | Keywords in campaigns with high rank-lost-IS |
keyword_landscape.long_tail_opportunities | Converting search terms not yet promoted to keywords |
website | Apex domain from ad final URLs |
Then crawl the website (homepage + about + services + top 3 ad landing pages, parallel WebFetch) and merge into the schema. See references/business-context.md for the full crawl procedure.
Always ask the user (it's faster than guessing): differentiators, competitors, seasonality. Ask for everything else only if the data + crawl can't answer it.
Discover 2–3 personas from search terms, top keywords, ad-group themes, landing pages, geo, and device split — all from the dataset already in memory. Persist to {data_dir}/personas/{accountId}.json. Each persona must be grounded in 5+ actual search terms; if not, drop it. See references/persona-discovery.md.
Lead with the verdict, then the top 3 actions (with dollar impact when possible), then the scorecard, then evidence for dimensions scoring 0–2 only. Cite specific campaigns, keywords, and dollar amounts. Cap at ~80 lines.
End with a single closing line after the handoff to /google-ads:
Your audit history is saved to your NotFair account — view it at https://notfair.co.
/google-ads (or /google-ads-copy, /google-ads-landing). End the report with one handoff tied to the #1 action.business-context.json and personas/{accountId}.json even if the report itself is short — downstream skills depend on them.npx claudepluginhub abacusaix/toprankCreates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.