# Google Ads Transparency Center Scraper: Ads, Text, EU Reach (`nat-labs/google-ads-transparency-scraper`) Actor

Every ad a brand runs on Google Search, YouTube, Shopping, Maps & Play. Search by brand, domain or advertiser ID. Ad copy (headline, description), YouTube IDs, EU impressions per country & platform, targeting, only-new-ads alerts. Hard spend limits, no login.

- **URL**: https://apify.com/nat-labs/google-ads-transparency-scraper.md
- **Developed by:** [Nat | Data Tools](https://apify.com/nat-labs) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 ads

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 Center Scraper: Ads, Text, EU Reach

See **every ad a company runs on Google** (Search, YouTube, Shopping, Maps and Play) as clean JSON, CSV or Excel. The data comes straight from Google's public [Ads Transparency Center](https://adstransparency.google.com/).

Search by **brand name, website domain, advertiser ID or a pasted Transparency Center link** and get:

- the ad's format, first and last shown dates, days served and image URL;
- with options switched on: the **ad copy** (headline, description, display URL), **YouTube video ID or direct MP4 link**, **impressions per country and per platform** (EU/EEA), targeting and every A/B variation.

No login, no cookies, no Google account.

**You never pay for a failed, duplicate or empty result, and limits are hard limits.** If you ask for 50 ads, you get and pay for at most 50 ads.

### Why this Google Ads scraper

| | This Actor | Typical Google Ads Transparency actors |
|---|---|---|
| Ad copy | **Headline, description and display URL of search text ads** (read with built-in OCR from Google's archive), copy of HTML/local/Shopping ads, **YouTube ID or MP4 URL** of video ads | "Only metadata": a preview link, no text |
| Impressions | **Per country and per platform** (Search, YouTube, Maps…) wherever Google publishes them (EU/EEA under the DSA), plus totals. `impressionsAvailable` tells you when Google publishes none | Missing, or one total |
| Limits and spend | Per-target and total caps checked **before** an ad is fetched; the run's maximum cost is a hard cap; duplicates are never charged | "Overcharged despite setting ad limits" |
| Search | Brand, domain, advertiser ID, links, **several countries or all of the EU in one go**, platform, format and date filters | One query and one country per run, or links only |
| Ad check | One cheap row per company: runs Google Ads? how many? still active? which accounts advertise this domain? **Rows without ads are free** | $12 per 1,000 checks, or not offered |
| Monitoring | **Only new ads** since the last run, per monitor name. Known ads are skipped free, and scheduled checks cost a few requests | Re-download everything |
| Failed runs | Fail fast with a clear reason, **not charged** | "Succeed" with 0 results |
| Price | **$1 per 1,000 ads**, $1.50 with full details, $2.50 with ad text. No start fee, platform usage included | $1.50–5 per 1,000 + start fees |
| AI agents | Lenient input: `"nike"`, `"nike.com"`, `"Germany"`, `"eu"`, `"last 30 days"`, `"video"` all work | Strict enums |
| Compatibility | Accepts the input field names of the popular Transparency Center actors (see *Migrating*) | — |

### What you can do

- **Competitor ad intelligence**: every ad a competitor runs right now, on which Google platforms, since when, with the actual copy.
- **Creative and messaging research**: build a swipe file of headlines, descriptions and videos; `shownForDays` and `numServedDays` show which ads keep running.
- **EU reach analysis**: impressions ranges per country and per platform for any advertiser active in the EU/EEA.
- **Lead generation / ad check**: give 1,000 domains and find out which companies advertise on Google, how much, and whether they are active this week.
- **Alerts**: schedule a daily run with `onlyNewAds` and get a Slack or email message when a competitor launches a new ad.
- **AI agents**: call it from Claude, ChatGPT or Cursor through Apify's AI integrations.
- **Your own stack**: run it from the Apify API, the Python or JavaScript client, Make, Zapier, n8n, Google Sheets, Slack, webhooks or a schedule; export JSON, CSV or Excel.

### Quick start

#### 1. All ads of a company in one country

```json
{ "queries": ["nike.com"], "region": ["US"], "maxAdsPerSearch": 200 }
```

`queries` accepts brand names (`Nike`), domains (`nike.com`), advertiser IDs (`AR16735076323512287233`) and Transparency Center links. A domain returns every advertiser account whose ads point at that website. For a brand name, the Actor checks the matching advertiser accounts and the brand's own domain, and picks the one with the most ads.

#### 2. Full intelligence: copy, impressions, targeting

```json
{
    "advertiserIds": ["AR16735076323512287233"],
    "region": ["EU"],
    "platforms": ["YOUTUBE", "SEARCH"],
    "datePreset": "last30",
    "includeDetails": true,
    "extractAdText": true,
    "maxAdsPerSearch": 100
}
```

#### 3. Video ads only, with video links

```json
{ "queries": ["hellofresh.com"], "adFormat": "video", "extractAdText": true }
```

→ `youtubeUrl` for YouTube video ads, and `videoUrl` (direct MP4) and `videoDurationSec` for in-stream video ads.

#### 4. Ad check for a list of companies

```json
{ "resultType": "advertisers", "domains": ["hellofresh.com", "gousto.co.uk", "mindfulchef.com"], "region": ["GB"] }
```

→ One row per domain with `hasAds`, `approxAdCountLow/High`, `latestAdLastShown`, `isActive`, the formats seen and the advertiser accounts behind it. Rows without ads cost nothing.

#### 5. Competitor monitoring (only new ads)

```json
{ "queries": ["competitor.com"], "region": ["US", "GB"], "onlyNewAds": true, "monitorStoreName": "competitor-watch", "extractAdText": true }
```

Schedule it daily and connect a Slack, email or webhook integration:

- The first run delivers everything (up to your limits) and becomes the baseline.
- Each later run delivers only ads first shown since the previous run.
- Ads that are already known are skipped for free.

#### Migrating from other Transparency Center actors

Paste your existing input. The following field names all work:

- `queries`, `domains`, `advertiserIds`, `startUrls`, `resultType` (`ads` / `advertisers`)
- `region`, `platforms`, `adFormat`, `startDate`, `endDate`
- `maxAdsPerSearch`, `maxAdvertisersPerQuery`, `includeDetails`, `onlyNewAds`, `monitorStoreName`
- the aliases `searchQuery`, `maxResults`, `resultsLimit`, `skipDetails`, `platform`, `format`, `dateFrom`, `dateTo`, `monitorStateName`, `fetchCreativeDetails`, `ocr`

Output fields keep the familiar names:

- `creativeId`, `advertiserId`, `advertiserName`, `format`, `firstShown`, `lastShown`, `shownForDays`
- `imageUrl`, `previewUrl`, `googleCreativeId`, `googleCustomerId`, `adGroupId`, `adUrl` / `adLibraryUrl`
- `variations`, `regionsShown`, `regionStats[].impressions.lowerBound/upperBound`, `surfaceServingStats`, `targeting`

### Output

#### Ad (`resultType: "ads"`, with `includeDetails` and `extractAdText`; example, shortened)

```json
{
    "type": "ad",
    "creativeId": "CR08027505669651300353",
    "advertiserId": "AR16735076323512287233",
    "advertiserName": "Nike, Inc.",
    "format": "TEXT",
    "firstShown": "2022-06-28T20:23:05.000Z",
    "lastShown": "2026-06-23T17:58:30.000Z",
    "shownForDays": 1456,
    "numServedDays": 1454,
    "lastShownDaysAgo": 115,
    "previewType": "image",
    "imageUrl": "https://tpc.googlesyndication.com/archive/simgad/11154944259192068658",
    "region": "DE",
    "adUrl": "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR08027505669651300353?region=DE",
    "headline": "Nike Official Site - Worry Free Shopping",
    "description": "Rock your game-ready look inspired by your favorite team. Explore new Nike fan gear now!",
    "displayUrl": "www.nike.com/nike/soccer",
    "adTextSource": "ocr",
    "youtubeUrl": null,
    "videoUrl": null,
    "variationCount": 3,
    "variations": [{ "type": "image", "imageUrl": "https://tpc.googlesyndication.com/archive/simgad/…", "width": 380, "height": 219 }],
    "regionStats": [
        {
            "regionCode": "DE",
            "regionName": "Germany",
            "firstShown": "2023-08-03",
            "lastShown": "2023-10-06",
            "impressions": { "lowerBound": 7000, "upperBound": 8000 },
            "surfaceServingStats": [
                { "surfaceCode": "SEARCH", "surfaceName": "Google Search", "impressions": { "lowerBound": 4000, "upperBound": 5000 } },
                { "surfaceCode": "YOUTUBE", "surfaceName": "YouTube", "impressions": { "lowerBound": 0, "upperBound": 1000 } }
            ]
        }
    ],
    "impressionsTotal": { "lowerBound": 50000, "upperBound": 60000 },
    "impressionsAvailable": true,
    "targeting": {
        "demographics": { "included": true, "excluded": true },
        "geography": { "included": true, "excluded": true },
        "contextual": { "included": true, "excluded": false },
        "customerLists": { "included": false, "excluded": true }
    },
    "dataStatus": { "details": "ok", "text": "ok" },
    "scrapedAt": "2026-10-17T10:21:16.720Z"
}
```

Values of `dataStatus.details` and `dataStatus.text`:

| Value | Meaning |
|---|---|
| `ok` | The data is there |
| `not-requested` | The option was off |
| `unavailable` | Nothing readable, e.g. a plain image banner. **Not charged** |
| `failed` | Fetching it failed. **Not charged** |
| `free-plan-limit` | The free-plan cap on ad text was reached |

#### Ad check row (`resultType: "advertisers"`)

```json
{
    "type": "domain-summary",
    "domain": "hellofresh.com",
    "region": "US",
    "hasAds": true,
    "approxAdCountLow": 800,
    "approxAdCountHigh": 900,
    "latestAdLastShown": "2026-10-17T08:59:36.000Z",
    "isActive": true,
    "formatsInSample": ["TEXT", "IMAGE", "VIDEO"],
    "advertisers": [
        { "advertiserId": "AR00930880903813529601", "name": "Grocery Delivery E-Services USA Inc.", "adsInSample": 96 },
        { "advertiserId": "AR17410177287600472065", "name": "HelloFresh SE", "adsInSample": 4 }
    ],
    "searchUrl": "https://adstransparency.google.com/?domain=hellofresh.com&region=US"
}
```

Advertiser IDs give `type: "advertiser-summary"` rows instead. They add `name`, `legalName`, `countryCode` and `verified`.

### Pricing

Pay per event. **Platform usage and proxies are included**: there is no start fee and no compute bill on top.

| Event | What it is | Price |
|---|---|---|
| Ad | One ad creative: format, dates, days served, media URL, IDs, links | **$1.00 / 1,000** |
| Ad details | Adds variations, all countries with dates, EU impressions per country and platform, targeting | $0.50 / 1,000 |
| Ad text | Adds headline, description, display URL, CTA, landing page, YouTube ID or MP4 URL. **Charged only when text or a video was found** | $1.00 / 1,000 |
| Advertiser check | One advertiser or domain summary row **that has ads** | $2.00 / 1,000 |

Lower rates apply automatically on higher Apify plans (ads down to $0.70 / 1,000).

**Never charged:**

- failed or blocked targets;
- duplicates (the same ad found through two searches);
- ads already delivered by earlier monitoring runs;
- details or text that failed or found nothing;
- ad-check rows without ads.

Examples:

| Job | Cost |
|---|---|
| 1,000 ads of a competitor | **$1.00** |
| The same 1,000 ads with copy, impressions and targeting | **$2.50** |
| Ad check for 1,000 domains where 300 advertise | **$0.60** |
| A daily monitor of 5 competitors that finds 10 new ads a day (with text) | about **$0.60 per month** |

The Actor checks your run's **maximum cost** before each ad and stops cleanly when it is reached, so you never pay more than you set.

**Free Apify plan:** up to 100 ads per run (25 of them with ad text) and 25 ad-check rows per run. This limit is set by the developer, not by Apify. Any paid plan removes it.

### Good to know

- **Impressions are published by Google only where the law requires it.** That means EU/EEA countries (Digital Services Act), and political ads in some regions. For US-only ads `impressionsAvailable` is `false`: Google publishes no numbers, it is not a scraping gap.
- **Platform filter** works only for ads shown since 4 September 2023. Google did not record platforms before that.
- **Search text ads are archived by Google as images**, so their copy is read with OCR. It is very accurate on these clean renders (~95 % line confidence). Occasional stray characters can appear in seller-rating lines, which go to `extensions`. Image banners are not OCR'd: `dataStatus.text = "unavailable"`, not charged. The OCR model is English. Other Latin-script languages read well, but accents can be lost ("enthält" → "enthalt"), and non-Latin scripts are not supported yet. HTML, local, Shopping and video ads are not affected, because their copy is read from the preview, not from an image.
- **Speed:** lists run at thousands of ads per minute. With `extractAdText`, OCR takes about 2–4 s per text ad at the default 1 GB memory. For large text jobs set the run's memory to 2–4 GB: it is faster and the price stays the same.
- **Brand names are ambiguous.** "Nike" matches dozens of accounts, including individuals called Nike. For exact results use the domain (`nike.com`) or the advertiser ID. `maxAdvertisersPerQuery` controls how many accounts a brand name may expand to.
- **Counts are Google's approximate ranges** (`approxAdCountLow/High`, `impressions.lowerBound/upperBound`), passed through unchanged.
- **Age-restricted ads** are hidden by Google from signed-out visitors, so they are not included.
- **Media links:**
  - `imageUrl`, `youtubeUrl` and `videoUrl` are public Google CDN links.
  - `previewUrl` is Google's preview script for HTML ads. Open the `adUrl` to see the ad rendered.
- **Topic:** `topicId` is Google's internal topic code, shown on the ad page as "Topic (labeled by Google)". Google does not publish a name table.

### Is it legal to scrape the Google Ads Transparency Center?

The Ads Transparency Center is a public archive that Google publishes so that anyone can see who advertises and how. Regulators require parts of it, for example the EU Digital Services Act's ad repository.

This Actor:

- reads the same public data a signed-out visitor sees;
- does not log in and uses no cookies or accounts;
- **never solves, bypasses or fakes CAPTCHAs or bot checks** (when Google asks for one, the request is retried later from a different address, and if that keeps happening the run stops);
- paces its requests;
- respects robots.txt (adstransparency.google.com publishes none).

Most data is about businesses. Some advertisers are individuals (sole traders), so treat advertiser names as potentially personal data under the GDPR, and keep only what you need. Check Google's terms for your own use case, and consult a lawyer for commercial redistribution.

### FAQ

**Which Google platforms are covered?** Search, YouTube, Shopping, Maps and Play. Filter with `platforms`, or leave it empty for all.

**Can I search by keyword, e.g. "running shoes"?** No. The Transparency Center itself is searchable only by advertiser or website, so no tool can do this honestly. Use domains or advertiser names.

**Where do I find an advertiser ID?** Open the advertiser on adstransparency.google.com: the URL contains `/advertiser/AR…`. You can also paste the whole URL into `queries` or `startUrls`.

**Does it download images and videos?** It returns direct links (`imageUrl`, `videoUrl`, `youtubeUrl`) rather than copies. That is faster and cheaper for you, and the links are permanent Google CDN URLs.

**How is this different from SerpApi?** SerpApi bills per search, at about $25–75 per 1,000 searches, and returns no ad copy. Here 1,000 ads cost $1–2.50 including copy and EU impressions.

**Can I use it from Claude, ChatGPT or Cursor?** Yes, through Apify's AI integrations. Inputs like `"nike.com"`, `"eu"`, `"video"` and `"last 30 days"` are understood.

**What happens when Google changes something?** Each record carries `dataStatus`, and failures are listed in the run's `STATISTICS` record. A run where Google answers nothing fails loudly instead of returning an empty "success". Open an issue in the Issues tab: it is answered within 24 hours.

# Actor input Schema

## `queries` (type: `array`):

What to look up. Each entry can be a brand name (Nike), a domain (nike.com), an advertiser ID (AR16735076323512287233) or a pasted adstransparency.google.com link. Brand names are matched to Google's advertiser accounts (see maxAdvertisersPerQuery).

## `domains` (type: `array`):

Every advertiser whose ads point at these websites, e.g. hellofresh.com. Same as putting a domain into queries.

## `advertiserIds` (type: `array`):

Google advertiser IDs (AR + digits), from an advertiser's Transparency Center URL. The most exact way to target one account.

## `startUrls` (type: `array`):

Links copied from adstransparency.google.com: advertiser pages, single ads (creative pages) or domain searches. Filters in the link (region, format, platform, dates) are kept for that link.

## `resultType` (type: `string`):

ads = every ad creative with its data. advertisers = a cheap ad check: one summary row per advertiser or domain with approximate ad count, latest ad date and the advertiser accounts behind a domain. Rows without ads are free.

## `region` (type: `array`):

Countries where the ads were shown: two-letter codes (US, DE, GB), names (Germany) or EU for all EU/EEA countries. Several regions are searched together. Empty or 'anywhere' = all regions. Impressions per country are published by Google for EU/EEA countries (DSA).

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

Only ads shown on these Google platforms. Empty = all. Google records platforms only for ads shown since 2023-09-04.

## `adFormat` (type: `string`):

Only text, image or video ads. Lowercase works too.

## `datePreset` (type: `string`):

Quick date filter on when the ad was shown. startDate/endDate override it.

## `startDate` (type: `string`):

YYYY-MM-DD. Only ads shown on or after this date. Relative values like '30 days' also work.

## `endDate` (type: `string`):

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

## `maxAdsPerSearch` (type: `integer`):

Hard cap per target (0 = all). The Actor never delivers or charges more than this, whatever Google returns.

## `maxItems` (type: `integer`):

Hard cap for the whole run across all targets (0 = no total cap; maxAdsPerSearch still applies). Together with the run's maximum cost this makes overspending impossible.

## `maxAdvertisersPerQuery` (type: `integer`):

A brand name can match several advertiser accounts (Nike, Inc.; NIKE SRL …). Exact name matches come first, then the accounts with the most ads.

## `includeDetails` (type: `boolean`):

One extra request per ad: all A/B variations, every country the ad ran in with first/last shown dates, impressions ranges per country and per platform (published for EU/EEA countries), audience targeting categories and Google's topic. Billed per ad as 'ad-details'.

## `extractAdText` (type: `boolean`):

Reads the ad copy: search text ads (archived by Google as images) are read with built-in OCR; HTML, local and video ads are parsed from their preview, which also gives the YouTube video ID and landing page. Billed per ad as 'ad-text' only when text was found.

## `onlyNewAds` (type: `boolean`):

For scheduled runs: deliver only ads not delivered by earlier runs with the same monitor name. The first run delivers everything and becomes the baseline. Known ads are skipped free of charge.

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

Separate memory per monitor, e.g. one per competitor or schedule.

## `maxConcurrency` (type: `integer`):

How many brands/domains/advertisers are processed at once.

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

Leave as is: Transparency Center requests go through Apify residential proxy (tiny, ~1 KB per ad) and ad media through Apify datacenter proxy, both included in the price. Set proxy groups or your own proxy URLs only if you want to override that.

## Actor input object example

```json
{
  "queries": [
    "nike.com"
  ],
  "resultType": "ads",
  "region": [
    "US"
  ],
  "adFormat": "ALL",
  "datePreset": "any",
  "maxAdsPerSearch": 100,
  "maxItems": 0,
  "maxAdvertisersPerQuery": 1,
  "includeDetails": false,
  "extractAdText": false,
  "onlyNewAds": false,
  "monitorStoreName": "default",
  "maxConcurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Ad creatives, or advertiser/domain summaries in ad-check mode.

## `statistics` (type: `string`):

Per-run counters, failed targets with reasons, proxy traffic.

# 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 = {
    "queries": [
        "nike.com"
    ],
    "region": [
        "US"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("nat-labs/google-ads-transparency-scraper").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 = {
    "queries": ["nike.com"],
    "region": ["US"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("nat-labs/google-ads-transparency-scraper").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 '{
  "queries": [
    "nike.com"
  ],
  "region": [
    "US"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call nat-labs/google-ads-transparency-scraper --silent --output-dataset

```

## MCP server setup

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

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/22hfrqCfRvxxfcE37/builds/JLR0fqtKgmckMyTp8/openapi.json
