Google ads report skill

Pull Google Ads performance data and generate reports.

by OpenClaudia·MIT license·★ 705 Stars on the repo·GitHub ↗

Use now

Files of Google ads report

OpenClaudia/main1 file shown
SKILL.md
Show the full text306 lines

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
# 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.

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
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.

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
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

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).

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

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
# 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

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
1---
2name: google-ads-report
3description: >
4 Pull Google Ads performance data and generate reports. Use when asked about
5 ad campaign performance, keyword costs, quality scores, ROAS, conversion
6 tracking, or ad spend analysis. Trigger phrases: "google ads", "adwords",
7 "campaign performance", "ad spend", "quality score", "CPC report",
8 "ROAS", "ad conversion", "keyword performance", "google ads report".
9---
10 
11# Google Ads Report
12 
13Pull campaign, keyword, and conversion data from the Google Ads API.
14 
15## Prerequisites
16 
17Requires:
18- `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` (OAuth)
19- `GOOGLE_ADS_DEVELOPER_TOKEN` (apply at https://ads.google.com/home/tools/manager-accounts/)
20- `GOOGLE_ADS_CUSTOMER_ID` (the account ID, format: `XXX-XXX-XXXX`, passed without dashes)
21- `GOOGLE_ADS_LOGIN_CUSTOMER_ID` (if using a manager account, the manager account ID)
22 
23Set in `.env`, `.env.local`, or `~/.claude/.env.global`.
24 
25### Getting an Access Token
26 
27```bash
28# Same OAuth flow as other Google APIs
29# Scope needed: https://www.googleapis.com/auth/adwords
30 
31echo "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"
32 
33# Exchange code for tokens
34curl -s -X POST "https://oauth2.googleapis.com/token" \
35 -d "code={AUTH_CODE}" \
36 -d "client_id=${GOOGLE_CLIENT_ID}" \
37 -d "client_secret=${GOOGLE_CLIENT_SECRET}" \
38 -d "redirect_uri=urn:ietf:wg:oauth:2.0:oob" \
39 -d "grant_type=authorization_code"
40```
41 
42---
43 
44## API Base
45 
46Google Ads API uses GAQL (Google Ads Query Language) via REST.
47 
48```
49POST https://googleads.googleapis.com/v17/customers/{CUSTOMER_ID}/googleAds:searchStream
50```
51 
52Headers:
53```
54Authorization: Bearer {ACCESS_TOKEN}
55developer-token: {DEVELOPER_TOKEN}
56login-customer-id: {LOGIN_CUSTOMER_ID} # Only if using manager account
57Content-Type: application/json
58```
59 
60---
61 
62## 1. Campaign Performance Report
63 
64Overview of all campaigns with key metrics.
65 
66```bash
67curl -s -X POST \
68 "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
69 -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
70 -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
71 -H "Content-Type: application/json" \
72 -d '{
73 "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"
74 }'
75```
76 
77### Parsing Campaign Data
78 
79```bash
80curl -s -X POST "..." | python3 -c "
81import json, sys
82data = json.load(sys.stdin)
83print(f\"{'Campaign':<35} {'Status':<10} {'Impr':>8} {'Clicks':>7} {'CTR':>7} {'Avg CPC':>8} {'Cost':>10} {'Conv':>6} {'CPA':>8}\")
84print('-' * 110)
85for batch in data:
86 for row in batch.get('results', []):
87 c = row.get('campaign', {})
88 m = row.get('metrics', {})
89 cost = int(m.get('costMicros', 0)) / 1_000_000
90 cpc = int(m.get('averageCpc', 0)) / 1_000_000
91 cpa = float(m.get('costPerConversion', 0)) / 1_000_000 if m.get('costPerConversion') else 0
92 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}\")
93"
94```
95 
96### Important: Cost Micros
97 
98All 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.
99 
100---
101 
102## 2. Keyword Performance Report
103 
104See how individual keywords perform.
105 
106```bash
107curl -s -X POST \
108 "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
109 -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
110 -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
111 -H "Content-Type: application/json" \
112 -d '{
113 "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"
114 }'
115```
116 
117### Quality Score Breakdown
118 
119```bash
120curl -s -X POST \
121 "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
122 -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
123 -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
124 -H "Content-Type: application/json" \
125 -d '{
126 "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"
127 }'
128```
129 
130Quality Score Components:
131- **quality_score**: Overall score (1-10)
132- **creative_quality_score**: Ad relevance (BELOW_AVERAGE, AVERAGE, ABOVE_AVERAGE)
133- **post_click_quality_score**: Landing page experience
134- **search_predicted_ctr**: Expected click-through rate
135 
136---
137 
138## 3. Ad Group Performance
139 
140```bash
141curl -s -X POST \
142 "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
143 -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
144 -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
145 -H "Content-Type: application/json" \
146 -d '{
147 "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"
148 }'
149```
150 
151---
152 
153## 4. Search Terms Report
154 
155See what users actually searched for (vs. your keywords).
156 
157```bash
158curl -s -X POST \
159 "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
160 -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
161 -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
162 -H "Content-Type: application/json" \
163 -d '{
164 "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"
165 }'
166```
167 
168Use this to:
169- Find new keyword opportunities (high-converting search terms)
170- Identify negative keyword candidates (irrelevant terms with spend)
171- Discover match type issues (broad match pulling in junk traffic)
172 
173---
174 
175## 5. Conversion Tracking
176 
177```bash
178curl -s -X POST \
179 "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
180 -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
181 -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
182 -H "Content-Type: application/json" \
183 -d '{
184 "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"
185 }'
186```
187 
188### ROAS Calculation
189 
190```bash
191# ROAS = conversions_value / (cost_micros / 1_000_000)
192curl -s -X POST "..." | python3 -c "
193import json, sys
194data = json.load(sys.stdin)
195print(f\"{'Campaign':<35} {'Cost':>10} {'Conv Value':>12} {'ROAS':>8}\")
196for batch in data:
197 for row in batch.get('results', []):
198 c = row['campaign']['name']
199 m = row['metrics']
200 cost = int(m.get('costMicros', 0)) / 1_000_000
201 value = float(m.get('conversionsValue', 0))
202 roas = value / cost if cost > 0 else 0
203 print(f\"{c:<35} \${cost:>9.2f} \${value:>11.2f} {roas:>7.2f}x\")
204"
205```
206 
207---
208 
209## 6. Daily Spend Trend
210 
211```bash
212curl -s -X POST \
213 "https://googleads.googleapis.com/v17/customers/${GOOGLE_ADS_CUSTOMER_ID}:searchStream" \
214 -H "Authorization: Bearer ${GADS_ACCESS_TOKEN}" \
215 -H "developer-token: ${GOOGLE_ADS_DEVELOPER_TOKEN}" \
216 -H "Content-Type: application/json" \
217 -d '{
218 "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"
219 }'
220```
221 
222---
223 
224## GAQL Date Ranges
225 
226Use these built-in date ranges in GAQL:
227- `TODAY`, `YESTERDAY`
228- `LAST_7_DAYS`, `LAST_14_DAYS`, `LAST_30_DAYS`
229- `THIS_MONTH`, `LAST_MONTH`
230- `THIS_QUARTER`, `LAST_QUARTER`
231- Custom: `segments.date BETWEEN '2024-01-01' AND '2024-03-31'`
232 
233---
234 
235## Workflow: Monthly Google Ads Report
236 
237When asked for a full ads report:
238 
2391. **Account Overview**: Total spend, impressions, clicks, conversions, ROAS
2402. **Campaign Performance**: All active campaigns ranked by spend
2413. **Top Keywords**: Top 20 keywords by spend with quality scores
2424. **Search Terms**: Top search terms and negative keyword candidates
2435. **Quality Score Distribution**: How many keywords at each QS level
2446. **Conversion Analysis**: Conversions and ROAS by campaign
2457. **Daily Trend**: Spend and conversion trend over the period
246 
247### Report Format
248 
249```
250## Google Ads Report: {Account Name}
251### Period: {date range}
252 
253### Account Summary
254| Metric | Value | vs Previous |
255|--------|-------|-------------|
256| Total Spend | $X | +Y% |
257| Impressions | X | +Y% |
258| Clicks | X | +Y% |
259| CTR | X% | +Y pp |
260| Avg CPC | $X | +Y% |
261| Conversions | X | +Y% |
262| ROAS | Xx | +Y% |
263 
264### Campaign Performance
265| Campaign | Spend | Clicks | Conv | CPA | ROAS |
266|----------|-------|--------|------|-----|------|
267| ... | ... | ... | ... | ... | ... |
268 
269### Top Keywords (by spend)
270| Keyword | Match | QS | Spend | Clicks | Conv | CPC |
271|---------|-------|-----|-------|--------|------|-----|
272| ... | ... | ... | ... | ... | ... | ... |
273 
274### Recommendations
275- **Pause**: Keywords with high spend and zero conversions
276- **Increase Bids**: Keywords with high conversion rate but limited budget
277- **Negative Keywords**: Search terms wasting budget
278- **Quality Score Fixes**: Keywords with QS < 5 and actions to improve
279- **Budget Reallocation**: Shift budget from low-ROAS to high-ROAS campaigns
280```
281 
282---
283 
284## Error Handling
285 
286| Error | Cause |
287|-------|-------|
288| `AUTHENTICATION_ERROR` | Invalid or expired access token |
289| `AUTHORIZATION_ERROR` | Developer token issue or account access |
290| `REQUEST_ERROR` | GAQL syntax error |
291| `QUOTA_ERROR` | API quota exceeded |
292 
293### Common GAQL Mistakes
294 
295- Missing `WHERE segments.date DURING ...` (required for most metric queries)
296- Using `REMOVED` status filter incorrectly
297- Forgetting to handle `costMicros` division by 1,000,000
298- Requesting incompatible resource + segment combinations
299 
300## Tips
301 
302- Always filter out REMOVED campaigns/ad groups/keywords
303- Use `searchStream` instead of `search` for large result sets (no pagination needed)
304- Cache results when building multi-section reports
305- Quality Score of 0 means "not enough data" -- treat as null
306 

Discussion

Alternatives