Skip to main content
skill CX Codex

google-ads-report — Claude Code skill: Google Ads reporting

This skill pulls campaign, keyword and conversion data through the Google Ads API and formats it into reports. Handy when you want ad spend numbers without opening the web UI.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add OpenClaudia/openclaudia-skills --skill google-ads-report -a claude-code
Open on GitHub

What google-ads-report does

The skill gives the agent a working path to the Google Ads API over REST with GAQL. It ships four canned queries: one for campaigns, one for keywords with Quality Score, one for ad groups, one for search terms. A short python3 snippet parses the response and prints aligned columns: impressions, clicks, CTR, average CPC, cost, conversions, CPA.

Cost fields arrive in micros, so the script divides them by 1 000 000. Skip that and your spend figures come out a million times too high. Access needs an OAuth token, a GOOGLE_ADS_DEVELOPER_TOKEN, the customer ID as XXX-XXX-XXXX (sent to the API without dashes), plus GOOGLE_ADS_LOGIN_CUSTOMER_ID when you work through a manager account. Store them in .env, .env.local or ~/.claude/.env.global.

Getting the token follows the standard Google OAuth dance scoped to https://www.googleapis.com/auth/adwords: the skill prints a consent URL, then you trade the code for tokens with curl. A developer token has to be approved before any call succeeds, so line that up first.

What it helps with

  • summarise the last 30 days of campaigns with cost and CPA
  • spot keywords with a weak Quality Score and break the score into parts
  • list high-spend search terms and shortlist negative keywords
  • compare ad groups by conversions and average cost per click
  • work out cost per conversion for each keyword before changing bids

Example prompts for the agent

  • Build a campaign report for the last 30 days, sorted by cost
  • Show me keywords with the lowest Quality Score and break down each component
  • List search terms with the highest spend — I want negative keyword candidates

How to install google-ads-report

1Skills CLI (Vercel)

npx skills add OpenClaudia/openclaudia-skills --skill google-ads-report -a claude-code

2Manually: put the files in place

~/.claude/skills/google-ads-report/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/google-ads-report/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about google-ads-report

How do I install google-ads-report in Claude Code?

Run npx skills add OpenClaudia/openclaudia-skills --skill google-ads-report -a claude-code. The skill lands in ~/.claude/skills/google-ads-report/SKILL.md.

What does google-ads-report do?

It fetches campaign, keyword, ad group and search term data from the Google Ads API and prints it as a table with the core metrics. Queries go to the REST searchStream endpoint in GAQL.

What does the skill need to run?

A Google OAuth client (GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET), a developer token, a customer account ID, and a manager account ID if you use one. The access token is issued with the https://www.googleapis.com/auth/adwords scope.

Source text

SKILL.md · MIT
---
name: google-ads-report
description: >
  Pull Google Ads performance data and generate reports. Use when asked about
  ad campaign performance, keyword costs, quality scores, ROAS, conversion
  tracking, or ad spend analysis. Trigger phrases: "google ads", "adwords",
  "campaign performance", "ad spend", "quality score", "CPC report",
  "ROAS", "ad conversion", "keyword performance", "google ads report".
---

# Google Ads Report

Pull campaign, keyword, and conversion data from the Google Ads API.

## Prerequisites

Requires:
- `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` (OAuth)
- `GOOGLE_ADS_DEVELOPER_TOKEN` (apply at https://ads.google.com/home/tools/manager-accounts/)
- `GOOGLE_ADS_CUSTOMER_ID` (the account ID, format: `XXX-XXX-XXXX`, passed without dashes)
- `GOOGLE_ADS_LOGIN_CUSTOMER_ID` (if using a manager account, the manager account ID)

Set in `.env`, `.env.local`, or `~/.claude/.env.global`.

### Getting an Access Token

```bash
# Same OAuth flow as other Google APIs
# Scope needed: https://www.googleapis.com/auth/adwords

echo "https://accounts.google.com/o/oauth2/v2/auth?client_id=${GOOGLE_CLIENT_ID}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://www.googleapis.com/auth/adwords&response_type=code&access_type=offline"

# Exchange code for tokens
curl -s -X POST "https://oauth2.googleapis.com/token" \
  -d "code={AUTH_CODE}" \
  -d "client_id=${GOOGLE_CLIENT_ID}" \
  -d "client_secret=${GOOGLE_CLIENT_SECRET}" \
  -d "redirect_uri=urn:ietf:wg:oauth:2.0:oob" \
  -d "grant_type=authorization_code"
```

---

## API Base

Google Ads API uses GAQL (Google Ads Query Language) via REST.

```
POST https://googleads.googleapis.com/v17/customers/{CUSTOMER_ID}/googleAds:searchStream
```

Headers:
```
Authorization: Bearer {ACCESS_TOKEN}
developer-token: {DEVELOPER_TOKEN}
login-customer-id: {LOGIN_CUSTOMER_ID}  # Only if using manager account
Content-Type: application/json
```

---

## 1. Campaign Performance Report

Overview of all campaigns with key metrics.

```bash
curl -s -X POST \
  "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
  -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
  -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "SELECT campaign.name, campaign.status, metrics.impressions, metrics.clicks, metrics.ctr, metrics.average_cpc, metrics.cost_micros, metrics.conversions, metrics.cost_per_conversion, metrics.conversions_value FROM campaign WHERE segments.date DURING LAST_30_DAYS AND campaign.status != REMOVED ORDER BY metrics.cost_micros DESC"
  }'
```

### Parsing Campaign Data

```bash
curl -s -X POST "..." | python3 -c "
import json, sys
data = json.load(sys.stdin)
print(f\"{'Campaign':<35} {'Status':<10} {'Impr':>8} {'Clicks':>7} {'CTR':>7} {'Avg CPC':>8} {'Cost':>10} {'Conv':>6} {'CPA':>8}\")
print('-' * 110)
for batch in data:
    for row in batch.get('results', []):
        c = row.get('campaign', {})
        m = row.get('metrics', {})
        cost = int(m.get('costMicros', 0)) / 1_000_000
        cpc = int(m.get('averageCpc', 0)) / 1_000_000
        cpa = float(m.get('costPerConversion', 0)) / 1_000_000 if m.get('costPerConversion') else 0
        print(f\"{c.get('name',''):<35} {c.get('status',''):<10} {int(m.get('impressions',0)):>8} {int(m.get('clicks',0)):>7} {float(m.get('ctr',0))*100:>6.2f}% \${cpc:>7.2f} \${cost:>9.2f} {float(m.get('conversions',0)):>6.1f} \${cpa:>7.2f}\")
"
```

### Important: Cost Micros

All cost values in Google Ads API are in **micros** (1/1,000,000 of the currency unit). Divide by 1,000,000 to get the actual amount.

---

## 2. Keyword Performance Report

See how individual keywords perform.

```bash
curl -s -X POST \
  "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
  -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
  -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "SELECT ad_group_criterion.keyword.text, ad_group_criterion.keyword.match_type, ad_group_criterion.quality_info.quality_score, metrics.impressions, metrics.clicks, metrics.ctr, metrics.average_cpc, metrics.cost_micros, metrics.conversions, metrics.conversions_value FROM keyword_view WHERE segments.date DURING LAST_30_DAYS AND ad_group_criterion.status != REMOVED ORDER BY metrics.cost_micros DESC LIMIT 50"
  }'
```

### Quality Score Breakdown

```bash
curl -s -X POST \
  "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
  -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
  -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "SELECT ad_group_criterion.keyword.text, ad_group_criterion.quality_info.quality_score, ad_group_criterion.quality_info.creative_quality_score, ad_group_criterion.quality_info.post_click_quality_score, ad_group_criterion.quality_info.search_predicted_ctr, metrics.impressions, metrics.average_cpc FROM keyword_view WHERE ad_group_criterion.quality_info.quality_score IS NOT NULL AND segments.date DURING LAST_30_DAYS ORDER BY ad_group_criterion.quality_info.quality_score ASC LIMIT 50"
  }'
```

Quality Score Components:
- **quality_score**: Overall score (1-10)
- **creative_quality_score**: Ad relevance (BELOW_AVERAGE, AVERAGE, ABOVE_AVERAGE)
- **post_click_quality_score**: Landing page experience
- **search_predicted_ctr**: Expected click-through rate

---

## 3. Ad Group Performance

```bash
curl -s -X POST \
  "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
  -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
  -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "SELECT campaign.name, ad_group.name, ad_group.status, metrics.impressions, metrics.clicks, metrics.ctr, metrics.average_cpc, metrics.cost_micros, metrics.conversions FROM ad_group WHERE segments.date DURING LAST_30_DAYS AND ad_group.status != REMOVED ORDER BY metrics.cost_micros DESC LIMIT 50"
  }'
```

---

## 4. Search Terms Report

See what users actually searched for (vs. your keywords).

```bash
curl -s -X POST \
  "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
  -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
  -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "SELECT search_term_view.search_term, segments.keyword.info.text, segments.keyword.info.match_type, metrics.impressions, metrics.clicks, metrics.ctr, metrics.cost_micros, metrics.conversions FROM search_term_view WHERE segments.date DURING LAST_30_DAYS ORDER BY metrics.impressions DESC LIMIT 100"
  }'
```

Use this to:
- Find new keyword opportunities (high-converting search terms)
- Identify negative keyword candidates (irrelevant terms with spend)
- Discover match type issues (broad match pulling in junk traffic)

---

## 5. Conversion Tracking

```bash
curl -s -X POST \
  "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
  -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
  -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "SELECT campaign.name, metrics.conversions, metrics.conversions_value, metrics.cost_micros, metrics.conversions_from_interactions_rate, metrics.value_per_conversion FROM campaign WHERE segments.date DURING LAST_30_DAYS AND campaign.status = ENABLED ORDER BY metrics.conversions DESC"
  }'
```

### ROAS Calculation

```bash
# ROAS = conversions_value / (cost_micros / 1_000_000)
curl -s -X POST "..." | python3 -c "
import json, sys
data = json.load(sys.stdin)
print(f\"{'Campaign':<35} {'Cost':>10} {'Conv Value':>12} {'ROAS':>8}\")
for batch in data:
    for row in batch.get('results', []):
        c = row['campaign']['name']
        m = row['metrics']
        cost = int(m.get('costMicros', 0)) / 1_000_000
        value = float(m.get('conversionsValue', 0))
        roas = value / cost if cost > 0 else 0
        print(f\"{c:<35} \${cost:>9.2f} \${value:>11.2f} {roas:>7.2f}x\")
"
```

---

## 6. Daily Spend Trend

```bash
curl -s -X POST \
  "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
  -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
  -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "SELECT segments.date, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions FROM customer WHERE segments.date DURING LAST_30_DAYS ORDER BY segments.date DESC"
  }'
```

---

## GAQL Date Ranges

Use these built-in date ranges in GAQL:
- `TODAY`, `YESTERDAY`
- `LAST_7_DAYS`, `LAST_14_DAYS`, `LAST_30_DAYS`
- `THIS_MONTH`, `LAST_MONTH`
- `THIS_QUARTER`, `LAST_QUARTER`
- Custom: `segments.date BETWEEN '2024-01-01' AND '2024-03-31'`

---

## Workflow: Monthly Google Ads Report

When asked for a full ads report:

1. **Account Overview**: Total spend, impressions, clicks, conversions, ROAS
2. **Campaign Performance**: All active campaigns ranked by spend
3. **Top Keywords**: Top 20 keywords by spend with quality scores
4. **Search Terms**: Top search terms and negative keyword candidates
5. **Quality Score Distribution**: How many keywords at each QS level
6. **Conversion Analysis**: Conversions and ROAS by campaign
7. **Daily Trend**: Spend and conversion trend over the period

### Report Format

```
## Google Ads Report: {Account Name}
### Period: {date range}

### Account Summary
| Metric | Value | vs Previous |
|--------|-------|-------------|
| Total Spend | $X | +Y% |
| Impressions | X | +Y% |
| Clicks | X | +Y% |
| CTR | X% | +Y pp |
| Avg CPC | $X | +Y% |
| Conversions | X | +Y% |
| ROAS | Xx | +Y% |

### Campaign Performance
| Campaign | Spend | Clicks | Conv | CPA | ROAS |
|----------|-------|--------|------|-----|------|
| ...      | ...   | ...    | ...  | ... | ...  |

### Top Keywords (by spend)
| Keyword | Match | QS | Spend | Clicks | Conv | CPC |
|---------|-------|-----|-------|--------|------|-----|
| ...     | ...   | ... | ...   | ...    | ...  | ... |

### Recommendations
- **Pause**: Keywords with high spend and zero conversions
- **Increase Bids**: Keywords with high conversion rate but limited budget
- **Negative Keywords**: Search terms wasting budget
- **Quality Score Fixes**: Keywords with QS < 5 and actions to improve
- **Budget Reallocation**: Shift budget from low-ROAS to high-ROAS campaigns
```

---

## Error Handling

| Error | Cause |
|-------|-------|
| `AUTHENTICATION_ERROR` | Invalid or expired access token |
| `AUTHORIZATION_ERROR` | Developer token issue or account access |
| `REQUEST_ERROR` | GAQL syntax error |
| `QUOTA_ERROR` | API quota exceeded |

### Common GAQL Mistakes

- Missing `WHERE segments.date DURING ...` (required for most metric queries)
- Using `REMOVED` status filter incorrectly
- Forgetting to handle `costMicros` division by 1,000,000
- Requesting incompatible resource + segment combinations

## Tips

- Always filter out REMOVED campaigns/ad groups/keywords
- Use `searchStream` instead of `search` for large result sets (no pagination needed)
- Cache results when building multi-section reports
- Quality Score of 0 means "not enough data" -- treat as null

© OpenClaudia · MIT · OpenClaudia/openclaudia-skills

Similar skills

Everything for Marketing and content →

From the same set50

linkedin-content Writes posts tuned to the LinkedIn feed: hooks, formats, content pillars and posting times. Useful when you need the actual text, not generic advice. CX Codex +1 482installs newsletter Helps plan email newsletter content, grow subscribers, and set up monetization. Useful when building a newsletter on Substack or Beehiiv. CX Codex +1 480installs keyword-research Runs automated keyword research via the SemRush API, clusters terms by intent, and identifies competitor content gaps. Useful for SEO audits and content planning. CX Codex +1 478installs affiliate-marketing Walks you through designing an affiliate program: commission models, platform choices, partner recruiting and activation metrics. CX Codex +1 478installs write-landing Writes landing page copy and wireframe structure for conversion and SEO. Useful when you need a full page, not just a headline. CX Codex +1 468installs write-blog Drafts a complete blog post: keyword research, an outline built from the SERP, then the article with meta tags. Useful when you need copy for a specific query. CX Codex +1 466installs thread-writer Turns blog posts, notes, or case studies into Twitter/X threads and Reddit posts. It applies structured hooks, formats value tweets, and adds clear calls to action. CX Codex +1 461installs discord-bot A skill that sends messages, embeds and announcements to Discord channels through a webhook or the Bot API. Useful when your agent needs to post to a community from inside the project. CX Codex +1 458installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor