Google ads report skill
Pull Google Ads performance data and generate reports.
by OpenClaudia·MIT license·★ 705 Stars on the repo·GitHub ↗
npx degit OpenClaudia/openclaudia-skills/skills/google-ads-report#main ~/.claude/skills/google-ads-reportChecked ·commit main
Files of Google ads report
Show the full text306 lines
Google Ads Report
Pull campaign, keyword, and conversion data from the Google Ads API.
Prerequisites
Requires:
GOOGLE_CLIENT_IDandGOOGLE_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,YESTERDAYLAST_7_DAYS,LAST_14_DAYS,LAST_30_DAYSTHIS_MONTH,LAST_MONTHTHIS_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:
- Account Overview: Total spend, impressions, clicks, conversions, ROAS
- Campaign Performance: All active campaigns ranked by spend
- Top Keywords: Top 20 keywords by spend with quality scores
- Search Terms: Top search terms and negative keyword candidates
- Quality Score Distribution: How many keywords at each QS level
- Conversion Analysis: Conversions and ROAS by campaign
- 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
REMOVEDstatus filter incorrectly - Forgetting to handle
costMicrosdivision by 1,000,000 - Requesting incompatible resource + segment combinations
Tips
- Always filter out REMOVED campaigns/ad groups/keywords
- Use
searchStreaminstead ofsearchfor 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 | |
| 2 | name google-ads-report |
| 3 | description > |
| 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 | |
| 13 | Pull campaign, keyword, and conversion data from the Google Ads API. |
| 14 | |
| 15 | ## Prerequisites |
| 16 | |
| 17 | Requires: |
| 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 | |
| 23 | Set in `.env`, `.env.local`, or `~/.claude/.env.global`. |
| 24 | |
| 25 | ### Getting an Access Token |
| 26 | |
| 27 | |
| 28 | # Same OAuth flow as other Google APIs |
| 29 | # Scope needed: https://www.googleapis.com/auth/adwords |
| 30 | |
| 31 | 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" |
| 32 | |
| 33 | # Exchange code for tokens |
| 34 | curl -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 | |
| 46 | Google Ads API uses GAQL (Google Ads Query Language) via REST. |
| 47 | |
| 48 | |
| 49 | POST https://googleads.googleapis.com/v17/customers/{CUSTOMER_ID}/googleAds:searchStream |
| 50 | |
| 51 | |
| 52 | Headers: |
| 53 | |
| 54 | Authorization: Bearer {ACCESS_TOKEN} |
| 55 | developer-token: {DEVELOPER_TOKEN} |
| 56 | login-customer-id: {LOGIN_CUSTOMER_ID} # Only if using manager account |
| 57 | Content-Type: application/json |
| 58 | |
| 59 | |
| 60 | |
| 61 | |
| 62 | ## 1. Campaign Performance Report |
| 63 | |
| 64 | Overview of all campaigns with key metrics. |
| 65 | |
| 66 | |
| 67 | curl -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 | |
| 80 | curl -s -X POST "..." | python3 -c " |
| 81 | import json, sys |
| 82 | data = json.load(sys.stdin) |
| 83 | print(f\"{'Campaign':<35} {'Status':<10} {'Impr':>8} {'Clicks':>7} {'CTR':>7} {'Avg CPC':>8} {'Cost':>10} {'Conv':>6} {'CPA':>8}\") |
| 84 | print('-' * 110) |
| 85 | for 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 | |
| 98 | 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. |
| 99 | |
| 100 | |
| 101 | |
| 102 | ## 2. Keyword Performance Report |
| 103 | |
| 104 | See how individual keywords perform. |
| 105 | |
| 106 | |
| 107 | curl -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 | |
| 120 | curl -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 | |
| 130 | Quality 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 | |
| 141 | curl -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 | |
| 155 | See what users actually searched for (vs. your keywords). |
| 156 | |
| 157 | |
| 158 | curl -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 | |
| 168 | Use 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 | |
| 178 | curl -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 | |
| 191 | # ROAS = conversions_value / (cost_micros / 1_000_000) |
| 192 | curl -s -X POST "..." | python3 -c " |
| 193 | import json, sys |
| 194 | data = json.load(sys.stdin) |
| 195 | print(f\"{'Campaign':<35} {'Cost':>10} {'Conv Value':>12} {'ROAS':>8}\") |
| 196 | for 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 | |
| 212 | curl -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 | |
| 226 | Use 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 | |
| 237 | When asked for a full ads report: |
| 238 | |
| 239 | **Account Overview**: Total spend, impressions, clicks, conversions, ROAS |
| 240 | **Campaign Performance**: All active campaigns ranked by spend |
| 241 | **Top Keywords**: Top 20 keywords by spend with quality scores |
| 242 | **Search Terms**: Top search terms and negative keyword candidates |
| 243 | **Quality Score Distribution**: How many keywords at each QS level |
| 244 | **Conversion Analysis**: Conversions and ROAS by campaign |
| 245 | **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
Browse more free Claude skills or everything in Marketing.