# Google Ads Scraper — Ads Transparency Center (`corvuslab/google-ads-transparency-scraper`) Actor

Scrape the Google Ads Transparency Center by brand, advertiser website or advertiser ID. Get every creative variation, first/last shown dates, per-country impression reach by Google surface, verified advertiser identity, EU audience targeting and election-ad spend. 222 countries, no login.

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

## Pricing

from $0.85 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

### What does Google Ads Transparency Center Scraper do?

It turns the **Google Ads Transparency Center** into a structured ad dataset. Search by brand name (`Nike`), advertiser website (`nike.com`) or advertiser ID (`AR167350…`) — or paste Ads Transparency Center URLs — and get every matching ad back as **60 typed fields**: the ad's own copy, every creative variation, first/last shown dates, per-country reach by Google surface, the advertiser's verified legal identity, and for election ads **who paid and how much**.

No code and no login. Download as JSON, CSV or Excel, or stream straight into the API, an AI agent or an MCP client.

> New to Apify? Sign up free and use the included monthly platform credit to try this Actor — no credit card needed.

***

### What data can you extract from the Google Ads Transparency Center?

Every record carries up to **60 fields**. Groups marked with a toggle arrive when that input is on — all three are on by default, and every field is always present, `null` where the ad doesn't expose it.

| Group | What you get |
|---|---|
| **Ad & creative** | `id`, `creativeId`, `title`, `format` (text / image / video), `imageUrl`, `previewUrl`, `previewHtml`, `width`, `height`, `adUrl`, `advertiserUrl` |
| **Video ads** *(`includeVideoDetails`)* | `videoId`, `youtubeUrl`, `thumbnailUrl` — a watchable link and real thumbnail where a YouTube video backs the ad (~6 in 10). For some of the rest, `videoUrl` is the served MP4 (a signed URL that expires within days) |
| **Ad copy & destination** *(`includeAdCopy`)* | `headline`, `description`, `callToAction`, `displayUrl`, and `landingPageUrl` where the creative exposes it. Also on each `variants[]` entry. Plus `merchantName` on Shopping ads and `businessLocation` where the ad carries a location extension |
| **Every creative variation** *(`includeDetails`)* | `variantCount` plus `variants[]` — each version's image or hosted preview with its own dimensions, so you see the whole rotation |
| **Timing & placement** | `firstShown`, `lastShown`, `approxDaysShown` (Google's own days-active number), `regionCode`, `regionsShown[]`, `matchedDomain`, `topicId` |
| **Reach by country & surface** *(`includeDetails`)* | `countryStats[]` — per-country impression bounds, first/last shown and a `platformStats[]` split by Google surface — plus whole-ad `impressionsLowerBound` / `impressionsUpperBound` |
| **Advertiser & election transparency** *(`includeDetails`)* | `advertiserName`, `advertiserLegalName`, `isVerifiedAdvertiser`, `advertiserLocation`, `advertiserTaxId`, `advertiserTotalAdsLowerBound` / `UpperBound`, `isPoliticalAd`, `spendLowerBound` / `spendUpperBound`, and `audienceSelection[]` — the five targeting groups Google discloses, each with an `included` / `excluded` flag |
| **Google Ads join keys** | `googleCustomerId`, `googleCreativeId`, `googleAdGroupId` — Google's own ids for the creative, so a transparency row joins to the same creative in an advertiser's Google Ads reporting (80-100% of ads across live runs), plus `googleVersionId` where the preview declares one |
| **Run & change tracking** | `source`, `searchKeyword`, `scrapedAt`, `detailFetched`, `contentHash`, `changeType` (NEW / UPDATED / UNCHANGED / EXPIRED), `isRepost`, `repostOfId`, `repostDetectedAt` |

***

### Key features

- 🔎 **Search the way you think** — brand name, advertiser website or advertiser ID, auto-detected. Comma-separate several, or paste Ads Transparency Center URLs.
- 🌍 **222 countries and 5 Google surfaces, as multi-selects** — any set of markets, and Google Search, YouTube, Shopping, Maps or Play, in one run instead of one run per country.
- 🎚️ **Format and date filters** — text, image or video within a date range, applied at the source so you never pay for rows you filtered out.
- ✍️ **The words on the ad** — text ads come back as genuine text rather than a picture of text, per ad *and* per variation, so you can search, group and diff ad copy **without running OCR over it first**.
- 🎞️ **Every creative variation** — each version of an ad with its own image, preview and dimensions, not just the grid thumbnail — usually where the messaging tests are.
- 📊 **Per-country reach, by surface** — impression ranges per country, split by Google surface, so you see where attention is actually bought.
- 🏢 **Verified advertiser identity** — the legal name and location behind the brand, tying an ad to a real company.
- 🎬 **Video ads you can actually watch** — video ads carry no image in the ad library, only a preview script. This resolves the **video ID, watch URL and thumbnail** (~6 in 10; the rest stay null rather than guessing).
- 🔗 **Join keys into Google Ads** — every ad carries Google's own customer, creative and ad-group ids, so transparency data lines up with an advertiser's own campaign reporting instead of sitting in a separate silo.
- 📈 **How big is their ad library?** — every record carries the advertiser's *total* ad count, so you can size a rival's whole operation without paging thousands of rows.
- 🗳️ **Election ads and EU disclosures others skip** — one switch returns who paid (legal name and tax ID), spend and impression ranges, and all five targeting groups Google discloses, each flagged *included* or *excluded*.
- ♻️ **Incremental "new ads only" monitoring** — each scheduled run emits only what appeared or changed, so an ad simply still running is not re-emitted and not re-billed.
- 🔔 **Notifications** — Telegram, Slack, Discord or any webhook (n8n / Make / Zapier), so a competitor's new creative lands in your channel.
- 🔗 **Clay and Notion, natively** — paste a Clay webhook URL or a Notion integration token and every ad lands in your table as it is scraped. Notion columns are created for you, typed correctly, from an empty database.
- 🤖 **AI-ready** — compact mode and drop-empty output keep payloads small for LLMs and MCP tools.

***

### 🚀 How to scrape the Google Ads Transparency Center

1. Open the Actor and enter a **search** — a brand (`Nike`), an advertiser website (`nike.com`) or an advertiser ID (`AR16735076323512287233`) — or paste Ads Transparency Center URLs.
2. Pick your **countries**, **Google surfaces**, **ad format** and **date range**, and set **Max results**.
3. Leave **Fetch full ad details** on for variations, reach and advertiser identity — or off for the fastest run.
4. (Optional) turn on **incremental mode** and a **notification** channel, then open **Schedules**.
5. Click **Start** — results stream into the Output tab as **JSON, CSV or Excel**, or pull from the **API**.

#### Quick-start example input

```json
{
  "query": "nike.com",
  "regions": ["US", "GB"],
  "platforms": ["youtube", "google_search"],
  "adFormat": "video",
  "maxResults": 100
}
```

Up to 100 video ads nike.com ran on YouTube and Google Search in the US and UK, each with its creative variations, shown dates and the countries it reached.

***

### Input

Configure it in the visual editor or pass JSON via the API. Every field has help text in the editor; the ones worth knowing are below.

#### 🔎 Search

| Field | Type | What it does |
|---|---|---|
| `query` | string | Brand name, advertiser website or advertiser ID — auto-detected. Comma-separate several; results are merged and de-duplicated. |
| `startUrls` | array | Ads Transparency Center URLs — advertiser page, single ad, or a search URL. Region is read from the URL when present. |
| `maxResults` | integer | Max ads to return (default 50, `0` = unlimited). |
| `ignoreUrlFailures` | boolean | Skip URLs that can't be interpreted instead of failing the run. |

#### 🎚️ Filters

| Field | Type | What it does |
|---|---|---|
| `regions` | array | Only ads shown in these countries — **222 options**, multi-select. Empty = worldwide. |
| `platforms` | array | Only these Google surfaces: Search, YouTube, Shopping, Maps, Play. Empty = all. |
| `adFormat` | enum | `any`, `text`, `image` or `video`. |
| `dateFrom` / `dateTo` | string | Shown-from / shown-until bounds, `YYYY-MM-DD`. |
| `politicalAdsOnly` | boolean | Switch to the election-ads surface: who paid, plus spend and impression ranges. Works with no query at all. |

#### 📄 Output & AI modes

- `includeDetails` — variations, per-country impressions, audience selection, advertiser identity (on by default).
- `includeAdCopy` — **headline, description, call-to-action, display URL and landing page** (on by default), as real text.
- `includeVideoDetails` — YouTube ID, watch URL and thumbnail for video ads.
- `compact` / `excludeEmptyFields` — core fields only, and drop nulls: lean AI/MCP payloads.

#### ♻️ Incremental monitoring

- `incrementalMode` — tag every record `NEW` / `UPDATED` / `UNCHANGED` / `EXPIRED`.
- `stateKey` — stable name for the tracked search; derived from your settings if blank.
- `emitUnchanged` / `emitExpired` — also emit unchanged ads, or ads that have since disappeared.

#### 🔔 Notifications

Telegram, Slack, Discord or any webhook — [setup below](#-how-to-set-up-google-ads-transparency-center-alerts). `webhookHeaders` adds your own headers to the generic webhook, and `notificationLimit` caps how many records go into each message.

#### ⚙️ Advanced

- `proxyConfiguration` — standard Apify Proxy, **on by default, leave it on**. Nothing to configure, no premium add-on to buy. The source limits how fast one IP can read ad details: the same 300-ad job returns 100% of ads fully detailed in ~2 min with proxy on, versus 73% in ~8.5 min without.
- `rotateProxyIps` — a fresh proxy IP per request rather than one for the whole run (on by default). This is what lets large runs finish.
- `maxRequestRetries` / `detailConcurrency` — retry budget, and detail requests in parallel.

#### More example inputs

```json
{
  "startUrls": ["https://adstransparency.google.com/advertiser/AR16735076323512287233?region=US"],
  "includeDetails": true,
  "maxResults": 0
}
```

```json
{
  "query": "nike.com, adidas.com",
  "regions": ["DE"],
  "incrementalMode": true,
  "politicalAdsOnly": false,
  "slackWebhookUrl": "https://hooks.slack.com/services/XXX/YYY/ZZZ"
}
```

***

### Output

Each dataset item is one ad. **Fetch full ad details** (on by default) adds `variants[]`, `countryStats[]`, `platformStats[]`, `audienceSelection[]` and advertiser identity. A real record, abridged:

```json
{
  "id": "CR13810034716822208513",
  "creativeId": "CR13810034716822208513",
  "advertiserId": "AR16735076323512287233",
  "advertiserName": "Nike, Inc.",
  "advertiserLegalName": "Nike, Inc.",
  "advertiserLocation": "US",
  "isVerifiedAdvertiser": true,
  "title": "Text ad by Nike, Inc.",
  "format": "text",
  "imageUrl": "https://tpc.googlesyndication.com/archive/simgad/18098505553023330831",
  "width": 348,
  "height": 425,
  "firstShown": "2023-11-17T00:11:26+00:00",
  "lastShown": "2026-09-12T06:52:55+00:00",
  "approxDaysShown": 896,
  "matchedDomain": "nike.com",
  "regionCode": "US",
  "regionsShown": ["US"],
  "variantCount": 5,
  "variants": [
    { "imageUrl": "https://tpc.googlesyndication.com/archive/simgad/18098505553023330831", "width": 348, "height": 425 },
    { "imageUrl": "https://tpc.googlesyndication.com/archive/simgad/11076221599670432433", "width": 348, "height": 436 }
  ],
  "countryStats": [
    { "regionCode": "US", "regionId": 2840, "impressionsLowerBound": null, "impressionsUpperBound": null, "firstShown": null, "lastShown": "2026-09-01", "platformStats": [] }
  ],
  "isPoliticalAd": false,
  "adUrl": "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR13810034716822208513?region=US",
  "advertiserUrl": "https://adstransparency.google.com/advertiser/AR16735076323512287233?region=US",
  "source": "adstransparency.google.com",
  "searchKeyword": "nike.com",
  "scrapedAt": "2026-09-12T07:25:27.894320+00:00",
  "detailFetched": true,
  "contentHash": "7fe1b0d45d0ff40e474ce1749899f99db4a40919"
}
```

On the election-ads surface the record also carries `spendLowerBound` / `spendUpperBound`, `impressionsLowerBound` / `impressionsUpperBound` and `advertiserTaxId`. In incremental mode it carries `changeType`.

**Ad copy and destination**, added by `includeAdCopy` — four real ads, one per creative type:

```json
[
  { "format": "text",  "headline": "Compra lo mejor y más nuevo",
    "description": "Descubre productos en Nike.com y compra en la página oficial de Nike.",
    "displayUrl": "nike.com",
    "businessLocation": "Nike Factory Store Malaga, Calle Antón García Abril, 6, Málaga, 29004, ES" },

  { "format": "image", "landingPageUrl": "https://www.nike.com/au/t/flex-train-mens-workout-shoes-VAQnVLTN/HV9972-303" },

  { "format": "image", "merchantName": "Nike Officiel",
    "headline": "Maillot de foot Replica Nike Dri-FIT Paris Saint-Germain 2026/27 Stadium Extérieur pour homme - Blanc - Taille: M" },

  { "format": "video", "callToAction": "INSTALL", "displayUrl": "itunes.apple.com",
    "landingPageUrl": "https://itunes.apple.com/app/id1094591345?mt=8",
    "videoUrl": "https://rr1---sn-uqj-j2ik.googlevideo.com/videoplayback?expire=1789490865&..." }
]
```

Every copy field is also on each `variants[]` entry, so you can tell which version says what.

Measured on live runs of 70 Nike ads: **80–88% of text ads** return headline + description + display URL, and **93% of image ads** return something readable (70% product title + merchant, 23% a landing page). Anything a creative doesn't expose comes back `null`, never guessed.

The Output tab ships three ready-made views: **Overview**, **Creatives** and **Reach & spend**.

***

### ♻️ How to monitor the Google Ads Transparency Center with incremental mode

Schedule the Actor and turn on **incremental mode**: each run compares against the last and emits only **NEW / UPDATED / EXPIRED** ads. A one-shot scrape becomes a competitor-ad watch feed — you learn the day a rival launches a creative, and stop paying for the hundred they've run all year.

"Changed" means the **creative content** changed. A still-running ad gains a day and a newer last-shown date every day; those ticks are ignored, so an unchanged campaign is never re-emitted and never re-billed. After the baseline your cost scales with how fast the advertiser rotates creative, not with how often you watch:

| Daily churn of 1,000 tracked ads | billable records | you save |
|---|---|---|
| 5 % | 50 | **95 %** |
| 15 % | 150 | **85 %** |
| 30 % | 300 | **70 %** |

#### Setting up scheduled monitoring

1. Configure your search, countries and surfaces, and turn on **Incremental mode**.
2. Run it once — this seeds the baseline.
3. Open the **Schedules** tab and set a recurring run (e.g. daily).
4. Add a notification channel so new creatives reach you without opening Apify.

***

### 🔔 How to set up Google Ads Transparency Center alerts

Get pinged the moment a tracked advertiser publishes a new ad. Fill in only the channels you want:

| Channel | What to configure |
|---|---|
| ✈️ **Telegram** | `telegramToken` (from @BotFather) + `telegramChatId` |
| 💬 **Slack** | `slackWebhookUrl` (Incoming Webhook URL) |
| 🎮 **Discord** | `discordWebhookUrl` (channel webhook URL) |
| 🪝 **Webhook** | `webhookUrl` receives structured JSON — ideal for n8n / Make / Zapier |

Each message carries the ad's title, advertiser, change type and a direct link to the ad. Channels fire independently — a broken one can't stop the scrape or the others. Pair with **incremental mode** to hear only about genuinely new creatives.

***

### 🔗 Send Google ads straight into Clay or Notion

This Actor writes into your table itself, **row by row while the run is still going** — no export step, no Zap. Both destinations work at once, both are optional, and neither can fail your run: a rejected token or an unreachable table is logged, the dataset is delivered in full, and the other destination keeps going.

| Field | Type | What it does |
|---|---|---|
| `clayWebhookUrl` | string | A Clay table's webhook URL. One ad = one row. |
| `clayAuthToken` | string | Optional — only if the Clay webhook source has an auth token. |
| `notionApiKey` | string | A Notion internal-integration secret (`ntn_…`). |
| `notionDatabaseId` | string | The target database's URL or 32-character ID. One ad = one page. |
| `notionCreateProperties` | boolean | Create missing columns with the right Notion type (on by default), so an empty database builds itself. |

#### 🧱 Clay — one ad per row

Every ad arrives as its own row, so Clay's columns map straight onto the ad fields. Nested fields (`regionsShown`, `countryStats`, `variants`) arrive as JSON text a Clay formula column can parse.

**Prefer to pull?** Clay's native Apify integration can run this Actor from a Clay column — one domain per row in, ads out. Clay documents that setup here: [Apify integration overview](https://university.clay.com/docs/apify-integration-overview).

#### 📓 Notion — one ad per page

The database can be **completely empty**: on the first run the Actor creates whatever columns are missing, each correctly typed — URL, date, number, select, multi-select, checkbox — and the creative becomes the page cover, so gallery view reads as a visual swipe file. Set `notionCreateProperties` to false to write only into columns you made yourself.

Do connect the database to your integration (**⋯ → Connections**), or Notion answers `object_not_found`. Notion's own guide covers creating the integration and granting it access: [Create integrations with the Notion API](https://www.notion.com/help/create-integrations-with-the-notion-api).

***

### 💡 What can you do with Google Ads Transparency Center data?

#### Reverse-engineer a competitor's ad strategy — and build a swipe file

Pull every creative a rival is running, with each variation and how long it has been live. `approxDaysShown` is the cheapest signal for what converts — the creatives they keep paying to run are the ones that work — and the same run doubles as an image-and-copy swipe file, filtered to video, image or text.

#### Map where a brand actually advertises

See which of the 222 markets an advertiser is live in, split by Google surface. Find a rival's expansion markets before the press release does.

#### Research election and issue advertising

Who paid for each political ad — legal name and tax ID — with spend and impression ranges per country: an auditable, exportable record.

#### Verify advertisers and investigate ad fraud

Tie a brand-name ad to the entity Google actually verified, so you can catch look-alike accounts running on your trademark.

#### Feed AI agents and LLM pipelines

Compact JSON straight into an LLM context, an MCP tool or a vector store — cluster creative themes, track rotation, score how a rival's mix shifts.

***

### 💰 How much does it cost to scrape the Google Ads Transparency Center?

This Actor uses Apify's **pay-per-event** model: a small fee when a run starts, plus a per-record fee. See the **Pricing** tab for current numbers. Three things cut your bill further:

- **Source-side filters** — country, surface, format and date filters are applied before results are returned, so ads you filtered out are never fetched and never billed.
- **Details off** — when you only need the creative, dates and advertiser, each run does a fraction of the work.
- **Incremental mode** — after the baseline you pay only for creatives that actually changed.

***

### 🔌 Integrations & export

Export to **JSON, CSV, Excel** or an HTML table, or pull from the **REST API** and the **JavaScript / Python** clients. Runs on a **schedule**, connects to **Google Sheets, Slack, Make, Zapier and n8n**, and works as an **MCP tool** for AI agents.

**Built in, no middleman:** [**Clay**](#-clay--one-ad-per-row) and [**Notion**](#-notion--one-ad-per-page) are written to directly, row by row, while the run is still going.

***

### 🔗 Using the API

Run this Actor from your own code. Example with the Apify Python client:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")

run_input = {
    "query": "nike.com",
    "regions": ["US"],
    "maxResults": 50,
    "includeDetails": True,
}

run = client.actor("corvuslab/google-ads-transparency-scraper").call(run_input=run_input)

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["advertiserName"], item["format"], item["adUrl"])
```

It also works with the JavaScript/TypeScript client, the Apify CLI and the REST API.

***

### ❓ FAQ

**Do I need a Google account, a login or my own cookies?** No — the Ads Transparency Center is public and this Actor reads only that public data. The standard Apify Proxy is on by default and needs no setup; leave it on, because the source limits how fast one IP can read ad details.

**Can I search by brand name, or do I need an advertiser ID?** All three work and the Actor detects which you gave it: a brand name (`Nike`) resolves to the matching verified advertisers, a website (`nike.com`) matches ads pointing at that domain, and an advertiser ID (`AR…`) goes straight there. Advertiser, single-ad and search URLs work too.

**Why is there no ad copy on some ads?** Ad copy comes from the creative itself, and not every creative exposes all of it. Anything it doesn't expose is `null`, never guessed.

**Why are `impressionsLowerBound`, `spendLowerBound` and `audienceSelection` empty on most ads?** Google discloses them only where a regulator requires it — EU/DSA disclosures and election ads. On an ordinary commercial ad those fields are legitimately `null`. Turn on **Election ads only** and the spend, impression and funder fields fill in.

**How do I get spend data and who paid for an ad?** Switch on `politicalAdsOnly` to read the election-ads surface: funder's legal name, tax ID where published, and spend plus impression ranges. Works with no search query — just pick countries.

**Is `approxDaysShown` just the gap between first and last shown?** No — it's Google's own days-active figure, which can differ because an ad can pause and resume. Shipped as reported, alongside raw `firstShown` / `lastShown`.

**Why does one ad have five `variants` but the grid shows one image?** The Transparency Center stores every version of a creative that ran under the same ad. `variants[]` returns all of them — usually where the messaging tests are.

**Will incremental mode re-bill me for ads that are simply still running?** No. Change detection hashes the creative, so a still-running ad counts as UNCHANGED and isn't emitted.

**Is it good for AI agents?** Yes — compact mode is MCP-friendly and drops empty fields, so an agent can reason over hundreds of ads in a small context. Export is JSON, CSV, Excel, HTML or straight from the API.

***

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

This Actor collects only **publicly available** data — the ad library Google publishes for exactly this purpose, with no login behind it. You're responsible for how you use it, in particular personal information such as the funder details on election ads, and for complying with the site's terms and applicable law (including GDPR/LGPD). Not affiliated with, endorsed by or sponsored by Google LLC.

***

**Keywords:** google ads transparency center scraper · google ads transparency api · adstransparency.google.com scraper · google ads scraper · ad library scraper · scrape competitor ads · advertiser ad search · ad creatives export · per-country ad reach · election ads transparency · political ad spend data · youtube ads scraper · google shopping ads data · google ads creative id · ad monitoring · competitor ad alerts · export to CSV · export to Excel · no-code scraper · MCP tool for AI agents

# Actor input Schema

## `query` (type: `string`):

Brand name, advertiser website or advertiser ID. Separate multiple searches with commas — each runs on its own and results are merged and de-duplicated. Leave empty and the run falls back to a sample search so you always get rows.

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

Paste Ads Transparency Center URLs directly — an advertiser page, a single ad page, or a search URL. Region is read from the URL when present.

## `maxResults` (type: `integer`):

Maximum number of ads to return. Set 0 for unlimited (bounded by how many the search has).

## `ignoreUrlFailures` (type: `boolean`):

Skip URLs that cannot be interpreted instead of failing the whole run.

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

Only return ads shown in these countries. Leave empty to search worldwide.

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

Only return ads that ran on these Google surfaces. Leave empty for all of them.

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

Only return ads of this creative type.

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

Only return ads shown on or after this date (YYYY-MM-DD). Leave empty for no lower bound.

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

Only return ads shown on or before this date (YYYY-MM-DD). Leave empty for no upper bound.

## `politicalAdsOnly` (type: `boolean`):

Switch to the election-ads surface, where Google discloses who paid for the ad plus its spend and impression ranges. Works with no search query — it browses every election ad in the selected countries.

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

Fetch each ad's detail record for all variations, per-country impressions, audience selection and verified advertiser identity. Turn off for the fastest, cheapest runs.

## `includeVideoDetails` (type: `boolean`):

Video ads have no image - only a Google-hosted preview script. Turn this on to also return the ad's YouTube video ID, a watch URL and a thumbnail image, so video creatives are viewable straight from the export. Costs one extra request per video ad.

## `includeAdCopy` (type: `boolean`):

Extract what the ad actually says and where it points: headline, description, call-to-action, display URL, landing page, and the product title + merchant on Shopping ads. Text-ad copy comes back as real text - no OCR, nothing to clean up. Adds one lightweight request per ad on a separate rate budget, so it does not slow the main scrape. Turn off for the fastest possible run.

## `includeAdvertiserTotals` (type: `boolean`):

Add how many ads each advertiser is running in total (advertiserTotalAdsLowerBound / UpperBound) - the whole-library count, not just the ads this run returned. One cheap extra request per advertiser.

## `compact` (type: `boolean`):

Emit only the core ad fields. Ideal for AI agents and MCP clients.

## `excludeEmptyFields` (type: `boolean`):

Remove null, empty-string and empty-array fields from each record.

## `incrementalMode` (type: `boolean`):

Track state between runs and tag every record with a changeType (NEW / UPDATED / UNCHANGED / EXPIRED).

## `stateKey` (type: `string`):

Stable name for the tracked search. Leave empty to derive one automatically from your search settings.

## `emitUnchanged` (type: `boolean`):

Also emit records that have not changed since the previous run.

## `emitExpired` (type: `boolean`):

Emit records for items present last run but gone now.

## `telegramToken` (type: `string`):

Bot token from @BotFather.

## `telegramChatId` (type: `string`):

Chat or channel ID, e.g. "-100123456789" or "@yourchannel".

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

Slack incoming-webhook URL.

## `discordWebhookUrl` (type: `string`):

Discord incoming-webhook URL.

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

Any HTTPS endpoint. Receives a JSON POST with the matched records — works with n8n, Make and Zapier.

## `webhookHeaders` (type: `object`):

Extra headers for the webhook request, e.g. {"Authorization": "Bearer xyz"}.

## `notificationLimit` (type: `integer`):

How many records to include in each notification message.

## `clayWebhookUrl` (type: `string`):

Paste the webhook URL of a Clay table. In Clay: open the table → Actions → Import → Import data from webhook (or + Add → Webhooks → Monitor webhook), then copy the generated URL. Each ad arrives as its own row. Nested columns (regionsShown, countryStats, variants) arrive as JSON text so a Clay formula column can parse them.

## `clayAuthToken` (type: `string`):

Optional. Only needed if you enabled an authentication token on the Clay webhook source. Sent as the 'x-clay-webhook-auth' header.

## `notionApiKey` (type: `string`):

Optional. Create an internal integration at notion.so/my-integrations, copy its Internal Integration Secret (starts with 'ntn\_'), then open the target database → ⋯ → Connections → and connect that integration. Without that last step Notion returns 'object\_not\_found'.

## `notionDatabaseId` (type: `string`):

Optional. Paste the database's URL or its 32-character ID. Every ad becomes one page in that database. The database can start completely empty — the columns are created for you on the first run.

## `notionCreateProperties` (type: `boolean`):

Add any columns the target database is missing (with the right Notion type) on the first run. Turn this off to write only into columns you created yourself — fields with no matching column are then skipped.

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

Apify Proxy is ON by default and should stay on. The source limits how fast a single IP can read ad details, so running direct returns fewer fully-detailed ads and takes far longer. Measured on the same 300-ad job: proxy on = 100% of ads fully detailed in ~2 minutes; direct = 73% in ~8.5 minutes. If proxy is unavailable the Actor falls back to a direct connection automatically.

## `rotateProxyIps` (type: `boolean`):

Only applies when a proxy is enabled above. The source limits how fast a single IP may ask for ad details, so one IP for the whole run is what gets throttled. Rotating spreads requests across the proxy pool and can substantially increase how many ads come back fully detailed on large runs.

## `maxRequestRetries` (type: `integer`):

How many times to retry a failed request before giving up on it.

## `detailConcurrency` (type: `integer`):

How many ad detail requests to run in parallel. Lower it if the source starts refusing requests.

## Actor input object example

```json
{
  "query": "nike.com",
  "maxResults": 50,
  "ignoreUrlFailures": true,
  "adFormat": "any",
  "politicalAdsOnly": false,
  "includeDetails": true,
  "includeVideoDetails": true,
  "includeAdCopy": true,
  "includeAdvertiserTotals": true,
  "compact": false,
  "excludeEmptyFields": false,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "notificationLimit": 5,
  "notionCreateProperties": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "rotateProxyIps": true,
  "maxRequestRetries": 3,
  "detailConcurrency": 5
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `allItems` (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 = {
    "query": "nike.com",
    "dateFrom": "",
    "dateTo": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("corvuslab/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 = {
    "query": "nike.com",
    "dateFrom": "",
    "dateTo": "",
}

# Run the Actor and wait for it to finish
run = client.actor("corvuslab/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 '{
  "query": "nike.com",
  "dateFrom": "",
  "dateTo": ""
}' |
apify call corvuslab/google-ads-transparency-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,corvuslab/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/YGexScL3djmAwAAs1/builds/sUE9FaWByPlT0YAdr/openapi.json
