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.

SignalLevelWhat it tells youAPIStatus
Campaign performanceCampaignSpend, 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=campaign
Show request & response
POST 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.

SignalLevelWhat it tells youAPIStatus
AdsAdEvery 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=ad
Show request & response
POST 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 groupsAd groupEach 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_group
Show request & response
POST 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
KeywordsKeywordText, 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=keyword
Show request & response
POST 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 termsSearch termThe 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_term
Show request & response
POST 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 targetingCampaignWhich locations each campaign targets or excludes, and any bid adjustment already in place.
googleAds:search - FROM campaign_criterionGET /api/google-ads/report?report=campaign_locations
Show request & response
POST 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.

SignalLevelWhat it tells youAPIStatus
By device, network, time & click typeCampaignThe 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_device

Network, 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 & response
POST 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 geographyCampaignPerformance 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_geo

The city-level sibling (report=segment_geo_city) adds segments.geo_target_city on the same resource.

Show request & response
POST 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 actionCampaign & keywordConversions 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_campaign

No 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 & response
POST 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.

SignalLevelWhat it tells youAPIStatus
Age, gender, household incomeAd groupPerformance by demographic band, wherever Google Ads reports it.
googleAds:search - FROM age_range_viewGET /api/google-ads/report?report=age

Gender (report=gender) and household income (report=income) are the identical shape on gender_view / income_range_view, one criterion field swapped.

Show request & response
POST 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.

SignalLevelWhat it tells youAPIStatus
Sitelinks, callouts & imagesAccountThe account's asset library, and how each asset performs.
googleAds:search - FROM assetGET /api/google-ads/report?report=asset
Show request & response
POST 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 groupsCampaignStatus and destinations of each Performance Max asset group.
googleAds:search - FROM asset_groupGET /api/google-ads/report?report=asset_group
Show request & response
POST 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.

SignalLevelWhat it tells youAPIStatus
Conversion actionsAccountHow the account defines a conversion - each action's category, type and status.
googleAds:search - FROM conversion_actionGET /api/google-ads/report?report=conversion_action
Show request & response
POST 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 & biddingCampaignDaily 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=budget
Show request & response
POST 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 historyAccountWho changed what in the account over the last 30 days.
googleAds:search - FROM change_eventGET /api/google-ads/report?report=change_event

Capped by Google to the last 30 days, LIMIT 200, newest first.

Show request & response
POST 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 recommendationsCampaignGoogle Ads' built-in optimization suggestions - we read them, but never apply them automatically.
googleAds:search - FROM recommendationGET /api/google-ads/report?report=recommendation
Show request & response
POST 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.