# 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](https://pozy.ai/docs/ads/reporting) 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`.

```bash cURL
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"]
  }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/ads/create", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Idempotency-Key": "search-plumbing-austin",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    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"],
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.ad.platformAdSetId); // the ad group id
```
```python Python
import os, requests

resp = requests.post(
    "https://api.pozy.ai/v1/ads/create",
    headers={
        "Authorization": f"Bearer {os.environ['POZY_API_KEY']}",
        "Idempotency-Key": "search-plumbing-austin",
    },
    json={
        "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"],
    },
    timeout=60,
)
resp.raise_for_status()
print(resp.json()["ad"]["_id"])
```

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

```json
{
  "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.

```json
{
  "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](https://pozy.ai/docs/ads/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](https://pozy.ai/docs/ads/targeting#google).

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

```bash
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](https://pozy.ai/docs/getting-started/rate-limits). 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`](https://pozy.ai/docs/api/getAdAccountHierarchy) | Get manager account hierarchy |
| [`POST /v1/ads/accounts/manager-links`](https://pozy.ai/docs/api/inviteAdAccountToManager) | Invite a client account to a manager |
| [`PATCH /v1/ads/accounts/manager-links`](https://pozy.ai/docs/api/updateAdAccountManagerLink) | Accept, decline, cancel or end a manager link |
| [`GET /v1/ads/accounts/billing`](https://pozy.ai/docs/api/getAdAccountBilling) | Get ad account billing |
| [`POST /v1/ads/accounts/billing`](https://pozy.ai/docs/api/setUpAdAccountBilling) | Set up ad account billing (monthly invoicing) |
| [`GET /v1/ads/accounts/user-invitations`](https://pozy.ai/docs/api/listAdAccountUserInvitations) | List user access invitations |
| [`POST /v1/ads/accounts/user-invitations`](https://pozy.ai/docs/api/inviteAdAccountUser) | Invite a user to an ad account by email |
| [`DELETE /v1/ads/accounts/user-invitations`](https://pozy.ai/docs/api/revokeAdAccountUserInvitation) | Revoke a user access invitation |
| [`GET /v1/ads/accounts/user-invitations/pending-approvals`](https://pozy.ai/docs/api/listAdAccountUserInvitationApprovals) | List invitations awaiting multi-party approval |
| [`DELETE /v1/ads/accounts/user-invitations/pending-approvals`](https://pozy.ai/docs/api/withdrawAdAccountUserInvitationApproval) | Withdraw an invitation awaiting multi-party approval |
| [`GET /v1/ads/recommendations`](https://pozy.ai/docs/api/listGoogleRecommendations) | List Google Ads recommendations |
| [`POST /v1/ads/recommendations/apply`](https://pozy.ai/docs/api/applyGoogleRecommendations) | Apply Google Ads recommendations |
| [`POST /v1/ads/recommendations/dismiss`](https://pozy.ai/docs/api/dismissGoogleRecommendations) | Dismiss Google Ads recommendations |
| [`GET /v1/ads/bid-strategies`](https://pozy.ai/docs/api/listBidStrategies) | List portfolio bid strategies |
| [`POST /v1/ads/bid-strategies`](https://pozy.ai/docs/api/createBidStrategy) | Create portfolio bid strategy |
| [`PATCH /v1/ads/bid-strategies/{strategyId}`](https://pozy.ai/docs/api/updateBidStrategy) | Update portfolio bid strategy |
| [`GET /v1/ads/shared-budgets`](https://pozy.ai/docs/api/listSharedBudgets) | List shared budgets |
| [`POST /v1/ads/shared-budgets`](https://pozy.ai/docs/api/createSharedBudget) | Create a shared budget |
| [`GET /v1/ads/keywords`](https://pozy.ai/docs/api/listAdKeywords) | List Search keywords |
| [`POST /v1/ads/keywords`](https://pozy.ai/docs/api/addAdKeywords) | Add Search ad-group keywords |
| [`PATCH /v1/ads/keywords/{keywordId}`](https://pozy.ai/docs/api/updateAdKeyword) | Pause or enable a Search keyword |
| [`DELETE /v1/ads/keywords/{keywordId}`](https://pozy.ai/docs/api/removeAdKeyword) | Remove a Search keyword |
| [`GET /v1/ads/campaigns/{campaignId}/negative-keywords`](https://pozy.ai/docs/api/listCampaignNegativeKeywords) | List campaign-level negative keywords |
| [`PUT /v1/ads/campaigns/{campaignId}/negative-keywords`](https://pozy.ai/docs/api/replaceCampaignNegativeKeywords) | Replace campaign-level negative keywords |
| [`GET /v1/ads/accounts/negative-keyword-lists`](https://pozy.ai/docs/api/listAdNegativeKeywordLists) | List negative keyword lists |
| [`POST /v1/ads/accounts/negative-keyword-lists`](https://pozy.ai/docs/api/createAdNegativeKeywordList) | Create a negative keyword list |
| [`GET /v1/ads/accounts/negative-keyword-lists/{listId}`](https://pozy.ai/docs/api/getAdNegativeKeywordList) | Get a negative keyword list |
| [`PUT /v1/ads/accounts/negative-keyword-lists/{listId}`](https://pozy.ai/docs/api/updateAdNegativeKeywordList) | Rename a negative keyword list |
| [`DELETE /v1/ads/accounts/negative-keyword-lists/{listId}`](https://pozy.ai/docs/api/deleteAdNegativeKeywordList) | Delete a negative keyword list |
| [`PUT /v1/ads/accounts/negative-keyword-lists/{listId}/keywords`](https://pozy.ai/docs/api/replaceAdNegativeKeywordListKeywords) | Replace negative list keywords |
| [`GET /v1/ads/campaigns/{campaignId}/negative-keyword-lists`](https://pozy.ai/docs/api/listCampaignNegativeKeywordLists) | List campaign negative lists |
| [`PUT /v1/ads/campaigns/{campaignId}/negative-keyword-lists`](https://pozy.ai/docs/api/replaceCampaignNegativeKeywordLists) | Replace campaign negative lists |
| [`POST /v1/ads/keywords/ideas`](https://pozy.ai/docs/api/generateKeywordIdeas) | Generate keyword ideas |
| [`POST /v1/ads/keywords/historical-metrics`](https://pozy.ai/docs/api/generateKeywordHistoricalMetrics) | Get historical keyword metrics |
| [`GET /v1/ads/search-terms`](https://pozy.ai/docs/api/getAdsSearchTerms) | Google Ads search terms report |
| [`GET /v1/ads/campaigns/{campaignId}/asset-groups`](https://pozy.ai/docs/api/listGoogleAssetGroups) | List Performance Max asset groups |
| [`POST /v1/ads/campaigns/{campaignId}/asset-groups`](https://pozy.ai/docs/api/createGoogleAssetGroup) | Create a Performance Max asset group |
| [`GET /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId}`](https://pozy.ai/docs/api/getGoogleAssetGroup) | Get a Performance Max asset group |
| [`PATCH /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId}`](https://pozy.ai/docs/api/updateGoogleAssetGroup) | Update a Performance Max asset group |
| [`DELETE /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId}`](https://pozy.ai/docs/api/removeGoogleAssetGroup) | Remove a Performance Max asset group |
| [`POST /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId}/assets`](https://pozy.ai/docs/api/editGoogleAssetGroupAssets) | Link or unlink asset group assets |
| [`PUT /v1/ads/campaigns/{campaignId}/asset-groups/{assetGroupId}/listing-group-filters`](https://pozy.ai/docs/api/replaceGoogleListingGroupFilters) | Replace an asset group's listing-group tree |
| [`GET /v1/ads/campaigns/{campaignId}/conversion-goals`](https://pozy.ai/docs/api/getCampaignConversionGoals) | Get campaign conversion goals |
| [`PATCH /v1/ads/campaigns/{campaignId}/conversion-goals`](https://pozy.ai/docs/api/updateCampaignConversionGoals) | Update campaign conversion goals |
| [`GET /v1/ads/campaigns/{campaignId}/targeting`](https://pozy.ai/docs/api/getCampaignTargeting) | Read a Google campaign's device, location, excluded location, and language targeting |
| [`PUT /v1/ads/campaigns/{campaignId}/targeting`](https://pozy.ai/docs/api/updateCampaignTargeting) | Edit a Google campaign's device, location, excluded location, or language targeting |
| [`GET /v1/ads/campaigns/{campaignId}/ad-schedule`](https://pozy.ai/docs/api/getCampaignAdSchedule) | Read a campaign's ad schedule (dayparting) |
| [`PUT /v1/ads/campaigns/{campaignId}/ad-schedule`](https://pozy.ai/docs/api/updateCampaignAdSchedule) | Replace a campaign's ad schedule (dayparting) |
| [`GET /v1/ads/campaigns/{campaignId}/bidding`](https://pozy.ai/docs/api/getCampaignBidding) | Read a campaign's current bidding |
| [`POST /v1/ads/ad-sets`](https://pozy.ai/docs/api/createAdSet) | Create a standalone ad group |
| [`GET /v1/ads/local-services/leads`](https://pozy.ai/docs/api/listLocalServicesLeads) | Google Local Services Ads leads |
| [`GET /v1/ads/local-services/leads/{leadId}/conversations`](https://pozy.ai/docs/api/listLocalServicesLeadConversations) | List lead conversations |

Assets and labels:

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/ads/accounts/sitelinks`](https://pozy.ai/docs/api/listAccountSitelinks) | List account sitelinks |
| [`POST /v1/ads/accounts/sitelinks`](https://pozy.ai/docs/api/addAccountSitelinks) | Add account sitelinks |
| [`PUT /v1/ads/accounts/sitelinks`](https://pozy.ai/docs/api/updateAccountSitelinks) | Update account sitelinks |
| [`DELETE /v1/ads/accounts/sitelinks`](https://pozy.ai/docs/api/removeAccountSitelink) | Remove account sitelink |
| [`GET /v1/ads/accounts/callouts`](https://pozy.ai/docs/api/listAccountCallouts) | List account callouts |
| [`POST /v1/ads/accounts/callouts`](https://pozy.ai/docs/api/addAccountCallouts) | Add account callouts |
| [`PUT /v1/ads/accounts/callouts`](https://pozy.ai/docs/api/updateAccountCallouts) | Update account callouts |
| [`DELETE /v1/ads/accounts/callouts`](https://pozy.ai/docs/api/removeAccountCallout) | Remove account callout |
| [`GET /v1/ads/accounts/structured-snippets`](https://pozy.ai/docs/api/listAccountStructuredSnippets) | List account snippets |
| [`POST /v1/ads/accounts/structured-snippets`](https://pozy.ai/docs/api/addAccountStructuredSnippets) | Add account snippets |
| [`PUT /v1/ads/accounts/structured-snippets`](https://pozy.ai/docs/api/updateAccountStructuredSnippets) | Update account snippets |
| [`DELETE /v1/ads/accounts/structured-snippets`](https://pozy.ai/docs/api/removeAccountStructuredSnippet) | Remove account snippet |
| [`GET /v1/ads/campaigns/{campaignId}/assets`](https://pozy.ai/docs/api/listCampaignAssets) | List campaign assets |
| [`POST /v1/ads/campaigns/{campaignId}/assets`](https://pozy.ai/docs/api/attachCampaignAssets) | Attach campaign assets |
| [`PUT /v1/ads/campaigns/{campaignId}/assets`](https://pozy.ai/docs/api/updateCampaignAssets) | Update campaign assets |
| [`DELETE /v1/ads/campaigns/{campaignId}/assets`](https://pozy.ai/docs/api/removeCampaignAssets) | Remove campaign assets |
| [`GET /v1/ads/ad-sets/{adSetId}/assets`](https://pozy.ai/docs/api/listAdGroupAssets) | List ad-group assets |
| [`POST /v1/ads/ad-sets/{adSetId}/assets`](https://pozy.ai/docs/api/attachAdGroupAssets) | Attach ad-group assets |
| [`PUT /v1/ads/ad-sets/{adSetId}/assets`](https://pozy.ai/docs/api/updateAdGroupAssets) | Update ad-group assets |
| [`DELETE /v1/ads/ad-sets/{adSetId}/assets`](https://pozy.ai/docs/api/removeAdGroupAssets) | Remove ad-group assets |
| [`GET /v1/ads/labels`](https://pozy.ai/docs/api/listAdLabels) | List ad labels |
| [`POST /v1/ads/labels`](https://pozy.ai/docs/api/createAdLabel) | Create a Google Ads label |
| [`PATCH /v1/ads/labels/{labelId}`](https://pozy.ai/docs/api/updateAdLabel) | Update a Google Ads label |
| [`DELETE /v1/ads/labels/{labelId}`](https://pozy.ai/docs/api/removeAdLabel) | Remove a Google Ads label |
| [`POST /v1/ads/labels/{labelId}/assignments`](https://pozy.ai/docs/api/attachAdLabel) | Attach a Google Ads label |
| [`DELETE /v1/ads/labels/{labelId}/assignments`](https://pozy.ai/docs/api/detachAdLabel) | Detach a Google Ads label |

The shared campaign, ad and reporting endpoints are listed on the [ads overview](https://pozy.ai/docs/ads#endpoints).
