Documentation - Insights
What GoodLads reads from your Google Ads account.
Every recommendation and every chat answer is grounded in one of the reads below. All of it is read-only - looking at your account never changes it - and it's the evidence every proposal has to cite before it can reach you. Every row's API column names the exact Google Ads resource it queries and our own route that serves it, with the literal request and response behind a "Show request & response" toggle.
Every read here goes through the same call: POST customers/{customerId}/googleAds:search - one query, one Google Ads resource, paginated on nextPageToken. Only the resource and fields change row to row. Our own dashboard reads them through one route, GET /api/google-ads/report, parameterized by a dataset key (?report=campaign, ?report=keyword, and so on) - shown per row below.
Performance
How your campaigns are doing, and the auction share you're winning or losing.
| Signal | Level | What it tells you | API | Status |
|---|---|---|---|---|
| Campaign performance | Campaign | Spend, clicks, conversions, conversion value, and impression share - how much of the auction you're winning versus losing to budget or ad rank - with a day-by-day trend. | googleAds:search - FROM campaignGET /api/google-ads/report?report=campaignShow request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT campaign.id, campaign.name, metrics.cost_micros,
metrics.clicks, metrics.conversions, metrics.conversions_value,
metrics.search_impression_share
FROM campaign
WHERE campaign.status = 'ENABLED'
ORDER BY metrics.cost_micros DESC"
}{
"results": [
{
"campaign": { "id": "998877", "name": "Search - Brand" },
"metrics": {
"costMicros": "184320000",
"clicks": "612",
"conversions": 41,
"conversionsValue": 3890.5,
"searchImpressionShare": 0.62
}
}
],
"nextPageToken": "CAo="
} | Ready |
Structure & creative
What's running today, down to the ad, the ad group, the keyword and the query.
| Signal | Level | What it tells you | API | Status |
|---|---|---|---|---|
| Ads | Ad | Every live ad's type, status and exact headlines and descriptions - Search and App campaigns alike - with per-ad performance. | googleAds:search - FROM ad_group_adGET /api/google-ads/report?report=adShow request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT campaign.id, campaign.name, ad_group.id, ad_group.name,
ad_group_ad.ad.id, ad_group_ad.ad.type, ad_group_ad.status,
metrics.cost_micros, metrics.clicks, metrics.conversions
FROM ad_group_ad
WHERE ad_group_ad.status != 'REMOVED'
ORDER BY metrics.cost_micros DESC"
}{
"results": [
{
"campaign": { "id": "998877", "name": "Search - Brand" },
"adGroup": { "id": "222111", "name": "Same-day delivery" },
"adGroupAd": {
"ad": { "id": "333444", "type": "RESPONSIVE_SEARCH_AD" },
"status": "ENABLED"
},
"metrics": { "costMicros": "42110000", "clicks": "203", "conversions": 12 }
}
]
} | Ready |
| Ad groups | Ad group | Each ad group's own bidding target and performance, so we know where within a campaign to act. | googleAds:search - FROM ad_groupGET /api/google-ads/report?report=ad_groupShow request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT campaign.id, campaign.name, ad_group.id, ad_group.name,
ad_group.status, ad_group.type,
metrics.cost_micros, metrics.conversions
FROM ad_group
WHERE ad_group.status != 'REMOVED'
ORDER BY metrics.cost_micros DESC"
}{
"results": [
{
"campaign": { "id": "998877", "name": "Search - Brand" },
"adGroup": {
"id": "222111",
"name": "Same-day delivery",
"status": "ENABLED",
"type": "SEARCH_STANDARD"
},
"metrics": { "costMicros": "42110000", "conversions": 12 }
}
]
} | Ready |
| Keywords | Keyword | Text, match type and performance, plus Quality Score and its three components - ad relevance, landing page experience, and expected click-through rate. | googleAds:search - FROM keyword_viewGET /api/google-ads/report?report=keywordShow request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT campaign.id, campaign.name, ad_group.id, ad_group.name,
ad_group_criterion.keyword.text, ad_group_criterion.keyword.match_type,
ad_group_criterion.quality_info.quality_score,
metrics.cost_micros, metrics.conversions
FROM keyword_view
WHERE ad_group_criterion.status != 'REMOVED'
ORDER BY metrics.cost_micros DESC"
}{
"results": [
{
"campaign": { "id": "998877", "name": "Search - Brand" },
"adGroup": { "id": "222111", "name": "Same-day delivery" },
"adGroupCriterion": {
"keyword": { "text": "same day courier", "matchType": "PHRASE" },
"qualityInfo": { "qualityScore": 7 }
},
"metrics": { "costMicros": "9110000", "conversions": 4 }
}
]
} | Ready |
| Search terms | Search term | The actual queries that triggered your ads, and which campaign and ad group they landed in. | googleAds:search - FROM search_term_viewGET /api/google-ads/report?report=search_termShow request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT campaign.id, campaign.name, ad_group.id, ad_group.name,
search_term_view.search_term, search_term_view.status,
metrics.cost_micros, metrics.conversions
FROM search_term_view
WHERE campaign.status = 'ENABLED'
ORDER BY metrics.cost_micros DESC"
}{
"results": [
{
"campaign": { "id": "998877", "name": "Search - Brand" },
"adGroup": { "id": "222111", "name": "Same-day delivery" },
"searchTermView": { "searchTerm": "free courier london", "status": "NONE" },
"metrics": { "costMicros": "3120000", "conversions": 0 }
}
]
} | Ready |
| Location targeting | Campaign | Which locations each campaign targets or excludes, and any bid adjustment already in place. | googleAds:search - FROM campaign_criterionGET /api/google-ads/report?report=campaign_locationsShow request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT campaign.id, campaign.name,
campaign_criterion.location.geo_target_constant,
campaign_criterion.negative, campaign_criterion.bid_modifier
FROM campaign_criterion
WHERE campaign_criterion.type = 'LOCATION'
AND campaign_criterion.status != 'REMOVED'
ORDER BY campaign.id"
}{
"results": [
{
"campaign": { "id": "998877", "name": "Search - Brand" },
"campaignCriterion": {
"location": { "geoTargetConstant": "geoTargetConstants/1007735" },
"negative": false,
"bidModifier": 1.15
}
}
]
} | Ready |
Segments
The same performance, sliced by where, when and how it happened.
| Signal | Level | What it tells you | API | Status |
|---|---|---|---|---|
| By device, network, time & click type | Campaign | The same performance, split by device, ad network, day of week and hour, and click type. | googleAds:search - FROM campaign, segmentedGET /api/google-ads/report?report=segment_deviceNetwork, day/hour and click-type are the identical shape at the sibling report keys segment_network, segment_time, segment_click_type - only the segments.* field changes. Show request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT campaign.id, campaign.name, segments.device,
metrics.cost_micros, metrics.conversions
FROM campaign
WHERE campaign.status = 'ENABLED'
ORDER BY metrics.cost_micros DESC"
}{
"results": [
{
"campaign": { "id": "998877", "name": "Search - Brand" },
"segments": { "device": "MOBILE" },
"metrics": { "costMicros": "98110000", "conversions": 22 }
}
]
} | Ready |
| By geography | Campaign | Performance by country and city, so a location quietly underperforming doesn't hide inside a campaign's overall average. | googleAds:search - FROM geographic_viewGET /api/google-ads/report?report=segment_geoThe city-level sibling (report=segment_geo_city) adds segments.geo_target_city on the same resource. Show request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT campaign.id, campaign.name,
geographic_view.country_criterion_id, geographic_view.location_type,
metrics.cost_micros, metrics.conversions
FROM geographic_view
ORDER BY metrics.cost_micros DESC"
}{
"results": [
{
"campaign": { "id": "998877", "name": "Search - Brand" },
"geographicView": { "countryCriterionId": "2826", "locationType": "LOCATION_OF_PRESENCE" },
"metrics": { "costMicros": "61200000", "conversions": 9 }
}
]
} | Ready |
| By conversion action | Campaign & keyword | Conversions split by the specific action that produced them - the read behind an account judged on one conversion goal, not everything blended together. | googleAds:search - FROM campaign, segmentedGET /api/google-ads/report?report=conversion_action_campaignNo cost field here - it stops being additive once split by conversion action. Spend still comes from the unsegmented campaign read above. The keyword-level sibling (report=conversion_action_keyword) is the identical shape on keyword_view. Show request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT campaign.id, campaign.name, segments.conversion_action_name,
metrics.conversions, metrics.conversions_value
FROM campaign
WHERE campaign.status = 'ENABLED'
ORDER BY metrics.conversions DESC"
}{
"results": [
{
"campaign": { "id": "998877", "name": "Search - Brand" },
"segments": { "conversionActionName": "Order completed" },
"metrics": { "conversions": 18, "conversionsValue": 2140.75 }
}
]
} | Ready |
Audiences
Who's converting, where Google Ads reports it.
| Signal | Level | What it tells you | API | Status |
|---|---|---|---|---|
| Age, gender, household income | Ad group | Performance by demographic band, wherever Google Ads reports it. | googleAds:search - FROM age_range_viewGET /api/google-ads/report?report=ageGender (report=gender) and household income (report=income) are the identical shape on gender_view / income_range_view, one criterion field swapped. Show request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT campaign.id, campaign.name, ad_group.id, ad_group.name,
ad_group_criterion.age_range.type,
metrics.cost_micros, metrics.conversions
FROM age_range_view
ORDER BY metrics.cost_micros DESC"
}{
"results": [
{
"campaign": { "id": "998877", "name": "Search - Brand" },
"adGroup": { "id": "222111", "name": "Same-day delivery" },
"adGroupCriterion": { "ageRange": { "type": "AGE_RANGE_25_34" } },
"metrics": { "costMicros": "11400000", "conversions": 3 }
}
]
} | Ready |
Assets
The extra creative attached to your ads, and how it's used.
| Signal | Level | What it tells you | API | Status |
|---|---|---|---|---|
| Sitelinks, callouts & images | Account | The account's asset library, and how each asset performs. | googleAds:search - FROM assetGET /api/google-ads/report?report=assetShow request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT asset.id, asset.name, asset.type,
asset.callout_asset.callout_text, asset.sitelink_asset.link_text
FROM asset
ORDER BY asset.id"
}{
"results": [
{
"asset": {
"id": "556677",
"name": "Callout - Free returns",
"type": "CALLOUT",
"calloutAsset": { "calloutText": "Free returns" }
}
}
]
} | Ready |
| Performance Max asset groups | Campaign | Status and destinations of each Performance Max asset group. | googleAds:search - FROM asset_groupGET /api/google-ads/report?report=asset_groupShow request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT campaign.id, campaign.name,
asset_group.id, asset_group.name, asset_group.status
FROM asset_group
ORDER BY asset_group.id"
}{
"results": [
{
"campaign": { "id": "998900", "name": "PMax - All products" },
"assetGroup": { "id": "778899", "name": "Core range", "status": "ENABLED" }
}
]
} | Ready |
Account & history
How the account is configured, and what's changed in it recently.
| Signal | Level | What it tells you | API | Status |
|---|---|---|---|---|
| Conversion actions | Account | How the account defines a conversion - each action's category, type and status. | googleAds:search - FROM conversion_actionGET /api/google-ads/report?report=conversion_actionShow request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT conversion_action.id, conversion_action.name,
conversion_action.category, conversion_action.type, conversion_action.status
FROM conversion_action
ORDER BY conversion_action.id"
}{
"results": [
{
"conversionAction": {
"id": "445566",
"name": "Order completed",
"category": "PURCHASE",
"type": "WEBPAGE",
"status": "ENABLED"
}
}
]
} | Ready |
| Budgets & bidding | Campaign | Daily budget, whether it's shared with other campaigns, the bidding strategy, and Google's own recommended budget. | googleAds:search - FROM campaignGET /api/google-ads/report?report=budgetShow request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT campaign.id, campaign.name, campaign.bidding_strategy_type,
campaign_budget.amount_micros, campaign_budget.explicitly_shared
FROM campaign
WHERE campaign.status = 'ENABLED'
ORDER BY campaign_budget.amount_micros DESC"
}{
"results": [
{
"campaign": {
"id": "998877",
"name": "Search - Brand",
"biddingStrategyType": "TARGET_ROAS"
},
"campaignBudget": { "amountMicros": "20000000", "explicitlyShared": false }
}
]
} | Ready |
| Change history | Account | Who changed what in the account over the last 30 days. | googleAds:search - FROM change_eventGET /api/google-ads/report?report=change_eventCapped by Google to the last 30 days, LIMIT 200, newest first. Show request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT change_event.change_date_time, change_event.user_email,
change_event.change_resource_type, change_event.resource_change_operation
FROM change_event
WHERE change_event.change_date_time BETWEEN
'2026-07-13 00:00:00' AND '2026-08-12 00:00:00'
ORDER BY change_event.change_date_time DESC
LIMIT 200"
}{
"results": [
{
"changeEvent": {
"changeDateTime": "2026-08-10 09:14:02",
"userEmail": "you@company.com",
"changeResourceType": "AD_GROUP_CRITERION",
"resourceChangeOperation": "CREATE"
}
}
]
} | Ready |
| Google's own recommendations | Campaign | Google Ads' built-in optimization suggestions - we read them, but never apply them automatically. | googleAds:search - FROM recommendationGET /api/google-ads/report?report=recommendationShow request & responsePOST customers/1234567890/googleAds:search
{
"query": "SELECT recommendation.type, recommendation.campaign
FROM recommendation
LIMIT 200"
}{
"results": [
{
"recommendation": {
"type": "KEYWORD",
"campaign": "customers/1234567890/campaigns/998877"
}
}
]
} | Ready |
See what GoodLads can do with all of this on the Actions page, the account journey it fits into on Jobs to be done, or which skill pairs which read with which write on Skills.