---
title: "Google Ads"
description: "Stream Google Ads campaigns, ads, criteria, assets and daily performance reports into Streamkap with a service account or an OAuth refresh token."
---

The Google Ads source reads one Google Ads account, several, or every client account under a manager, and writes each selected resource to its own topic, `source_<id>.google_ads.<resource>`.

import Callout from 'blume/components/content/Callout.astro';

:::info
This connector is in alpha. See [API sources](/api-sources) for how API sources work and what alpha means.
:::

## Prerequisites

* A Google Ads account, or a manager account with client accounts.
* A Google Cloud project where you can enable APIs and create OAuth clients or service accounts.
* One of:
  * a Google user with at least read-only access to the accounts and 2-Step Verification turned on, or
  * a service account you can add as a user of the accounts.

No developer token is needed. Google stopped using them on 9 September 2026, and API access now belongs to the Cloud project that owns the OAuth client or service account.

## Google Setup

### 1. Enable the Google Ads API

In the [Google Cloud console](https://console.cloud.google.com/), select your project and enable the **Google Ads API** under **APIs & Services → Library**.

### 2. Get Production Access

A new project has **Test** access, which reads test accounts only. On the project's **Google Ads API Overview** page, apply for:

* **Explorer**: 2,880 operations a day against production accounts.
* **Basic**: 15,000 operations a day.

The budget belongs to the Cloud project and is shared by every tool that uses it. See [Quota](#quota).

### 3. Create the Credential

**OAuth client**

* Under **APIs & Services → OAuth consent screen**, set the consent screen to **In production**, or use the **Internal** user type. A consent screen left in **Testing** issues refresh tokens that expire after 7 days.
* Under **APIs & Services → Credentials**, create an **OAuth client ID** of type **Web application**.
* Copy the **Client ID** and **Client secret**.

Then mint a refresh token with this same client for the `https://www.googleapis.com/auth/adwords` scope with offline access. Google keeps at most 100 refresh tokens per user and client and silently revokes the oldest.

**Service account**

A service account needs no consent screen and has no 7-day Testing expiry or 100-token limit ([Google's guide](https://developers.google.com/google-ads/api/docs/oauth/service-accounts)).

* Under **IAM & Admin → Service accounts**, create a service account (it needs no Cloud role), then choose **Keys → Add key → Create new key → JSON** and keep the file.
* In Google Ads, open **Admin → Access and security → Users** and add the service account's email (`client_email` in the key file) with read-only access. No domain-wide delegation is needed.
* A service account can be added to at most 20 Google Ads accounts. For more, add it to a manager account and set **Login customer ID (manager)** to that manager.

### 4. Find Your Customer IDs

The customer ID is the ten-digit number at the top right of Google Ads, such as `123-456-7890`. Dashes are optional. If you reach the account through a manager account, also note the manager's customer ID.

## Streamkap Setup

### 1. Create the Source

* Navigate to [Add Connectors](https://app.streamkap.com/connectors/add?tab=Sources).
* Choose **Google Ads**.

### 2. Connection Settings (Auth Tab)

* **Source name**: A unique name for this source.
* **Authentication**: **Service account** (default) or **Paste a refresh token**.
* **OAuth client ID**, **OAuth client secret** and **Refresh token** *(Paste a refresh token)*: From your OAuth client, and a token minted with it.
* **Service account key** *(Service account)*: The whole JSON key file.
* **Customer ID**: The account to sync. Once the credentials are entered, the field lists the accounts they reach by name; pick one or type the ID. For a manager account, turn on **Include client accounts**.
* **Additional customer IDs** *(optional)*: More accounts to read with the same credentials. See [Several Accounts](#several-accounts).
* **Login customer ID (manager)** *(optional)*: The manager account through which the Google user or service account reaches these accounts. Leave it empty for direct access.
* **Include client accounts** *(default off)*: Treat **Customer ID** as a manager and sync every enabled client account under it, including clients added later.

**Test connection** and **Test access** work as described in [API sources](/api-sources#test-connection-and-test-access). For Google Ads:

* Each search costs one operation of your project's daily budget. The test checks at most the first 10 accounts.
* A report is read for yesterday in each account's time zone, with its row count. A report with more than 25,000 rows a day is blocked; one with more rows than restatement can track gets a warning (see [Restatement](#restatement)).
* A custom query runs for one day, which Google validates in full.
* Warnings that do not block saving: a test account (it serves no ads), a manager with no enabled client accounts, or an account the user does not reach directly (set **Login customer ID (manager)**).

### 3. Settings Tab

![Google Ads source Settings step with the resource presets, the Resources list, Custom queries, Conversion window and API version](/blume-assets/content/docs/_assets/images/docs/sources-create-google-ads-settings.png)

* **Resources**: The objects and reports to sync. The ten defaults are marked in [Supported Resources](#supported-resources) and use about 360 operations a day per account. Select `custom_<name>` for each query in **Custom queries**. Presets under **Add a preset**:
  * **Overview**: the ten defaults.
  * **Search**: Overview plus `keyword_view`, `search_term_view` and `ad_group_ad_performance`.
  * **Audience and geography**: `campaign`, `ad_group`, `customer`, `geographic_view`, `user_location_view`, `age_range_view`, `gender_view` and `ad_group_audience_view`.
  * **Performance Max and assets**: `campaign`, `customer`, `asset_group`, `asset`, `campaign_asset`, `ad_group_asset`, `asset_set` and `campaign_asset_set`.
* **Custom queries** *(optional)*: GAQL queries as a JSON list of `{name, query}`. See [Custom Queries](#custom-queries).
* **Conversion window (days)** *(default 30, max 90)*: How many days back reports are re-read for late conversions. Set it to the longest click-through conversion window of your conversion actions.
* **API version**: `v25`, the only version.
* **Backfill start date**: **Last two years (change history 29 days; clicks, calls and leads 90 days)** or **Specific start date**. See [Backfill](#backfill) and [API sources](/api-sources#backfill-start-date).

### 4. Review and Create

Check the summary and click **Create**. Then send the topics to a destination: see [Send topics to a destination](/api-sources#send-topics-to-a-destination).

## Editing the Source

* **Credentials**: click **Replace** on the client secret, refresh token or service account key and save. If you change **OAuth client ID**, enter the new client's secret and a refresh token minted with it in the same save.
* **Custom queries**: a synced query cannot change under the same name, because its fields define the columns and the `id`. The poll fails with that fix. Add the changed query under a new name.

Everything else follows [Editing an API source](/api-sources#editing-an-api-source).

## Supported Resources

58 resources, plus your custom queries. Every resource reads every account of the source into one topic.

### Entities

Read whole on the first poll, then only what Google's `change_status` lists as changed, every 5 minutes.

| Resource | What it holds | Default |
| --- | --- | --- |
| `campaign` | Campaigns: status, channel, bidding, budget, network and geo settings, dates | Yes |
| `ad_group` | Ad groups: status, type, bids | Yes |
| `ad_group_ad` | Ads: type, text, final URLs, policy summary | Yes |
| `ad_group_criterion` | Keywords, audiences, placements and other ad-group targeting | Yes |
| `campaign_criterion` | Campaign targeting: locations, languages, devices, schedules, negatives | Yes |
| `campaign_budget` | Budgets: amount, period, delivery method, shared flag | Yes |
| `ad_group_bid_modifier` | Ad-group bid adjustments | |
| `asset`, `asset_group`, `asset_set` | Assets, Performance Max asset groups, asset sets | |
| `ad_group_asset`, `campaign_asset`, `customer_asset`, `campaign_asset_set` | Links between assets and ad groups, campaigns or the account | |
| `shared_set`, `campaign_shared_set` | Shared negative keyword and placement lists, and the campaigns using them | |

### Daily Re-reads

Read whole once a day. New and changed rows are sent; a row missing from a complete read is deleted.

| Resource | What it holds | Default |
| --- | --- | --- |
| `customer` | The account: name, currency, time zone, status, auto-tagging, tracking template | Yes |
| `customer_client` | The accounts under a manager | |
| `label`, `customer_label`, `campaign_label`, `ad_group_label`, `ad_group_ad_label`, `ad_group_criterion_label` | Labels and what they are attached to | |
| `bidding_strategy` | Portfolio bidding strategies | |
| `campaign_bid_modifier` | Campaign bid adjustments | |
| `audience`, `user_interest` | Audiences, and Google's interest categories | |
| `local_services_lead` | Local Services leads | |
| `geo_target_constant` | Google's location list, read weekly | |

### Logs

Read hourly by their own timestamp. Each row is written once.

| Resource | What it holds |
| --- | --- |
| `change_event` | Who changed what and when, with old and new values. Google keeps 30 days. |
| `call_view` | Calls: caller area, duration, status |
| `lead_form_submission_data` | Lead-form submissions |
| `local_services_lead_conversation` | Messages and calls on Local Services leads |

### Reports

One row per day per key, in the account's time zone, re-read hourly for the conversion window. Each report selects the standard metrics its resource allows: impressions, clicks, cost, conversions and value, all conversions, view-through conversions, interactions, video views, CTR, average CPC and CPM, search impression share and Active View metrics.

| Resource | One row per day per | Default |
| --- | --- | --- |
| `account_performance` | account | Yes |
| `campaign_performance` | campaign | Yes |
| `ad_group_performance` | ad group | Yes |
| `account_performance_by_network_device`, `campaign_performance_by_network_device`, `ad_group_performance_by_network_device` | …, network and device | |
| `account_performance_hourly`, `campaign_performance_hourly`, `ad_group_performance_hourly` | …, and hour | |
| `campaign_budget_performance` | budget | |
| `ad_group_ad_performance` | ad, network and device | |
| `keyword_view` | keyword | |
| `search_term_view` | search term and ad group | |
| `display_keyword_view`, `topic_view` | Display keyword or topic | |
| `geographic_view`, `user_location_view`, `location_view` | country and location type, user location, or targeted location | |
| `shopping_performance_view` | product, channel and device | |
| `age_range_view`, `gender_view` | age range or gender, and device | |
| `ad_group_audience_view` | audience, network and device | |
| `landing_page_view` | landing page | |
| `click_view` | click (GCLID). Google serves one day per query and keeps 90 days. | |

`change_status` is not a topic: it drives the entities. Select `change_event` for the change history.

## Custom Queries

**Custom queries** is a JSON list of `{name, query}`. Select each as `custom_<name>`. Google's [Query Builder](https://developers.google.com/google-ads/api/fields/v25/overview_query_builder) helps write them.

```json
[
  {"name": "campaign_devices", "query": "SELECT campaign.id, campaign.name, segments.device, metrics.clicks, metrics.cost_micros FROM campaign WHERE campaign.status = 'ENABLED'"}
]
```

* Write `SELECT`, `FROM` and an optional `WHERE`. `ORDER BY`, `LIMIT`, `PARAMETERS` and date filters are refused: Streamkap sets them.
* A query **with metrics** is a report: Streamkap adds `segments.date` and re-reads it for the conversion window. Its `FROM` must allow `segments.date`.
* A query **without metrics** is re-read whole daily, like the daily re-reads.
* `FROM change_status`, `change_event` and `click_view` are refused; use their resources.
* Each query is checked on save against Google's field list for the API version: fields must exist, be selectable (and filterable where filtered) and be compatible with the `FROM` resource.

## Behavior & Limits

### Record Columns

* Column names are the GAQL field names with `.` replaced by `_`, such as `campaign_id` and `metrics_clicks`. A name over 63 characters keeps its first 54 characters plus a short hash.
* **`id`** is the Kafka key. For an entity, daily re-read or log it is the resource name, such as `customers/1234567890/campaigns/2001`. For a report it is the resource name, then any segmenting resource names, the date and each other selected segment, joined with `|`, such as `customers/1234567890/campaigns/2001|2026-09-24|MOBILE`.
* **`customer_id`** names the account the row came from.
* Entities also carry `streamkap_changed_at` (UTC), `change_status_last_change_date_time` (Google's time, in the account's time zone) and `change_status_resource_status`.
* 64-bit numbers (ids, `cost_micros`, `impressions`, `clicks`) are integers, other metrics doubles, enums their names, dates `YYYY-MM-DD`, nested messages and lists JSON text. A field Google omits takes its default (`0`, `false`, empty string or `UNSPECIFIED`).
* Money is in micros of the account's currency: `cost_micros` 1,500,000 is 1.50.

### Backfill

The **Backfill start date** bounds each resource's first sync only:

* **Reports** start on that date, or two years back by default, never more than 37 months back. `click_view` reads at most 90 days.
* **Logs** start on that date, or 90 days back by default. `change_event` reads at most 29 days.
* **Entities and daily re-reads** are always read whole.

### Restatement

Google keeps attributing conversions to past clicks. Each hourly poll re-reads the last **Conversion window (days)** of every report and re-sends rows whose values changed, under the same `id`. Set the destination to upsert on `id`, and never sum a report topic.

* Streamkap tracks 10,000 report rows across the window. A report with more than 10,000 ÷ (conversion window + 1) rows a day (about 320 at 30 days, 110 at 90) re-sends some rows of its oldest days on every poll as duplicate upserts.
* A row Google stops serving, such as a day whose metrics fell to zero, keeps its last values.

### Entity Changes and Removals

Each poll reads `change_status` from where the last one stopped, minus an hour to cover daylight-saving changes, and refetches each changed entity whole. Google lists a change up to three minutes after it happens.

Every entity is also re-read whole every 7 days, to catch changes Google makes itself that `change_status` may not list, and after a pause longer than 88 days (`change_status` keeps 90).

A removed campaign, ad group, ad or criterion arrives as an upsert with `status` `REMOVED`, not as a delete, so its spend in the reports keeps a matching row. One Google no longer returns arrives as its key, `REMOVED` and the change columns.

### Several Accounts

**Additional customer IDs** and **Include client accounts** add accounts to **Customer ID**. Client accounts under a manager are looked up again every hour. Every row carries its `customer_id`, and each account's reports use its own time zone.

An account added after a resource has synced is read from that resource's current position. Reset the source to backfill its history.

When Google refuses one account (no access, not enabled, or a metric unavailable to it), that account is skipped with a warning, its existing rows stay, and each poll retries it. When the source reads one account, or every account is refused, the refusal applies to the whole source; a metric refused on every account pauses only that report.

### Quota

Google counts **operations** per Cloud project over a rolling 24 hours. Each search is one operation; extra pages are free; a refused request still counts. The default selection costs about 360 a day per account.

* **Project budget spent**: the source shows **Throttled** until Google's retry delay, or an hour, then resumes.
* **One account's budget spent**: that account pauses and the others continue. A report advances only as far as the paused account can catch up.
* **Short-term rate limit**: the request waits Google's retry delay, up to a minute, and retries.

To use fewer operations, select fewer resources or apply for Basic access.

### Cadence

* Entities: every 5 minutes.
* Reports and logs: hourly.
* Daily re-reads: daily; `geo_target_constant` weekly.

## Troubleshooting

**"Google rejected the refresh token …" or "Google rejected the OAuth client or refresh token …"**

The token was revoked, unused for six months, dropped past Google's 100-token limit, or issued by a consent screen in **Testing**, whose tokens expire after 7 days.

**Resolution:** Set the consent screen to **In production**, or use the **Internal** user type. Then mint a new refresh token with the same client, paste it with **Replace** on the **Auth** tab, and save.

**"… has no access to Google Ads" or "Google requires 2-Step Verification …"**

The Google user behind the token cannot sign in to Google Ads, or has not turned on 2-Step Verification, which the manager account enforces.

**Resolution:** Give the user access or turn on 2-Step Verification, then connect again or paste a new token minted by that user.

**"The Google Cloud project that owns this OAuth client (or service account) has only Test access"**

**Resolution:** Apply for **Explorer** or **Basic** access on the project's **Google Ads API Overview** page. See [Get Production Access](#2-get-production-access).

**"Account … is a manager account"**

A manager has no campaigns or metrics of its own.

**Resolution:** Put a client account in **Customer ID** and the manager in **Login customer ID (manager)**, or turn on **Include client accounts**.

**"Google Ads denied the Google user who authorized the OAuth client access to account …"**

**Resolution:** Grant the user access under **Admin → Access and security** in Google Ads, or set **Login customer ID (manager)** to the manager that gives access.

**"Google Ads denied the service account … access to account …"**

**Resolution:** Add the service account's email under **Admin → Access and security → Users** on that account, or set **Login customer ID (manager)** to the manager you added it to.

**"Google rejected the service account key"**

The key or its service account was deleted or disabled, or the pasted text is not the key file.

**Resolution:** Create a new JSON key under **IAM & Admin → Service accounts → Keys → Add key**, paste it with **Replace**, and save.

**"Login customer ID … does not manage account …"**

**Resolution:** Set it to the manager above the account, or clear it for direct access.

**Numbers differ from Google Ads**

Recent days are still being revised for conversions. Compare a day older than the conversion window, in the account's time zone, and compare the same conversion columns (`metrics_conversions` or `metrics_all_conversions`).

## Related Documentation

- [API sources](/api-sources) - how API sources work and how to send their topics to a destination
- [Google Analytics 4](/google-analytics-source) - daily GA4 reports, preset or your own
- [Facebook Ads](/facebook-ads-source) - Meta ad objects and daily insights reports
