Ads

Google Ads

Create Search, Display, Performance Max and Demand Gen campaigns, manage keywords and assets, apply recommendations and query GAQL from the API.

A googleads connection lets you build Google Search, Display, Performance Max and Demand Gen campaigns, run Keyword Planner, upload Customer Match lists, choose conversion goals, act on Google's recommendations and query anything Google reporting can answer. Campaigns of every type, including Shopping and Video ones made in Google Ads, sync into the campaign tree with metrics.

#Connect

Call GET /v1/connect/googleads/ads with the profileId. Google Ads is its own OAuth connection, separate from YouTube and Google Business Profile. The first call returns an authUrl; once the user consents, the same call returns alreadyConnected: true and the accountId.

You do not need a Google Ads developer token or an API access application of your own. Accounts under a manager (MCC) account work too.

GET /v1/ads/accounts?accountId=... lists the customer ids the connection reaches. Use the id without dashes as adAccountId. GET /v1/ads/accounts/hierarchy shows how the accounts nest under managers.

Id What it is on Google
adAccountId Customer id, digits only
platformCampaignId / campaignId Campaign id
platformAdSetId / adSetId Ad group id
platformAdId Ad id. On Performance Max, Pozy lists the asset group as the ad.

#Create a Search campaign

campaignType picks the campaign: search, display, pmax or demand_gen. A Search create needs the ad copy and keywords.

curl -X POST https://api.pozy.ai/v1/ads/create \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: search-plumbing-austin" \
  -d '{
    "accountId": "66b2e19d8c3f5a7e9d0b1c3e",
    "adAccountId": "1234567890",
    "campaignType": "search",
    "name": "Emergency plumbing - Austin",
    "goal": "traffic",
    "budgetAmount": 50,
    "budgetType": "daily",
    "headline": "24/7 Emergency Plumber",
    "body": "Licensed plumbers at your door in 60 minutes. Upfront prices.",
    "additionalHeadlines": [
      "Same-Day Repairs",
      { "text": "Austin Plumbing Pros", "pinnedField": "HEADLINE_1" }
    ],
    "additionalDescriptions": ["No call-out fee before 8pm. Book online in two minutes."],
    "linkUrl": "https://plumbing.example.com/austin",
    "keywords": [
      "emergency plumber austin",
      { "text": "burst pipe repair", "matchType": "phrase" }
    ],
    "countries": ["US"]
  }'
Budgets are not micros

Amounts are in the account's currency units: on a USD account, budgetAmount: 50 with budgetType: "daily" means $50.00 per day. Google's API and GAQL reports count in micros instead, so a figure pasted from a report is off by a factor of one million.

Rules for the Search create:

  • goal is engagement, traffic or awareness. video_views and conversion goals return 422.
  • keywords are broad match unless you send { text, matchType } with exact, phrase or broad. Up to 1,000 per call, 80 characters each. negativeKeywords and campaignNegativeKeywords take the same shape.
  • The responsive search ad ends up with 3 to 15 headlines (30 characters each) and 2 to 4 descriptions (90 characters each) after headline, body, additionalHeadlines and additionalDescriptions are combined. pinnedField fixes one to HEADLINE_1, HEADLINE_2, HEADLINE_3, DESCRIPTION_1 or DESCRIPTION_2.
  • sitelinks, callouts and structuredSnippets attach assets to the new campaign in the same call.
  • Every Google create starts as pending_review. Later syncs move it to active, rejected, paused, completed or cancelled.
  • validateOnly: true checks the whole request against Google without creating anything.

#Display campaigns

Send campaignType: "display", businessName (25 characters) and both images:

Display fields
{
  "campaignType": "display",
  "businessName": "Austin Plumbing",
  "headline": "Fast, fair plumbing",
  "longHeadline": "Licensed Austin plumbers, same-day repairs, upfront prices",
  "body": "Book online in two minutes.",
  "images": {
    "landscape": "https://cdn.example.com/plumbing-1200x628.jpg",
    "square": "https://cdn.example.com/plumbing-1080.jpg"
  }
}

Google requires a landscape (1.91:1) and a square (1:1) image, JPEG or PNG up to 5,120 KB. Sending only one fails with NOT_ENOUGH_SQUARE_MARKETING_IMAGE_ASSET. longHeadline defaults to headline.

#Performance Max

Send campaignType: "pmax" with an assetGroup. The campaign, its budget and the asset group with every asset are created in one atomic request.

Performance Max fields
{
  "campaignType": "pmax",
  "budgetAmount": 80,
  "budgetType": "daily",
  "assetGroup": {
    "name": "All services",
    "finalUrl": "https://plumbing.example.com",
    "businessName": "Austin Plumbing",
    "headlines": ["Emergency plumber", "Same-day repairs", "Upfront prices"],
    "longHeadline": "Licensed Austin plumbers at your door in 60 minutes",
    "descriptions": ["Book online in two minutes.", "No call-out fee before 8pm."],
    "images": {
      "landscape": ["https://cdn.example.com/pm-1200x628.jpg"],
      "square": ["https://cdn.example.com/pm-1080.jpg"],
      "logo": ["https://cdn.example.com/logo-512.png"]
    },
    "youtubeVideoId": "dQw4w9WgXcQ"
  }
}
Rule Detail
Starts paused Google will not create a Performance Max campaign that is already running. Once it passes review, switch it on with PUT /v1/ads/campaigns/{campaignId}/status.
Daily budget only Send budgetType: "daily"; nothing else is accepted.
Bidding Either maximize conversions or maximize conversion value. Bid caps and portfolio strategies get an error.
Targeting Locations, excluded locations and languages only. No geo means everywhere.
Asset minimums At least 3 headlines, 2 descriptions, and landscape, square and logo images.

After launch, manage asset groups under /v1/ads/campaigns/{campaignId}/asset-groups: list them with Google's ad strength (POOR to EXCELLENT), add groups, edit URLs and status, link and unlink assets in one atomic request, and replace the retail listing-group tree for Merchant Center campaigns. An asset group's status is separate from its campaign's: an enabled group in a paused campaign spends nothing.

#Demand Gen

Demand Gen serves on YouTube (in-stream, in-feed, Shorts), Discover, Gmail and display partners. Send campaignType: "demand_gen" with a demandGen object. The format follows from what you send:

Format Selected by Creative
Multi-asset image Default 1 to 5 headlines (40 characters), 1 to 5 descriptions (90), businessName, landscape or square images plus a logo
Video youtubeVideoIds (1 to 5) headlines, longHeadlines (required here), descriptions, businessName, one logo
Carousel carouselCards (2 to 10) One headline, description and logo; each card has its own headline and image

To restrict where a Demand Gen campaign shows, list channels in demandGen.channels, choosing from youtube_in_stream, youtube_in_feed, youtube_shorts, discover, gmail and display. Describe the audience in demandGen.audience (user lists, interests, custom audiences, age ranges, genders), or point demandGen.audienceId at one you already have. These campaigns are created paused, and their locations and languages are set per ad group. The same create endpoint adds more: an ad group with existingCampaignId, an ad with adSetId.

#Bidding and budgets

bidStrategy uses the same names as the other networks and maps to Google's strategies:

bidStrategy With Google strategy
LOWEST_COST_WITHOUT_CAP Maximize conversions
COST_CAP bidAmount Target CPA
LOWEST_COST_WITH_MIN_ROAS roasAverageFloor Target ROAS
LOWEST_COST_WITH_BID_CAP bidAmount Maximize clicks with a CPC ceiling

Other ways to bid, in place of bidStrategy: a shared portfolio strategy through portfolioBidStrategyId (ids come from GET /v1/ads/bid-strategies), targetImpressionShare for Search, and manualCpc: { maxCpc } for Search or Display. Use networkSettings to include or exclude search partners and the display network.

Google budgets are daily. PUT /v1/ads/campaigns/{campaignId} with budget changes one. A shared budget changes every campaign on it, so the call returns 409 unless you send allowSharedBudgetUpdate: true. sharedBudgetId moves a campaign onto a budget from GET /v1/ads/shared-budgets.

#Keywords

Job Call
Read the keywords your ad groups run, with quality score and 30-day metrics GET /v1/ads/keywords (filters: campaignId, adSetId, status, matchType, negative, search)
Add keywords to an ad group POST /v1/ads/keywords with adSetId and keywords (negative: true for negatives)
Pause or enable one keyword PATCH /v1/ads/keywords/{keywordId}
Remove one DELETE /v1/ads/keywords/{keywordId}
Replace a campaign's negatives PUT /v1/ads/campaigns/{campaignId}/negative-keywords (up to 1,000)
Shared negative lists /v1/ads/accounts/negative-keyword-lists (up to 5,000 keywords each, 20 lists per campaign)
Keyword ideas POST /v1/ads/keywords/ideas with seedKeywords, seedUrl or both
Historical volume and bids POST /v1/ads/keywords/historical-metrics (up to 1,000 keywords)

keywordId takes Pozy's id or Google's {adSetId}~{criterionId}. A bare criterion id is rejected because it is only unique inside its ad group. The synced keyword list refreshes about once a day, so keywords added in Google Ads can take a day to appear. Keyword Planner rows come back in Google's raw shape: counters are strings and bids are micros.

#Assets, labels and accounts

  • Sitelinks, callouts and structured snippets exist at account, campaign and ad group level. Create and attach them with the /v1/ads/accounts/sitelinks family, POST /v1/ads/campaigns/{campaignId}/assets or POST /v1/ads/ad-sets/{adSetId}/assets. Updating an asset edits it everywhere it is attached. Removing only detaches it, because Google assets cannot be deleted.
  • Labels organize campaigns, ad groups, ads and keywords: POST /v1/ads/labels, then attach with POST /v1/ads/labels/{labelId}/assignments.
  • Manager accounts: create a client account under a manager with POST /v1/ads/accounts, bill it to the manager's monthly invoicing with POST /v1/ads/accounts/billing, invite a client account to a manager with POST /v1/ads/accounts/manager-links, and accept, decline or end links with PATCH. Ads do not serve until the account has billing.
  • User access: invite people to an ad account by email with POST /v1/ads/accounts/user-invitations.
  • Conversion goals: read and change which conversion actions a campaign bids on with /v1/ads/campaigns/{campaignId}/conversion-goals. Sending conversions is covered in Conversions.

#Recommendations

GET /v1/ads/recommendations lists Google's suggestions (budgets, bidding targets, keywords, assets) with impact.base and impact.potential estimates. Filter by campaignId or types. POST /v1/ads/recommendations/apply applies up to 100, with Google's suggested values or your own in parameters. POST /v1/ads/recommendations/dismiss stops Google suggesting them.

Applying cannot be undone

Applying changes budgets, bids, keywords or assets, has no dry run and is not idempotent. Each item reports applied or failed on its own, and the call returns 200 even when some fail. Check every result.

#Targeting

Locations, languages, age, gender, the top_10 income tier, interests and Customer Match lists. Google has no city radius: use a customLocations pin instead. locationTargetingType decides whether locations reach people who are there (presence) or also people interested in them (presence_or_interest, Google's default). PUT /v1/ads/campaigns/{campaignId}/targeting changes a live campaign's devices, locations, exclusions and languages. Full rules are in Targeting.

#Reporting

You want Call
Spend, clicks, CPC, CPM and conversions per campaign, ad group and ad GET /v1/ads/tree?accountId=...&platform=google
Budget metadata and Search impression share for a campaign GET /v1/ads/campaigns/{campaignId}/analytics
The search terms that triggered your ads, by cost GET /v1/ads/search-terms
Anything else Google reporting answers GET /v1/ads/insights?accountId=...&query=SELECT ...
Local Services leads and their conversations GET /v1/ads/local-services/leads

With GET /v1/ads/insights you can run your own read-only GAQL SELECT. Rows come back exactly as Google returns them, up to 10,000 a page; pass paging.nextPageToken as pageToken for the next one. Selecting segments.date requires a bounded date range in the query, for example DURING LAST_30_DAYS.

GAQL query
curl -G https://api.pozy.ai/v1/ads/insights \
  -H "Authorization: Bearer $POZY_API_KEY" \
  --data-urlencode "accountId=66b2e19d8c3f5a7e9d0b1c3e" \
  --data-urlencode "query=SELECT campaign.name, metrics.cost_micros, metrics.conversions FROM campaign WHERE segments.date DURING LAST_30_DAYS ORDER BY metrics.cost_micros DESC"

Impression share fields are ratios: 0.42 is 42%. Pozy imports the last 90 days of metrics when an account connects. For older days, query GAQL directly.

#Limits and errors

Live calls to Google (GAQL, search terms, Keyword Planner, keyword and asset writes, recommendations) share a burst limit of 15 requests a minute per user and 120 across your accounts, on top of your Pozy rate limit. Cached reads (asset groups, recommendations, shared lists) carry cachedAt and stale: stale: true means Google throttled the request and you are reading the last good copy.

Error Cause Fix
422 feature_not_available goal: "video_views" or a conversion goal on create Use engagement, traffic or awareness.
400 NOT_ENOUGH_SQUARE_MARKETING_IMAGE_ASSET Display create with one image Send both images.landscape and images.square.
400 on headlines or descriptions More than 15 headlines or 4 descriptions after deduplication Trim the lists.
409 on a budget change Several campaigns share this budget Add allowSharedBudgetUpdate: true when you do want all of them changed.
429 with details.resetsAt The burst limit or Google's quota Retry after the time given.
400 invalid_field_value on query Google rejected the GAQL Google's message names the field.

Redirects after consent can carry error=google_ads_auth_failed (the user declined) or error=google_ads_quota_exhausted (retry later).

#Endpoints

Google-only endpoints:

Endpoint What it does
GET /v1/ads/accounts/hierarchy Get manager account hierarchy
POST /v1/ads/accounts/manager-links Invite a client account to a manager
PATCH /v1/ads/accounts/manager-links Accept, decline, cancel or end a manager link
GET /v1/ads/accounts/billing Get ad account billing
POST /v1/ads/accounts/billing Set up ad account billing (monthly invoicing)
GET /v1/ads/accounts/user-invitations List user access invitations
POST /v1/ads/accounts/user-invitations Invite a user to an ad account by email
DELETE /v1/ads/accounts/user-invitations Revoke a user access invitation
GET /v1/ads/accounts/user-invitations/pending-approvals List invitations awaiting multi-party approval
DELETE /v1/ads/accounts/user-invitations/pending-approvals Withdraw an invitation awaiting multi-party approval
GET /v1/ads/recommendations List Google Ads recommendations
POST /v1/ads/recommendations/apply Apply Google Ads recommendations
POST /v1/ads/recommendations/dismiss Dismiss Google Ads recommendations
GET /v1/ads/bid-strategies List portfolio bid strategies
POST /v1/ads/bid-strategies Create portfolio bid strategy
PATCH /v1/ads/bid-strategies/{strategyId} Update portfolio bid strategy
GET /v1/ads/shared-budgets List shared budgets
POST /v1/ads/shared-budgets Create a shared budget
GET /v1/ads/keywords List Search keywords
POST /v1/ads/keywords Add Search ad-group keywords
PATCH /v1/ads/keywords/{keywordId} Pause or enable a Search keyword
DELETE /v1/ads/keywords/{keywordId} Remove a Search keyword
GET /v1/ads/campaigns/{campaignId}/negative-keywords List campaign-level negative keywords
PUT /v1/ads/campaigns/{campaignId}/negative-keywords Replace campaign-level negative keywords
GET /v1/ads/accounts/negative-keyword-lists List negative keyword lists
POST /v1/ads/accounts/negative-keyword-lists Create a negative keyword list
GET /v1/ads/accounts/negative-keyword-lists/{listId} Get a negative keyword list
PUT /v1/ads/accounts/negative-keyword-lists/{listId} Rename a negative keyword list
DELETE /v1/ads/accounts/negative-keyword-lists/{listId} Delete a negative keyword list
PUT /v1/ads/accounts/negative-keyword-lists/{listId}/keywords Replace negative list keywords
GET /v1/ads/campaigns/{campaignId}/negative-keyword-lists List campaign negative lists
PUT /v1/ads/campaigns/{campaignId}/negative-keyword-lists Replace campaign negative lists
POST /v1/ads/keywords/ideas Generate keyword ideas
POST /v1/ads/keywords/historical-metrics Get historical keyword metrics
GET /v1/ads/search-terms Google Ads search terms report
GET /v1/ads/campaigns/{campaignId}/asset-groups List Performance Max asset groups
POST /v1/ads/campaigns/{campaignId}/asset-groups Create a Performance Max asset group
GET /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId} Get a Performance Max asset group
PATCH /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId} Update a Performance Max asset group
DELETE /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId} Remove a Performance Max asset group
POST /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId}/assets Link or unlink asset group assets
PUT /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId}/listing-group-filters Replace an asset group's listing-group tree
GET /v1/ads/campaigns/{campaignId}/conversion-goals Get campaign conversion goals
PATCH /v1/ads/campaigns/{campaignId}/conversion-goals Update campaign conversion goals
GET /v1/ads/campaigns/{campaignId}/targeting Read a Google campaign's device, location, excluded location, and language targeting
PUT /v1/ads/campaigns/{campaignId}/targeting Edit a Google campaign's device, location, excluded location, or language targeting
GET /v1/ads/campaigns/{campaignId}/ad-schedule Read a campaign's ad schedule (dayparting)
PUT /v1/ads/campaigns/{campaignId}/ad-schedule Replace a campaign's ad schedule (dayparting)
GET /v1/ads/campaigns/{campaignId}/bidding Read a campaign's current bidding
POST /v1/ads/ad-sets Create a standalone ad group
GET /v1/ads/local-services/leads Google Local Services Ads leads
GET /v1/ads/local-services/leads/{leadId}/conversations List lead conversations

Assets and labels:

Endpoint What it does
GET /v1/ads/accounts/sitelinks List account sitelinks
POST /v1/ads/accounts/sitelinks Add account sitelinks
PUT /v1/ads/accounts/sitelinks Update account sitelinks
DELETE /v1/ads/accounts/sitelinks Remove account sitelink
GET /v1/ads/accounts/callouts List account callouts
POST /v1/ads/accounts/callouts Add account callouts
PUT /v1/ads/accounts/callouts Update account callouts
DELETE /v1/ads/accounts/callouts Remove account callout
GET /v1/ads/accounts/structured-snippets List account snippets
POST /v1/ads/accounts/structured-snippets Add account snippets
PUT /v1/ads/accounts/structured-snippets Update account snippets
DELETE /v1/ads/accounts/structured-snippets Remove account snippet
GET /v1/ads/campaigns/{campaignId}/assets List campaign assets
POST /v1/ads/campaigns/{campaignId}/assets Attach campaign assets
PUT /v1/ads/campaigns/{campaignId}/assets Update campaign assets
DELETE /v1/ads/campaigns/{campaignId}/assets Remove campaign assets
GET /v1/ads/ad-sets/{adSetId}/assets List ad-group assets
POST /v1/ads/ad-sets/{adSetId}/assets Attach ad-group assets
PUT /v1/ads/ad-sets/{adSetId}/assets Update ad-group assets
DELETE /v1/ads/ad-sets/{adSetId}/assets Remove ad-group assets
GET /v1/ads/labels List ad labels
POST /v1/ads/labels Create a Google Ads label
PATCH /v1/ads/labels/{labelId} Update a Google Ads label
DELETE /v1/ads/labels/{labelId} Remove a Google Ads label
POST /v1/ads/labels/{labelId}/assignments Attach a Google Ads label
DELETE /v1/ads/labels/{labelId}/assignments Detach a Google Ads label

The shared campaign, ad and reporting endpoints are listed on the ads overview.