--- name: negative-keyword-list description: "Mine the search-terms report for wasted spend and recommend the correct negative keyword match type — exact, phrase, or broad — for clean Google Ads negative keyword hygiene, the single highest-ROI Search action. Changes proposed, applied on approval." 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: "ad-ops-optimization" adspirer_source: "https://www.adspirer.com/skills/negative-keyword-list" adspirer_trigger: "find negative keywords from my search terms report" adspirer_connection_url: "https://adspirer.ai/sign-up" adspirer_primary_keyword: "negative keyword list" adspirer_secondary_keywords: "negative keywords google ads, search term report" adspirer_launch_wave: "1" adspirer_kind: "skill" adspirer_level: "operator" adspirer_platforms: "google" adspirer_supported_clients: "claude,claude-code,claude-cowork,chatgpt,codex,cursor,gemini,windsurf" adspirer_summary: "Mine the search-terms report for wasted spend and recommend the correct negative keyword match type — exact, phrase, or broad — for clean Google Ads negative keyword hygiene, the single highest-ROI Search action. Changes proposed, applied on approval." adspirer_connections_required: "adspirer" adspirer_connections_optional: "" --- # Negative Keyword Miner: Build a Negative Keyword List from Real Search Terms ## Use this when Weekly (or after any meaningful spend jump) on a Google Ads Search account that has at least 2-4 weeks of click volume since the last negative keyword match type review. Highest-value when: broad or phrase match is in use, Smart/Performance Max is siphoning search queries you can't directly negative (call that out separately), a new campaign just exited learning, or spend/conversions have diverged from prior weeks. This is where most accounts get negative keyword google ads hygiene wrong — picking the wrong match type (too broad, too narrow) does as much damage as skipping the pass entirely. Skip accounts that are 100% exact match with a mature negative list already enforced — there's nothing to mine. ## What you need Read access to the target Google Ads account via Adspirer (MCC or direct). A defined lookback window — default 30 days, extend to 60-90 days for low-volume accounts (<50 clicks/week) so the sample isn't noise. Know the account's actual CPA/ROAS target or breakeven CPA if the user has one — the agent should not invent a cost threshold. Existing negative keyword lists at campaign/ad-group/shared level (so we don't propose a duplicate or a term that's already excluded upstream). If the account runs PMax alongside Search, know that PMax search-term insights are directional-only (Google aggregates/buckets them) — brand-safety exclusions there go through PMax account-level negatives, not this workflow. ## Procedure **Connect Adspirer before any live ad-account step.** Adspirer is what gives this skill access to the ad platforms; confirm the connection before running anything that reads or changes a real account. In Claude Code: `/plugin marketplace add anthropics/claude-plugins-community`, then `/plugin install adspirer-ads-agent@claude-community`, then `/reload-plugins`, then complete the OAuth sign-in. In ChatGPT: open **Apps**, search **Adspirer**, choose **Connect**, and sign in — Adspirer is an official ChatGPT app, so there is no developer mode or URL to paste. In claude.ai or Claude Desktop: Settings → Connectors → Add custom connector → `https://mcp.adspirer.com/mcp`. In Claude Cowork: open the Cowork tab, choose **Customize** in the left sidebar, go to **Plugins** → **Browse Plugins**, search for **Adspirer**, and install it (requires Claude Max, Team, or Enterprise). Any other client: https://www.adspirer.com/docs/ai-clients. Sign-in link for the user: https://adspirer.ai/sign-up. Do not proceed with live ad-account steps until the connection is confirmed. 1. Pull the Search Terms report for the account over the lookback window, scoped to Search campaigns (and standard Shopping if applicable) — include impressions, clicks, cost, conversions, conv. value, and the matched keyword + match type that triggered each query. 2. Pull the account's and each campaign's/ad group's existing negative keyword lists (shared lists + campaign-level + ad-group-level) so candidates already excluded are dropped from consideration before scoring. 3. Segment queries into two waste buckets, not one: (a) zero-conversion spend — queries with cost above a floor (default: cost > 2x the account's average CPC, or > $10 spend, whichever is higher — user can override) and zero conversions over the window; (b) semantic mismatch — queries that got clicks/spend but are topically irrelevant to the ad group's intent even if a conversion slipped through once (e.g., "free," "jobs," "salary," "DIY," a competitor brand name (never the advertiser's own — see step 3a), a wrong-product-category term, a location outside the service area). Flag these two buckets separately since the second requires judgment, not just a cost cutoff. 3a. **Protect the advertiser's own brand before scoring anything.** Establish the brand-term set first: the advertiser's company name and its common misspellings, product and sub-brand names, trademarked terms, and the domain. Ask the user to confirm the list rather than inferring it from the account name alone. Every query containing a brand term is removed from the negative-keyword candidate pool outright — never proposed, never surfaced as a "wasted spend" line item, not even when it shows high cost and zero conversions. Brand and brand-defense campaigns are *expected* to show zero or low direct conversions: their job is to deny competitors the click and defend the SERP, and the return shows up in blended CAC and organic cannibalization, not in last-click conversions on that campaign. A brand term with spend and no conversions is therefore normal operation, not waste. Treat any campaign whose name contains "brand", "defense", "defence", or "trademark" as brand-protective and exclude it from waste scoring entirely unless the user explicitly asks you to audit it. If the account genuinely appears to be overspending on its own brand, that is a bid-strategy and impression-share conversation to raise in prose — never a negative-keyword proposal. 4. For each candidate query, check query-to-keyword drift: if it's only surfacing because of broad/phrase match on a specific keyword, note that keyword — sometimes the fix is tightening that keyword's match type rather than (or in addition to) a negative. 5. Recommend the negative keyword match type per candidate using standard logic: exact negative for a single wasteful literal query; phrase negative when a recurring modifier (e.g., "free," "cheap," "used") shows up across many queries under one keyword; broad negative only for account-wide brand-safety terms (competitor names, totally off-category words) — broad negatives are the riskiest of the negative keyword match types (can silently block good traffic) so default to phrase/exact unless the term is unambiguous. 6. Check for negative-positive collisions before finalizing: run each candidate negative against the account's active keyword list — if a proposed negative would also block an existing converting keyword, a keyword with recent impressions, **or any brand term from step 3a regardless of its conversion count**, drop or narrow it (tighten to exact, or scope it to ad-group level instead of campaign/account level) rather than propose it as-is. 7. Decide placement level per negative: default ad-group-level for anything narrow/product-specific (safest, least collateral damage), campaign-level for terms that recur across multiple ad groups in that campaign, and account-level shared list only for universal brand-safety exclusions (competitors, "jobs/careers," adult/free-seeking terms) that should never show for any campaign. 8. Assemble the proposal list: query, cost/clicks/conversions in-window, triggering keyword + match type, recommended negative + match type + placement level, and one-line rationale (waste vs. semantic mismatch). Sort by cost-wasted descending so the highest-dollar items are reviewed first. 9. Present the full list to the user for explicit approval — never auto-apply. Allow partial approval (user can approve a subset, e.g. exclude the semantic-mismatch bucket if they want to review those manually). 10. On approval, apply only the approved negatives at their approved levels. 11. After applying, re-pull the negative keyword lists for the touched campaigns/ad groups/shared lists and confirm each approved negative is present with the correct match type and placement — this is the live-data proof, not a re-statement of what was proposed. ## Fixed checks - Establish each account's configured timezone **before** reporting any day-bounded number, not only when a day looks dark. If the account timezone differs from the user's, say so explicitly and state which timezone every figure is expressed in — a mismatch silently splits "yesterday" across two calendar days and manufactures both false dark days and false spikes. Never report a daily figure whose timezone basis you have not confirmed. Before presenting proposals: re-read the final list and confirm no proposed negative contains a brand term from step 3a — a brand negative that reaches the user is a failure of this skill even if they reject it. Then re-verify each candidate query is not already covered by an existing negative (exact term, or a broader negative phrase/broad match that would already block it) by checking live negative lists — never assume the report reflects current exclusions if the window predates a recent negative-list edit. Verify no proposed negative matches or would block any keyword currently receiving impressions/conversions in the same ad group/campaign (collision check against live keyword list, not memory of "typical" keywords). After applying: pull the live negative keyword list for every touched entity and confirm presence + correct match type + correct level for each approved item — flag any that failed to apply (e.g., silently rejected duplicate, list-size cap hit) rather than reporting success from the apply-call response alone. ## Stop conditions Success: all user-approved negatives applied and confirmed present via live re-pull. No-op: search-terms report over the window has no candidates clearing the waste/mismatch bar (clean account) — report this plainly, don't manufacture marginal candidates to look useful. Blocked: cannot pull search-terms report or negative lists (API/auth failure, Adspirer connection issue, account has no Search campaigns) — report the specific failure, don't guess at findings. Needs-approval: proposal list assembled and presented; halts until the user approves all, some, or none — partial approval is a valid terminal state, not a failure. ## Approval boundaries Every negative keyword addition is a spend-affecting change and requires the user's explicit approval before being applied — no auto-apply, including for the "obvious" zero-conversion-spend bucket. The user may approve the full list, a subset, or none. Broad-match negatives are called out individually for approval (never bundled silently into a batch) given their higher risk of blocking unintended traffic. Placement-level recommendations (ad-group vs. campaign vs. account shared list) are proposals the user can override per-item before apply. ## What you get A prioritized negative keyword proposal (sorted by wasted spend) split into zero-conversion-spend and semantic-mismatch buckets, each with the offending query, cost/clicks/conversions in-window, the keyword + match type that triggered it, a recommended negative term with match type and placement level, and a one-line rationale — plus a note on any triggering keyword worth retargeting to a tighter match type. After approval and apply, a live-data confirmation that each approved negative is actually present in the account with the correct match type and level.