# Google Ads Transparency Scraper - Competitor Ad Creatives (`scrapersdelight/adstransparency-google-creatives-scraper`) Actor

Scrape every ad creative an advertiser runs on Google Search, YouTube, Shopping, Maps and Play. Search by domain, advertiser ID or brand name. Each ad ships with its format, first/last shown dates, days shown, every country it ran in, impression ranges and the creative image. No API key.

- **URL**: https://apify.com/scrapersdelight/adstransparency-google-creatives-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Marketing, SEO tools, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 ad creative scrapeds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## 🔎 Google Ads Transparency Scraper — every ad creative a competitor is running

Pull an advertiser's **entire live and archived ad library** out of the
[Google Ads Transparency Center](https://adstransparency.google.com) — the ads they run on
**Google Search, YouTube, Shopping, Maps and Google Play** — as clean, structured rows.

Give it a **domain** (`nike.com`), a **Google advertiser ID** (`AR16735076323512287233`) or just a
**brand name** (`Progressive Insurance`). You get one row per creative, with the dates it ran, how
many days it ran, **every country it ran in**, the impression range, the platform split and the ad
asset itself.

***

### 🎯 What does this Actor do?

Google publishes an ad-transparency record for every advertiser it has verified. It is the single
best free source of competitor ad intelligence — and it is close to unusable by hand: no export, no
bulk view, one creative at a time.

This Actor turns it into a dataset.

- **🏷️ By domain** — `nike.com` returns every verified advertiser entity pointing at that site.
- **🆔 By advertiser ID** — exact, when one domain is shared by several legal entities.
- **🔤 By brand name** — resolved to real advertiser IDs through Google's own suggestion endpoint.
- **🌍 Country filter, server-side** — `US`, `GB`, `DE`… so a narrow scrape is genuinely cheaper.
- **🎬 Format filter** — text/search ads, image/display ads, or video (YouTube) ads.
- **📅 Ran-when** — first shown, last shown, and Google's own days-shown counter.
- **📈 Reach** — impression range, plus the Google Search / Shopping / YouTube / Maps / Play split.
- **🖼️ The creative itself** — the archived image (for text ads this is a *rendered screenshot of
  the ad*, headline, description and sitelinks included), plus every rendition Google kept.
- **✍️ Ad copy** — headline, description, display URL and sitelinks, decoded out of the live preview
  where Google still serves one.

One site, one job: **ad creatives**. No fluff, no half-scraped side products.

***

### 📊 What you actually get — measured, not promised

Field-fill measured on **932 unique creatives** pulled live on **2026-09-02** across five different
targets (nike.com, hubspot.com, progressive.com, one advertiser ID, and a US-filtered booking.com).
These are the real numbers, including the fields that are often empty:

| Field | Fill | What it is |
|---|---|---|
| `creativeId`, `advertiserId`, `advertiserName` | **100%** | Identity of the ad and who runs it |
| `creativeUrl` | **100%** | The ad's own page on adstransparency.google.com |
| `format` / `formatId` | **100%** | `TEXT`, `IMAGE` or `VIDEO` |
| `firstShownAt`, `lastShownAt` | **100%** | ISO 8601 timestamps |
| `daysShown` | **100%** | Google's run-days counter |
| `countries`, `countryCount`, `regionStats` | **100%** | Every country the ad ran in, with per-country first/last shown dates |
| `advertiserDomain` | 78.5% | Set on domain searches and wherever Google returns it |
| `creativeImageUrl` (+ `creativeWidth`/`Height`) | 79.5% | The archived creative image |
| `previewUrl`, `previewUrls` | 27.9% | Google's live rendering of the creative |
| `platforms` | 19.6% | Search / Shopping / YouTube / Maps / Play split |
| `impressionsMax` / `impressionsMin` | 19.5% / 13.8% | Google's banded impression range |
| `adCategoryId` / `adCategory` | 29.0% / 3.5% | Google's own category id, and its label where published |
| `adHeadline`, `adDescription`, `adDisplayUrl` | 2.9% | Ad copy, where a live preview still exists |
| `adExtensions` | 0.5% | Sitelinks / callouts on search ads |

In that sample: **97 distinct countries**, format mix **871 TEXT / 43 IMAGE / 18 VIDEO**, and
platform hits of Search 182, Shopping 73, YouTube 59, Maps 32.

**Read the low numbers honestly.** `adHeadline` is 2.9% because Google only keeps a live, decodable
preview for a small slice of ads; for the other 97% the ad's wording is baked into
`creativeImageUrl` as a rendered picture of the ad. Impression ranges and the platform split are
published by Google only for ads above a reach threshold and mostly in the EU — they are a bonus on
the ads that have them, not a promise on every row. `regionStats`, which is the field most
competitor-research work actually turns on, is **100%**.

#### Example row (trimmed)

```json
{
  "creativeId": "CR01843149584029712385",
  "advertiserId": "AR06365026152670560257",
  "advertiserName": "WPP MEDIA MANAGEMENT",
  "advertiserDomain": "nike.com",
  "format": "TEXT",
  "firstShownAt": "2023-10-14T04:51:05.000Z",
  "lastShownAt": "2026-09-02T04:14:45.000Z",
  "daysShown": 992,
  "creativeUrl": "https://adstransparency.google.com/advertiser/AR06365026152670560257/creative/CR01843149584029712385",
  "creativeImageUrl": "https://tpc.googlesyndication.com/archive/simgad/2824011479213509032",
  "creativeWidth": 380,
  "creativeHeight": 341,
  "variantCount": 3,
  "countries": ["BE", "CA", "US", "GT", "PE", "ES", "CL", "IE", "AR", "IT", "DE", "NL", "MX", "PT", "CO", "AT", "PL", "FR"],
  "countryCount": 18,
  "impressionsMin": 4000,
  "impressionsMax": 5000,
  "platforms": ["YouTube", "Google Shopping", "Google Search", "Google Maps"],
  "regionStats": [
    {
      "regionCode": 2056,
      "country": "BE",
      "countryName": "Belgium",
      "firstShownDate": "2023-10-14",
      "lastShownDate": "2026-08-16",
      "impressionsMin": null,
      "impressionsMax": 1000,
      "platforms": [
        { "platform": "Google Search", "platformId": 3, "impressionsMin": null, "impressionsMax": 1000 }
      ]
    }
  ]
}
```

***

### 🚀 Input

```json
{
  "domains": ["nike.com"],
  "countries": ["US"],
  "format": "any",
  "includeDetails": true,
  "maxItems": 200
}
```

| Input | Default | Notes |
|---|---|---|
| `domains` | `[]` | Advertiser websites. `https://`, `www.` and paths are stripped for you. |
| `advertiserIds` | `[]` | `AR…` ids. All of them are queried in one request. |
| `advertiserNames` | `[]` | Brand names, resolved to advertiser ids via Google's suggestion endpoint. |
| `countries` | `[]` | ISO 3166-1 alpha-2. **Server-side** filter. |
| `format` | `any` | `TEXT`, `IMAGE` or `VIDEO`. Google accepts only one at a time. |
| `includeDetails` | `true` | The enrichment call. Off is ~2x faster and much thinner. |
| `maxItems` | `200` | Total cap. `0` = unlimited. |
| `maxItemsPerTarget` | `0` | Stops one giant advertiser eating the whole run. |
| `maxAdvertisersPerName` | `3` | How far each brand name expands. |
| `detailConcurrency` | `6` | Parallel detail requests. |
| `residentialFallback` | `true` | Auto-escalate when Google rate-limits datacenter IPs. |
| `proxyConfiguration` | Apify Proxy on | Leave it on — see below. |

If you send an empty input, the Actor runs the documented `nike.com` demo instead of failing.

***

### 💰 Pricing

**Pay per event — $0.001 per ad creative delivered.** One event, no start fee, no per-page charge.

| | This Actor | Typical alternative |
|---|---|---|
| Per ad | **$0.001** | $0.0015 – $0.002 |
| Per ad **with** country/impression/platform detail | **$0.001** (included) | $0.002 + $0.003 = $0.005 |

1,000 creatives, fully enriched, cost **$1.00**.

Rows are charged as they are pushed (`Actor.pushData(items, 'creative-scraped')`), so **delivered
always equals billed** — if you set a spend cap, the run stops at it instead of handing you rows you
already paid for or rows you did not.

***

### 🌐 Proxies and rate limits — the honest version

Google rate-limits this endpoint **per IP**, and Apify's datacenter pool is shared across every
user on the platform. Measured on 2026-09-02, within the same minute and with an identical request:

| Exit | Result |
|---|---|
| Apify Proxy `auto` (datacenter) | HTTP 429 × 3 |
| `groups-auto` + session | HTTP 429 × 3 |
| `BUYPROXIES94952` + session | HTTP 429 × 3 |
| **`RESIDENTIAL`, country-US** | **HTTP 200 × 3, 40 rows each** |

So the Actor defaults to cheap datacenter proxies and, after 2 consecutive 429s, **switches itself
to residential IPs for the rest of the run** — with a fresh retry budget on the new transport, so
the escalation actually rescues the request that triggered it. It says so in the log and the run
status.

Proof under load: **five runs fired simultaneously**, each pulling 300 creatives from a different
advertiser. Every one hit rate limits (5, 6, 8, 8 and 16 refusals), every one escalated, and every
one finished **SUCCEEDED with 300/300 creatives and 300/300 detail enrichment** in 124–158 s.
Delivered equalled billed on all five.

If every single request is refused, the run does **not** pretend the advertiser has no ads — and it
does not end FAILED either. It exits cleanly with a status message that starts
`BLOCKED, not empty:` and gives you the exact request counts and the fix. A blocked page is never
reported as an empty page.

### ❓ FAQ

**Q: Do I need a Google account or API key?**
No. The Ads Transparency Center is public and this Actor uses no credentials, cookies or login.

**Q: How do I find an advertiser ID?**
Open the advertiser on adstransparency.google.com; the `AR…` segment of the URL is the ID. Or skip
it and pass the domain or brand name.

**Q: Why do two advertisers come back for one domain?**
Large brands verify one advertiser entity per market or per agency (`Nike Retail BV`,
`NIKE GLOBAL TRADING B.V. SINGAPORE BRANCH`, `WPP MEDIA MANAGEMENT`). All of them are returned so
you see the whole picture; group by `advertiserId` if you want them separated.

**Q: What exactly is `daysShown`?**
Google's own counter of days the creative was shown. Measured against the first/last-shown span on
240 creatives: equal on 76, **lower** on 161 (the ad did not run every day), and higher by at most
one day on 3. Treat it as run-days, not the calendar gap.

**Q: Why is `adHeadline` empty on most rows?**
Google keeps a decodable live preview for only some creatives. For the rest, the ad's wording is
rendered into `creativeImageUrl` — a picture of the ad, sitelinks and all. Sorting by
`format = TEXT` and reading the images is how you see the copy for the other 97%.

**Q: Are impressions exact numbers?**
No, and nobody's are. Google publishes **banded ranges** (`impressionsMin` … `impressionsMax`) and
only for ads above a reach threshold, largely in the EU under the DSA. 19.5% of the measured sample
carried a range.

**Q: What are the platform values?**
`Google Search`, `Google Shopping`, `YouTube`, `Google Maps`, `Google Play` — taken from Google's own
frontend enum, not guessed.

**Q: How far back does it go?**
As far as Google's archive does. The measured sample includes creatives first shown in 2021.

**Q: Can I filter by date?**
Not server-side — Google's endpoint no longer exposes a date filter. Filter `firstShownAt` /
`lastShownAt` in the dataset, or use the per-country dates in `regionStats`.

**Q: Can I get more than 100 ads?**
Yes. 100 is Google's page ceiling; the Actor pages through with a cursor until it hits `maxItems`.
Cursor pagination was verified over 1,000 rows across five targets with **zero duplicates**.

**Q: Does a run ever return fewer rows than I asked for?**
Yes, when the advertiser simply has fewer ads, or when a country/format filter is narrower than
their activity. The run status message tells you which of the two happened.

**Q: What happens at my spend cap?**
The run stops delivering. Because rows are billed on push, you never receive unpaid rows or pay for
rows you did not receive.

***

### ⚖️ Legal and fair use

The Google Ads Transparency Center is a **public transparency disclosure** that Google publishes to
meet regulatory obligations (the EU Digital Services Act among them). This Actor reads only the
public, unauthenticated data those pages already serve to any visitor: advertiser names, ad
creatives, and the run metadata Google itself chose to publish. It uses no credentials, bypasses no
login, and collects no personal data about consumers.

Use it for competitor and market research, ad-creative analysis, brand-safety monitoring and
compliance work. You are responsible for how you use the output, including any downstream use of
the creative images, which remain the property of their advertisers.

***

### 🔧 Output fields

`creativeId` · `advertiserId` · `advertiserName` · `advertiserDomain` · `format` · `formatId` ·
`firstShownAt` · `lastShownAt` · `daysShown` · `creativeUrl` · `adHeadline` · `adDescription` ·
`adDisplayUrl` · `adExtensions` · `creativeImageUrl` · `creativeWidth` · `creativeHeight` ·
`previewUrl` · `imageUrls` · `previewUrls` · `variantCount` · `countries` · `countryCount` ·
`impressionsMin` · `impressionsMax` · `platforms` · `regionStats` · `adCategory` · `adCategoryId` ·
`searchedBy` · `searchedValue` · `scrapedAt`

# Actor input Schema

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

The websites the ads point at, e.g. `nike.com`. This is the easiest way in: you do not need to know Google's advertiser ID. Use the bare registered domain (`https://`, `www.` and any path are stripped for you). One advertiser domain can map to several legal advertiser entities and all of them are returned.

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

Google Ads Transparency advertiser IDs, e.g. `AR16735076323512287233` — the `AR…` segment of an adstransparency.google.com advertiser URL. Exact and unambiguous; use it when a domain is shared by several advertisers. All the IDs you list are queried in a single request.

## `advertiserNames` (type: `array`):

Advertiser names to look up, e.g. `Progressive Insurance`. Each name is resolved to real advertiser IDs through Google's own suggestion endpoint and the top matches are then scraped. Use `Max advertisers per brand name` to control how many entities each name expands to.

## `countries` (type: `array`):

Only return creatives that ran in these countries, as ISO 3166-1 alpha-2 codes (`US`, `GB`, `DE`, `JP`). Leave empty for every country. This is a server-side filter, so it makes the scrape cheaper rather than just trimming the output. Codes that are not valid alpha-2 are ignored with a warning.

## `format` (type: `string`):

Restrict to one Google ad format. `TEXT` is the search/text ad (Google archives these as a rendered screenshot of the ad, sitelinks included), `IMAGE` is a display or shopping creative, `VIDEO` is a YouTube ad. Google's API accepts only one format at a time, so `any` is the way to get the full mix.

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

On by default, and it is where most of the value is: one extra request per creative adds every country the ad ran in with per-country first/last-shown dates, the impression range, the Google Search / Shopping / YouTube / Maps / Play split, every archived rendition of the creative, and — for search ads that still have a live preview — the ad's own headline and description text. Switch it off for a roughly 2x faster, list-only scrape.

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

Stop after this many creatives across all targets. Large advertisers run tens of thousands of ads, so the default is deliberately modest — raise it once you know the scope you want. 0 = no limit.

## `maxItemsPerTarget` (type: `integer`):

Cap the creatives taken from any single domain / advertiser-ID set / brand name, so one enormous advertiser cannot eat the whole run. 0 = no per-target cap (the total cap still applies).

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

How many advertiser entities each entry in `Brand names` expands to. A brand often has one advertiser account per market, so 3 picks up the main ones without pulling in every namesake.

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

How many creative-detail requests run in parallel. Google rate-limits this endpoint per IP (HTTP 429), so raising this without proxy rotation will slow the run down rather than speed it up.

## `residentialFallback` (type: `boolean`):

Leave this on. Google rate-limits this endpoint per IP and Apify's shared datacenter range can be sitting on HTTP 429 for hours at a time; when that happens the run automatically switches to residential IPs instead of returning you an empty dataset. Turn it off only if your account must never use residential proxy traffic.

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

Apify Proxy is on by default and you should leave it on: Google rate-limits this endpoint per IP, and a rotating proxy session per request is what keeps a long run from stalling on HTTP 429. The endpoint itself needs no residential IPs — datacenter is enough.

## Actor input object example

```json
{
  "domains": [
    "nike.com"
  ],
  "advertiserIds": [],
  "advertiserNames": [],
  "countries": [],
  "format": "any",
  "includeDetails": true,
  "maxItems": 20,
  "maxItemsPerTarget": 0,
  "maxAdvertisersPerName": 3,
  "detailConcurrency": 6,
  "residentialFallback": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `creatives` (type: `string`):

The dataset of scraped ad creatives (one item per creative).

# 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 = {
    "domains": [
        "nike.com"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/adstransparency-google-creatives-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 = {
    "domains": ["nike.com"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/adstransparency-google-creatives-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 '{
  "domains": [
    "nike.com"
  ],
  "maxItems": 20
}' |
apify call scrapersdelight/adstransparency-google-creatives-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/adstransparency-google-creatives-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/KfiEnv1kjusga8y16/builds/th4pr62dvrdg2L6Dc/openapi.json
