Install
$ agentstack add skill-fourteenwm-ppc-ai-skills-add-account-negative-keywords ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Add Account-Level Negative Keywords
Purpose: Add account-level negative keywords (the ones that show in Admin → Account Settings → Negative Keywords) to one or more Google Ads accounts using the confirmed 3-step SharedSet approach.
Type: Mutation skill (two-step approval required)
Auto-Invoke When
- "add account negatives for [account]"
- "add account-level negatives to [accounts]"
- "set up account negative keywords for [account(s)]"
- "roll the [name] negatives to [accounts]"
- "add the baseline negatives to [account]"
Background — Why 3 Steps, Not 2
Account-level negatives (the ones in Admin → Account Settings → Negative Keywords) are backed by a SharedSet of type ACCOUNT_LEVEL_NEGATIVE_KEYWORDS. Most external guidance describes a 2-step setup that appears to work in the API and even renders in the UI, but leaves the data model fragile. The correct, confirmed pattern is 3 steps:
Step 1: Create SharedSet (type=ACCOUNT_LEVEL_NEGATIVE_KEYWORDS)
Step 2: Add keywords as SharedCriterion entries (PHRASE match, batched up to 1000)
Step 3: Create CustomerNegativeCriterion with negative_keyword_list.shared_set
pointing to the SharedSet ← the step most guides miss
If a SharedSet + CNC already exists for an account, only Step 2 runs — the skill adds just the missing SharedCriteria to the existing set. No duplicate SharedSet, no duplicate CNC.
Per-Account State Categorization
For each target account, the script queries all 3 layers and routes one of three ways:
| State | Path | Mutations | |---|---|---| | NO_SET | Full 3-step setup | 1 SharedSet + N SharedCriteria + 1 CustomerNegativeCriterion | | PARTIAL | Step 2 only | M SharedCriteria (only the keywords not already in the set) | | COMPLIANT | Skip | None |
This means re-running against an already-compliant portfolio is a safe no-op, and partial accounts only get the delta.
Two-Step Mutation Flow
This skill follows the [mutation-safety](../mutation-safety/) pattern. Never skip the dry-run, never auto-approve.
Step 1 — Dry-run preview
# Single account by name
python scripts/add_account_negative_keywords.py "Example Account"
# Multiple accounts (comma-separated names or CIDs)
python scripts/add_account_negative_keywords.py "Example Account, 1234567890, Another Account"
# All accounts in a portfolio (resolved via your accounts.json)
python scripts/add_account_negative_keywords.py --portfolio my-portfolio
Output includes:
- Per-account state query (which layer exists, how many keywords are present)
- Per-account categorization (NO_SET / PARTIAL / COMPLIANT)
- Account-level summary table with
Addcolumn showing keywords to be added - Total mutations across the batch
- Command to run for execution
Step 2 — Generate approval code
python scripts/add_account_negative_keywords.py "Example Account" --execute
Generates a unique APPROVE-XXXXXXXX code and saves a session JSON with the resolved plan. The session is keyed by approval code — re-running execute produces a fresh code (and a fresh state query, so accounts that became compliant since the dry-run drop out).
Step 3 — Execute with approval code
python scripts/add_account_negative_keywords.py "Example Account" --execute --approval-code APPROVE-XXXXXXXX
The script:
- Loads the saved session for that approval code
- Re-runs the appropriate path per account (full 3-step or Step 2 only)
- Skips COMPLIANT accounts
- Logs each per-account result to a local JSONL file and (optionally) a Google Sheet
- Continues on per-account failures — one bad account doesn't stop the batch
CLI Reference
| Flag | Default | Purpose | |------|---------|---------| | accounts (positional) | — | Comma-separated names or CIDs (optional if using --portfolio) | | --portfolio NAME | — | Resolve all accounts in a portfolio from accounts.json | | --keywords-file PATH | data/sample_baseline_keywords.txt | Path to baseline keyword list (one per line) | | --set-name NAME | "Account Negative Keywords" | SharedSet name to create | | --exclude-cid CID | — | Skip a specific CID (repeatable) | | --execute | off | Generate approval code + save session (without this, the script is dry-run) | | --approval-code APPROVE-XXX | — | Execute the saved session for the given code | | --accounts-json PATH | configurable | Path to your accounts.json | | --log-sheet-id ID | (off) | Optional Google Sheet ID for central mutation log | | --credentials PATH | google-ads.yaml | Google Ads API credentials |
accounts.json Format
The skill resolves account names → CIDs via a local accounts.json you maintain. Example shape:
{
"accounts": {
"example-account": {
"name": "Example Account",
"id": "1234567890",
"portfolio": "my-portfolio",
"aliases": ["EA", "Example"]
}
}
}
You can pass either the account name, an alias, or the raw CID — the script accepts all three.
Mutation Logging
Local (always on)
Every per-account result appends to ./logs/account_negs_mutations.jsonl with timestamp, account, CID, action type, details, success flag, error string, and approval code. This is your audit trail.
Google Sheet (opt-in)
Pass --log-sheet-id YOUR_SHEET_ID to also log to a central Sheet with columns: Timestamp | Account | CID | Action Type | Details | Success | Error | Approval Code
Uses the refresh token in google-ads.yaml — that token must include the spreadsheets scope.
Known API Quirks
These three quirks are documented in the Google Ads API but easy to trip on. The skill is built around them:
| Quirk | Impact | How the skill handles it | |---|---|---| | shared_set.member_count always reports 0 | You can't trust the count field to verify keywords were added | Skill re-queries shared_criterion directly | | shared_set.reference_count always reports 0 | You can't trust this to verify CNC attachment | Skill checks customer_negative_criterion directly | | negative_keyword_list.shared_set reads back empty in GAQL | The write succeeds; the read doesn't return the field | Skill confirms via UI; CNC existence is the signal | | GAQL uses customer_negative_criterion.id, not criterion_id | Wrong field name returns no results | Skill uses the correct field |
CRITICAL Rules
- NEVER delete existing keywords — only ADD missing ones
- NEVER delete a SharedSet automatically — a separate cleanup script handles that, gated by explicit user request
- Always PHRASE match for account-level negatives (broad-match account negatives have surprising side effects)
- Verify in UI after execution: Admin → Account Settings → Negative Keywords
- Mutation-safety is mandatory — do not bypass
Prerequisites
- Google Ads API credentials —
google-ads.yaml(see [google-ads-api-setup](../google-ads-api-setup/)) - Python:
pip install google-ads google-auth google-api-python-client pyyaml - An
accounts.jsonmapping names → CIDs (or pass CIDs directly) - A baseline keyword list (the skill ships a generic property-management sample; replace with your own)
- For
--log-sheet-id: the refresh token ingoogle-ads.yamlmust includehttps://www.googleapis.com/auth/spreadsheetsscope
Installation
mkdir -p .claude/skills/add-account-negative-keywords/scripts
curl -o .claude/skills/add-account-negative-keywords/SKILL.md \
https://raw.githubusercontent.com/fourteenwm/ppc-ai-skills/main/add-account-negative-keywords/SKILL.md
curl -o .claude/skills/add-account-negative-keywords/scripts/add_account_negative_keywords.py \
https://raw.githubusercontent.com/fourteenwm/ppc-ai-skills/main/add-account-negative-keywords/scripts/add_account_negative_keywords.py
curl -o .claude/skills/add-account-negative-keywords/scripts/sample_baseline_keywords.txt \
https://raw.githubusercontent.com/fourteenwm/ppc-ai-skills/main/add-account-negative-keywords/scripts/sample_baseline_keywords.txt
Related Skills
- [
mutation-safety](../mutation-safety/) — two-step approval pattern this skill implements - [
dgen-automation-disable](../dgen-automation-disable/) — same Pattern B preview/execute approach for a different mutation domain
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: fourteenwm
- Source: fourteenwm/ppc-ai-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.