# Google Ads Transparency Scraper & Competitor Monitor (`kingly_contest_7cb/google-ads-transparency-monitor`) Actor

Scrape Google ads (Search, YouTube, Shopping, Maps, Play) for any advertiser or domain from the Ads Transparency Center. Monitor competitors for new, stopped and new-country ads with Slack/webhook alerts, or bulk-check which domains advertise.

- **URL**: https://apify.com/kingly_contest_7cb/google-ads-transparency-monitor.md
- **Developed by:** [Carina Hatton](https://apify.com/kingly_contest_7cb) (community)
- **Categories:** Lead generation, SEO tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 1,000 ad results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Google Ads Transparency Scraper & Monitor

See the ads any company runs on Google Search, YouTube, Shopping, Maps and Play, straight from the public [Google Ads Transparency Center](https://adstransparency.google.com). Export a competitor's ads to a spreadsheet, **get alerted when a rival launches or stops an ad**, or check a list of domains to see who advertises on Google.

Enter an advertiser name, a domain, an advertiser ID or any Transparency Center URL. You get structured data: ad text where Google publishes it, image URLs, YouTube video IDs, first and last shown dates, formats and a direct link to every ad.

### Built for agencies and competitor-research teams

- **Competitor ad research**: every creative a competitor is running right now, and since when.
- **Competitor alerts**: schedule the monitor and get a Slack message or webhook when a rival launches new ads, stops ads or enters a new country.
- **Agency pitches and audits**: export a prospect's ad history before the first call.
- **Lead qualification**: check a list of domains to see which companies advertise on Google, and roughly how much.
- **Creative inspiration**: collect headlines, descriptions and YouTube videos from your category.

### Three modes

| Mode | What it does | Typical use |
|---|---|---|
| **Scrape ads** | Lists ads for each advertiser or domain, newest first, with optional per-ad details | One-off research and exports |
| **Monitor for changes** | Compares the latest ads with the previous run and reports `NEW_AD`, `AD_STOPPED`, `AD_RESUMED` and `NEW_REGION` | Scheduled daily or weekly competitor tracking |
| **Batch domain check** | One quick lookup per domain: is it advertising on Google, roughly how many ads does Google say it has, and when was its latest ad shown | Qualifying lead lists |

### Quick start

#### Scrape a competitor's ads

```json
{
  "mode": "scrape",
  "targets": ["nike.com"],
  "regions": ["US"],
  "maxResultsPerTarget": 100
}
```

Each ad becomes one row in the dataset. Turn on `"fetchDetails": true` for all variations and per-region dates (charged per ad, see Pricing).

#### Monitor competitors for changes

```json
{
  "mode": "monitor",
  "targets": ["AR16735076323512287233", "hubspot.com"],
  "regions": ["US"],
  "monitorStoreName": "my-competitors",
  "slackWebhookUrl": "https://hooks.slack.com/services/…"
}
```

Save it as a Task and schedule it, for example daily. The first run records a baseline; later runs report only what changed.

#### Check which domains advertise

```json
{
  "mode": "domainCheck",
  "targets": ["nike.com", "hubspot.com", "shopify.com"],
  "regions": ["US"]
}
```

One row per domain and region: is it advertising now, Google's estimated ad count, its latest ad date and the advertiser accounts behind it.

### Input

| Field | Notes |
|---|---|
| **Advertisers, domains or URLs** | One per line. The type is detected automatically:<br>• `AR…` advertiser ID<br>• `nike.com` domain<br>• `HubSpot` name, looked up with Google's own search suggestions<br>• any `adstransparency.google.com` URL, including single-ad URLs. Filters in the URL (region, platform, format, dates) are respected. |
| **Regions** | One or more countries, or "Anywhere". |
| **Platforms** | Search, YouTube, Shopping, Maps, Play. Leave empty for all platforms. |
| **Ad formats** | Text, Image, Video. Leave empty for all formats. |
| **Shown from / until** | Date range. |
| **Max ads per target / in total** | Strict caps. The run stops when it reaches them. |
| **Fetch ad details** | Adds all variations and per-region dates. For EU regions it also adds the impression ranges Google publishes, by platform. Costs one extra request per ad. |
| **Proxy** | Residential proxy is the default and is strongly recommended. |

Example with a URL and filters:

```json
{
  "mode": "scrape",
  "targets": ["hubspot.com", "https://adstransparency.google.com/advertiser/AR16735076323512287233?region=US&platform=YOUTUBE"],
  "regions": ["US"],
  "formats": ["VIDEO"],
  "maxResultsPerTarget": 50
}
```

### Output

Each ad is one dataset item. Below is a real item from a test run on 30 Oct 2026, shortened:

```json
{
  "type": "ad",
  "advertiserId": "AR10072600183532683265",
  "advertiserName": "Hubspot, Inc.",
  "creativeId": "CR15036202842803142657",
  "format": "VIDEO",
  "platforms": ["YOUTUBE"],
  "platformsSource": "platform-filter",
  "firstShown": "…",
  "lastShown": "2026-10-30T…Z",
  "matchedRegions": ["US"],
  "youtubeVideoId": "kJ26TwqroAk",
  "youtubeUrl": "https://www.youtube.com/watch?v=kJ26TwqroAk",
  "adCopy": {
    "headline": "Know your customers. Close more deals.",
    "longHeadline": "Everything your team needs to grow. Get started free with HubSpot Smart CRM.",
    "description": "Track every contact, call, and deal in one place so nothing slips through the cracks.",
    "callToAction": "Sign up",
    "source": "preview-js"
  },
  "detailsFetched": true,
  "regions": [{ "regionCode": "US", "lastShown": "2026-10-30" }, { "regionCode": "CA", "lastShown": "…" }],
  "variations": [ … ],
  "adUrl": "https://adstransparency.google.com/advertiser/AR10072600183532683265/creative/CR15036202842803142657?region=US"
}
```

Key fields:

- **format**: `TEXT`, `IMAGE` or `VIDEO`. This is the label Google itself shows on the ad's page. It was checked against the live site.
- **adCopy**: headline, description, display URL and call to action. `adCopy.source` says how the text was obtained:
  - `overlay`: decoded from Google's own preview data.
  - `overlay-extra-texts`: a best guess when Google gives text without labels, for example local/Maps ads.
  - `preview-js`: video and display ads.
  - `preview-shopping`: Shopping product title and merchant.
- **imageUrl**: the ad image. **Many text ads are published by Google only as an image.** For those, `renderedAsImage` is `true` and `adCopy` is `null`, because the text exists only inside the picture.
- **platforms**: which Google platforms the ad matched. Google's ad list does not include this, so it is filled only in two cases:
  - when you filter by platform (`platformsSource: "platform-filter"`)
  - from EU impression data when details are fetched
- **regions** (with details):
  - for every region: last shown date
  - for EU regions only: first shown date and impression ranges per platform. This is all Google publishes.
- **adUrl / advertiserUrl**: links to the Transparency Center.

Monitor mode writes `monitorEvent` items (`event`: `NEW_AD`, `AD_STOPPED`, `AD_RESUMED`, `NEW_REGION`).

Domain check writes `domainCheck` items. A real item from a test run on 30 Oct 2026:

```json
{
  "type": "domainCheck",
  "domain": "nike.com",
  "region": "US",
  "isAdvertising": true,
  "hasAdHistory": true,
  "googleEstimatedAdsMin": 8000,
  "googleEstimatedAdsMax": 9000,
  "latestAdLastShown": "2026-10-30T23:56:08.000Z",
  "advertisers": [
    { "advertiserId": "AR16735076323512287233", "advertiserName": "Nike, Inc." },
    { "advertiserId": "AR06365026152670560257", "advertiserName": "WPP MEDIA MANAGEMENT" },
    { "advertiserId": "AR18378488041124659201", "advertiserName": "Nike Retail BV" }
  ],
  "transparencyUrl": "https://adstransparency.google.com/?region=US&domain=nike.com",
  "status": "OK"
}
```

Monitor events carry the same ad fields plus `event`, `monitoredTarget` and `detectedAt`. The dataset has a separate table view for each item type.

#### Honest run status

Every run saves a `RUN_SUMMARY` record in its key-value store. For each target it compares **Google's own estimated ad count with the number of ads actually returned**. The run's status message calls out problems:

- `BLOCKED`: Google kept refusing requests after all retries, so no ads were collected. A run where every target is blocked **fails** instead of returning an empty "success".
- `PARTIAL_BLOCKED`: some ads were collected before Google blocked the run.
- `EMPTY_BUT_ADS_EXPECTED`: Google says ads exist but returned none.
- `FEWER_THAN_ESTIMATED`: Google's list ended early compared with its own estimate.
- `NO_ADS`: Google has no ads for this target and filters.

You are charged only for ads and checks that were actually returned. Blocked or failed lookups are never charged; only the $0.001 run start applies.

### Monitoring competitors

1. Set **Mode** to *Monitor for changes* and add the competitors.
2. Optional: add a **Slack incoming webhook URL** and/or a **Webhook URL**. The webhook gets a JSON POST with all events, so it works with Zapier, Make, n8n or your own endpoint.
3. Save the input as a **Task** and add a **Schedule**, for example daily.
4. The first run saves a baseline and reports nothing by default. Every later run reports only what changed.

What each event means:

- **NEW_AD**: a creative that was not seen in earlier runs.
- **AD_STOPPED**: Google's "last shown" date is older than the *stopped after* setting (default 3 days). An ad that disappears from the list is also flagged, but only when the list is known to cover it.
- **AD_RESUMED**: a stopped ad was shown again.
- **NEW_REGION**: the ad matched a region that was not seen before. Compare several regions, or turn on details, to use this.

Monitor state is kept in a named key-value store (`gads-transparency-monitor` by default) under your account. Use a different store name for each independent monitoring job.

**Email alerts**: in the Actor's or task's *Integrations* tab, add an email notification for successful runs. Or send the webhook to Zapier or Make and email from there.

### Limits you should know

- **Many text ads are image-only.** Google often publishes Search text ads only as a rendered picture, so `adCopy` is `null` and the text is only in `imageUrl`. In a 100-ad test of one large US advertiser on 30 Oct 2026, 98 ads were image-only text ads. Video and Shopping ads in our tests did include text.
- **Use residential proxy.** Google rate-limits the Transparency Center. The Actor rotates IPs and backs off automatically, but datacenter IPs are blocked much more often.
- **Name lookups** pick the best-matching advertiser account by default. Some brands have several accounts, for example a US entity and an EU entity. Raise *Advertisers per name search*, or use the exact `AR…` ID.
- **Domain searches** return every ad Google links to that domain. This can include resellers or partners that advertise the domain, so check `advertiserName`.
- **Impressions and first-shown dates per region** exist only for EU countries. Google does not publish them elsewhere.
- **Audience targeting** (`audienceSelectionRaw`) is returned as Google publishes it, without interpretation.
- Some ads Google labels as Video are local/Maps ads with no YouTube video. Those have `youtubeVideoId: null`.
- Results are sorted the way Google sorts them: most recently shown first.

### Pricing

Pay per event. You pay only for what the Actor does; platform usage (compute, proxy) is included in the price.

| Event | What triggers it | Price |
|---|---|---|
| Actor start | Once per run (charged automatically by Apify; once per GB of memory above 1 GB) | $0.001 |
| `ad-result` | Each ad returned in scrape mode | $0.0018 Free, $0.0015 Bronze, $0.0012 Silver, $0.0009 Gold and above (by Apify plan tier) |
| `ad-detail` | Each ad's details, when *Fetch ad details* is on | $0.003 |
| `advertiser-checked` | Each advertiser or domain checked in monitor mode | $0.003 |
| `new-ad-alert` | Each new ad found in monitor mode | $0.003 |
| `domain-check` | Each domain and region checked in domain-check mode | $0.003 |
| Stopped, resumed and new-region alerts | Monitor mode change events | Free |
| Blocked or failed lookups | Nothing returned | Free |

Example: scraping 100 ads on the Apify Free plan costs $0.001 + 100 × $0.0018 = $0.181. A daily monitor of one advertiser with no new ads costs $0.001 + $0.003 = $0.004 per run.

Set a maximum cost per run in the run options. The Actor stops cleanly when it reaches it.

### FAQ

**What happens if Google blocks the run or returns nothing?**
The Actor retries with new IPs and backs off. If every target stays blocked, the run **fails** with a clear message instead of reporting an empty success. If Google says ads exist but returns none, the target is marked `EMPTY_BUT_ADS_EXPECTED`. Details for every target are in `RUN_SUMMARY`.

**Am I charged for blocked targets or empty results?**
No. Ads, checks and alerts are charged only when a result is saved to your dataset. A blocked monitor target is not charged and its saved state is left unchanged. The $0.001 run start is the only fixed charge.

**Are my limits respected?**
Yes. *Max ads per target* and *Max ads in total* are hard caps, and the Actor stops cleanly at your maximum cost per run.

**Do I get the ad text, or just links?**
You get the text wherever Google publishes it: headline, long headline, description, call to action, and Shopping product titles. For Search text ads that Google publishes only as an image, you get the image URL (see Limits).

**Why does a domain search show advertisers I don't recognise?**
Google lists every advertiser whose ads point to that domain, including resellers, agencies and partners. Filter by `advertiserName` or `advertiserId`.

**Can I get impressions or spend?**
Google publishes impression ranges and first-shown dates per country only for EU regions. Turn on *Fetch ad details* and pick an EU region. Spend is not published.

**Will the monitor spam me with old ads?**
No. The first run only records a baseline. After that, an ad is reported as new only when it first appeared since the previous run, not when an old ad moves back into view.

### Is it legal?

The Actor reads only the public Google Ads Transparency Center. Google publishes this site so anyone can see who is advertising and what they show. No login is needed. It collects no personal data beyond what advertisers publish. Check your local laws and Google's terms for your use case.

# Actor input Schema

## `mode` (type: `string`):

<b>scrape</b>: list ads. <b>monitor</b>: compare with the previous run and report new ads, stopped ads and new regions (schedule it). <b>domainCheck</b>: quick yes/no "is this domain advertising on Google?" for a list of domains.

## `targets` (type: `array`):

One per line. Accepts an advertiser ID (AR...), a domain (nike.com), an advertiser name (looked up via Google's search suggestions), or any adstransparency.google.com URL (filters in the URL override the ones below).

## `regions` (type: `array`):

Countries where the ads were shown. "Anywhere" covers all regions. Selecting several runs one search per region and records which regions each ad matched.

## `platforms` (type: `array`):

Leave empty for all platforms. Selecting platforms runs one search per platform and labels each ad with the platforms it matched.

## `formats` (type: `array`):

Leave empty for all formats.

## `dateFrom` (type: `string`):

Only ads shown on or after this date (YYYY-MM-DD).

## `dateTo` (type: `string`):

Only ads shown on or before this date (YYYY-MM-DD).

## `maxResultsPerTarget` (type: `integer`):

Hard cap on ads per advertiser/domain. In monitor mode this is how many of the most recently shown ads are compared each run.

## `maxTotalResults` (type: `integer`):

Hard cap on ads across all targets. 0 = no overall cap.

## `fetchDetails` (type: `boolean`):

One extra request per ad: all variations, per-region first/last shown dates, and (EU regions only, as published by Google) impression ranges per platform. Charged as a separate ad-detail event.

## `fetchPreviewContent` (type: `boolean`):

Downloads the ad preview script when needed to extract YouTube video IDs, image URLs and headline/description text. Recommended.

## `maxAdvertisersPerName` (type: `integer`):

When a target is a name, how many matching advertiser accounts to include (best match first).

## `monitorStoreName` (type: `string`):

Named key-value store that remembers ads seen in previous runs. Use a different name per monitoring job if you run several.

## `stoppedAfterDays` (type: `integer`):

An ad counts as stopped when Google's "last shown" date is older than this.

## `alertOnFirstRun` (type: `boolean`):

Off: the first run only saves a baseline. On: every ad found is reported as NEW_AD.

## `webhookUrl` (type: `string`):

Optional. Receives a JSON POST with all change events at the end of each monitor run.

## `slackWebhookUrl` (type: `string`):

Optional. https://hooks.slack.com/... URL; posts a short summary with links to new ads.

## `notifyWhenNoChanges` (type: `boolean`):

Send the webhook/Slack message on every run, not only when there are changes.

## `activeWithinDays` (type: `integer`):

Domain check marks a domain as advertising when its latest ad was shown within this many days.

## `cookies` (type: `array`):

Optional Google cookies (Cookie-Editor JSON export). Not needed normally; the Actor gets fresh anonymous cookies itself.

## `reuseSessionCookie` (type: `boolean`):

Saves the anonymous Google session cookie (no login, no personal data) in a key-value store named gads-transparency-session in your account for up to 12 hours, so later runs skip the warm-up request. Turn off to start every run with a fresh session.

## `proxyConfiguration` (type: `object`):

Residential proxy strongly recommended: Google rate-limits datacenter IPs quickly.

## `maxRetries` (type: `integer`):

On a block the Actor switches to a new IP and backs off exponentially.

## `requestDelayMs` (type: `integer`):

Base delay between API requests (plus random jitter).

## Actor input object example

```json
{
  "mode": "scrape",
  "targets": [
    "hubspot.com"
  ],
  "regions": [
    "US"
  ],
  "platforms": [],
  "formats": [],
  "maxResultsPerTarget": 20,
  "maxTotalResults": 0,
  "fetchDetails": false,
  "fetchPreviewContent": true,
  "maxAdvertisersPerName": 1,
  "monitorStoreName": "gads-transparency-monitor",
  "stoppedAfterDays": 3,
  "alertOnFirstRun": false,
  "notifyWhenNoChanges": false,
  "activeWithinDays": 30,
  "reuseSessionCookie": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxRetries": 6,
  "requestDelayMs": 400
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "targets": [
        "hubspot.com"
    ],
    "maxResultsPerTarget": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("kingly_contest_7cb/google-ads-transparency-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "targets": ["hubspot.com"],
    "maxResultsPerTarget": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("kingly_contest_7cb/google-ads-transparency-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "targets": [
    "hubspot.com"
  ],
  "maxResultsPerTarget": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call kingly_contest_7cb/google-ads-transparency-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kingly_contest_7cb/google-ads-transparency-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/sdBWgbstj4CgvaFaF/builds/qNzBXohRckQnAOE0i/openapi.json
