# Google Ads Transparency Scraper — Ads, Creatives & Contacts (`scrapersdelight/google-ads-transparency-scraper`) Actor

Scrape the Google Ads Transparency Center: get every ad an advertiser runs on Google by domain, advertiser ID or brand name — creative ID, format, image/preview URL, first and last shown, days active, regions — plus the advertiser's website email, phone and socials. No login.

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

## Pricing

from $2.00 / 1,000 per ad creative returneds

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 — Ads, Creatives & Contacts

Turn the [Google Ads Transparency Center](https://adstransparency.google.com) into a clean
table of every ad an advertiser runs on Google — Search, YouTube, Display, Shopping, Maps and
Play. One row per ad creative: **creativeId, advertiserId, advertiserName, domain, format,
imageUrl / previewUrl, videoUrl, firstShown, lastShown, daysShown, googleCreativeId,
googleCustomerId, adGroupId, regionsShown, variations, adUrl** — and, on the same row, the
advertiser's own website contact: **advertiserEmail, advertiserPhone, advertiserSocials,
advertiserWebsite**. Look up by domain, advertiser ID, brand name or a pasted Transparency
Center URL. No login. No cookies. No CAPTCHA solving. Runs at 256 MB with no browser.

**Measured 2026-08-22: 600 ads across three domains in 44 seconds, 600 unique creatives,
0 duplicates, 15 of 15 pages served** — through the Apify datacenter proxy, not a home IP.
The same sustained test passed 15 of 15 on RESIDENTIAL too.

```json
{
  "domains": ["hellofresh.com"],
  "region": "US",
  "maxAdsPerTarget": 40,
  "maxItems": 200,
  "enrichAdvertiserContacts": true
}
```

Click **Try for free** and hit **Start** — that block is literally the input the Actor ships
with. A run on those shipped defaults returned **40 ads in 16 seconds** with creative ID,
advertiser, format, dates and days-active at 100% fill, and the advertiser contact found
(`hello@hellofresh.com`, Facebook, Instagram, X). Cost: **$0.11** ($0.08 for the ads + $0.03
for the one contact).

***

### The wedge: "who is advertising" becomes "who to write to"

More than a dozen Actors on the Store already read the Transparency Center, and several of
them do it well. What none of their input schemas has is the step after the ad list. An
advertiser that is spending on Google right now is a live buyer of marketing, analytics,
creative and agency services — the ad row tells you they spend, but nothing in the
Transparency Center tells you how to reach them.

This Actor opens the advertiser's **own website** (the landing domain the Transparency Center
publishes on every domain-route row), reads the public business contact off the homepage and
its contact / about / imprint pages, and puts it on the row: the best on-domain email
(`sales@`, `hello@`, `info@` ranked above `careers@` and `privacy@`), the phone number from
the site's `tel:` links, and the LinkedIn / Facebook / Instagram / X / YouTube / TikTok
profiles. **Business contact data only** — no people lookups, no name inference, nothing
behind a login.

It is **success-billed**: one `advertiser-contact-found` event per advertiser domain,
charged **only when an email or phone was actually read off the site**. A site with nothing
public, a dead domain or a bot-walled homepage costs nothing and the columns stay `null`.
Measured on a deliberately hard list of twelve big-brand domains (the ones most likely to
hide contact details behind forms): **9 of 12 returned an email or phone**; the three misses
were two bot-walled homepages and one HTTP 400, all free.

Two more things that set the row apart:

- **`videoUrl`** — for YouTube video ads, with *Fetch per-region reach and variations* on,
  the Actor resolves the actual `youtube.com/watch?v=` link from the preview bundle. Measured
  on 12 Nike video ads: 7 resolved (58%); the other 5 are non-YouTube preview types and stay
  `null`.
- **Monitor mode** (`onlyNewAds`) — a named key-value store remembers every creative the
  Actor has delivered; a scheduled run returns only creatives launched since, and is billed
  only for those. Verified: the second run on the same domain returned 0 rows, stopped after
  one page, and said so in the status message.

***

### Read this before you buy rows

Five things that would otherwise turn into a refund request.

1. **Contact enrichment needs a domain, and only the domain route guarantees one.** Rows
   found by domain carry `domain` on 100% of rows (Google publishes it). Rows found by
   advertiser ID or brand name carry no landing domain, so `advertiserEmail` / `advertiserPhone`
   stay `null` on those routes and you are never charged a contact event for them. If you
   want the contact, search by domain.
2. **Brand-name search is Google's fuzzy autocomplete, not a brand lookup.** `"Nike"` returns
   ten fuzzy matches — *Nikena*, *Nikesh*, a Kenyan *Nike* — alongside *Nike, Inc.*. The Actor
   ranks them by name match, verified badge and account size (on a log scale, so HelloFresh's
   10,000-ad main account outranks its 2-ad verified stub), which puts the 10,000-ad
   *Nike, Inc.* account first, but the candidate list is Google's and a brand with no clean
   match can be missed. When you know the domain, use it.
3. **Region changes the result set, not just a filter flag.** `hellofresh.com` returned 40
   ads for US, 10 for GB and a different 40 for DE in the same minute. `ANYWHERE` returns
   ads shown in any country. Pick deliberately — the default is US.
4. **Text ads come back as images.** Google renders Search text ads as a static image
   (`imageUrl`, `tpc.googlesyndication.com/archive/simgad/…`), so there is no headline or
   description text to extract — for any scraper, not just this one. Image and video ads come
   back as a Google-hosted preview (`previewUrl`) that renders the live creative.
5. **`daysShown` is Google's own "days shown" figure, not a span you can recompute** — it
   usually differs from `lastShown − firstShown` (in our sample by 2 days on most rows and by
   142 on one). Treat it as a Google-reported reach signal, not as continuous days live.

***

### What you get

One row per unique ad creative. Dates are ISO-8601 UTC; `lastShown` is the most recent
impression Google recorded in the searched region.

| Group | Fields | Example |
|---|---|---|
| **Identity** | `creativeId`, `advertiserId`, `advertiserName`, `advertiserVerified`, `advertiserCountry`, `domain` | `CR08275988769178386433` · `Grocery Delivery E-Services USA Inc.` · `hellofresh.com` |
| **Creative** | `format`, `formatCode`, `previewType`, `imageUrl`, `imageWidth`, `imageHeight`, `previewUrl`, `videoUrl`, `youtubeVideoId` | `IMAGE` · `iframe` · a `content.js` preview URL |
| **Google IDs** | `googleCreativeId`, `googleCustomerId`, `adGroupId`, `versionId` | `662879873968` · `5588076203` · `150785776295` |
| **Timing** | `firstShown`, `lastShown`, `daysShown` | `2023-06-22T22:08:17Z` · `2026-08-22T16:08:31Z` · `1010` |
| **Reach** (detail on) | `regionsShown[]` (`region`, `criteriaId`, `lastShown`), `regionCount`, `variations[]`, `variationCount` | `[{US, 2840, 2026-08-22}, {DE, …}]` · `3` |
| **Contact** (domain route) | `advertiserWebsite`, `advertiserEmail`, `advertiserEmails[]`, `advertiserPhone`, `advertiserPhones[]`, `advertiserSocials{}`, `contactSource`, `contactFound` | `hello@hellofresh.com` · `{facebook, instagram, twitter}` |
| **Links** | `adUrl`, `advertiserUrl` | the Transparency Center pages for the ad and the advertiser |
| **Provenance** | `region`, `searchType`, `searchTarget`, `isNew`, `scrapedAt` | `US` · `domain` · `hellofresh.com` |

The dataset ships with a saved **table view** (advertiser, domain, format, dates, days
active, image, preview, contact email, phone, website, regions, IDs) so you do not have to
configure columns.

***

### Field fill — measured on 600 ads

Three domains (`hellofresh.com`, `hubspot.com`, `casper.com`), region ANYWHERE, 200 ads each,
2026-08-22. Sorted by fill, so the sparse fields are impossible to miss. Format mix on this
sample: 463 TEXT, 108 IMAGE, 29 VIDEO.

| Field | Fill | Notes |
|---|---|---|
| `creativeId` / `advertiserId` / `advertiserName` | 100% | |
| `domain` | 100% | domain route; `null` on advertiser-ID and name routes |
| `format` / `formatCode` | 100% | TEXT / IMAGE / VIDEO |
| `firstShown` / `lastShown` / `daysShown` | 100% | |
| `adUrl` / `advertiserUrl` | 100% | |
| `advertiserWebsite` | 100% | the homepage that answered (domain route) |
| `advertiserSocials` | 100% | at least one profile on all 3 domains |
| `contactSource` | 100% | the page the contact was read from |
| **`imageUrl` / `imageWidth` / `imageHeight`** | **73.5%** | almost every TEXT ad (Google renders Search ads as a static snapshot); IMAGE and VIDEO ads normally carry `previewUrl` instead |
| **`advertiserEmail`** | **66.7%** | 2 of the 3 domains published one (`hello@hellofresh.com`, `support@casper.com`); HubSpot publishes phone only |
| **`advertiserPhone`** | **66.7%** | HubSpot and Casper; HelloFresh's site has no `tel:` link |
| **`previewUrl` / `googleCreativeId` / `googleCustomerId` / `adGroupId`** | **26.5%** | wherever Google serves a live preview bundle — nearly all IMAGE and VIDEO ads and a small share of TEXT ads. These IDs are parsed out of the preview URL, so they are absent whenever `previewUrl` is `null`. |
| **`versionId`** | **5.7%** | only on versioned rich-media previews |
| **`regionsShown` / `variations`** | **0% here, 100% with detail on** | measured 12/12 on a separate detail run; off by default |
| **`videoUrl` / `youtubeVideoId`** | **58% of VIDEO ads with detail on** | 7 of 12 Nike video ads resolved to a YouTube watch URL |

**The headline that could mislead you: `imageUrl` + `previewUrl` together are 100%.** Every
ad has exactly one of them — `imageUrl` when Google rendered a static snapshot (nearly always
a TEXT ad), `previewUrl` when it serves a live preview (nearly all IMAGE and VIDEO ads, and
some TEXT ads). Do not filter on one of them alone.

***

### How to run it

#### 1. By domain (the usual choice, and the one that brings the contact)

```json
{
  "domains": ["hellofresh.com", "casper.com"],
  "region": "US",
  "maxAdsPerTarget": 200,
  "maxItems": 1000
}
```

Google matches ads whose landing page is on that domain, so one domain can return several
advertiser accounts (`nike.com` returns *Nike, Inc.*, *Nike Retail BV* and *NIKE GLOBAL
TRADING B.V. SINGAPORE BRANCH*). Paste a full URL and the Actor strips it to the hostname.

#### 2. By advertiser ID

```json
{ "advertiserIds": ["AR00930880903813529601"], "region": "ANYWHERE", "maxAdsPerTarget": 500 }
```

The `AR…` token from an advertiser page URL. Exact, but carries no landing domain (see gotcha 1).

#### 3. By brand name

```json
{ "queries": ["HelloFresh"], "maxAdvertisersPerQuery": 3, "region": "DE" }
```

Resolved through the Transparency Center's own autocomplete into up to `maxAdvertisersPerQuery`
accounts, each walked separately. The log prints exactly which accounts were chosen and why
(`HelloFresh SE (AR1741…, DE)` vs `HelloFresh SE (AR1171…, DE, verified)`).

#### 4. Pasted Transparency Center URLs

```json
{
  "startUrls": [
    { "url": "https://adstransparency.google.com/advertiser/AR00930880903813529601?region=US" },
    { "url": "https://adstransparency.google.com/?domain=casper.com&region=GB" },
    { "url": "https://adstransparency.google.com/advertiser/AR00930880903813529601/creative/CR12924188684000952321?region=US" }
  ]
}
```

Advertiser pages, domain searches and single creatives are all parsed; a `region=` in the URL
overrides the Region field for that URL. A single-creative URL returns that one ad with its
regions and variations.

#### Filters

- **`region`** — the Transparency Center's country dropdown (49 countries + ANYWHERE). Applied
  by Google inside the search.
- **`adFormat`** — ALL / TEXT / IMAGE / VIDEO. Applied by Google inside the search, so an
  unwanted format is never fetched or billed. Verified: `VIDEO` on `nike.com` returned 40 of 40
  video ads.
- **`lastShownAfter`** — keep ads still live on or after a date. Results come back
  newest-last-shown first, so the Actor stops paging a target as soon as it passes this date:
  a 7-day window on a 10,000-ad brand costs one or two pages, not 250.
- **`firstShownAfter`** — keep ads *launched* on or after a date (new creatives). Per-row only.

#### Per-region reach, variations and video URLs

```json
{ "domains": ["nike.com"], "adFormat": "VIDEO", "includeRegionDetail": true, "maxAdsPerTarget": 50 }
```

One extra request per ad adds `regionsShown` (every country the ad ran in, each with its own
last-shown date — a Nike video ad listed DE, ID, PH, NL, PL, FR and US), `variations` (every
A/B variant with its own preview) and, for YouTube ads, `videoUrl`. **Same price per ad** —
it costs time, not money. Measured: 12 ads with detail in 13 RPC calls, ~10 seconds.

#### Monitor mode — only what launched since last time

```json
{ "domains": ["casper.com"], "onlyNewAds": true, "monitorStoreName": "monitor-casper", "region": "US" }
```

The first run seeds the memory and returns everything up to your limits. Every later run
returns only creatives not delivered before — and stops paging at the first already-seen
creative, because results are ordered newest-last-shown first and everything launched since
sits above it. Use one `monitorStoreName` per watch. Attach an Apify **Schedule** (`0 7 * * *`)
and a webhook and you have a standing new-ad alert on a competitor for a few cents a day.

#### Scheduling and integrations

Save the input as a **Task**, attach a **Schedule**, read the dataset over the REST API or
through the standard integrations (Zapier, Make, n8n, webhooks, MCP). From the API:

```bash
curl -X POST "https://api.apify.com/v2/acts/scrapersdelight~google-ads-transparency-scraper/runs?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"domains":["casper.com"],"region":"US","maxAdsPerTarget":100}'
```

***

### Sample row

A real row from the shipped-default run, captured 2026-08-22 17:34 UTC.

```jsonc
{
  "creativeId": "CR08275988769178386433",
  "advertiserId": "AR00930880903813529601",
  "advertiserName": "Grocery Delivery E-Services USA Inc.",
  "advertiserVerified": null,
  "advertiserCountry": null,
  "domain": "hellofresh.com",

  "format": "IMAGE",
  "formatCode": 2,
  "previewType": "iframe",
  "imageUrl": null,
  "imageWidth": null,
  "imageHeight": null,
  "previewUrl": "https://displayads-formats.googleusercontent.com/ads/preview/content.js?client=ads-integrity-transparency&obfuscatedCustomerId=5588076203&creativeId=662879873968&uiFeatures=12,54&adGroupId=150785776295&itemIds=12663816311744455808&…",
  "googleCreativeId": "662879873968",
  "googleCustomerId": "5588076203",
  "adGroupId": "150785776295",
  "versionId": null,
  "videoUrl": null,
  "youtubeVideoId": null,

  "firstShown": "2023-06-22T22:08:17.000Z",
  "lastShown": "2026-08-22T16:08:31.000Z",
  "daysShown": 1010,
  "regionsShown": null,
  "regionCount": null,
  "variationCount": null,
  "variations": null,

  "adUrl": "https://adstransparency.google.com/advertiser/AR00930880903813529601/creative/CR08275988769178386433?region=US",
  "advertiserUrl": "https://adstransparency.google.com/advertiser/AR00930880903813529601?region=US",
  "region": "US",

  "advertiserWebsite": "https://www.hellofresh.com/",
  "advertiserEmail": "hello@hellofresh.com",
  "advertiserEmails": ["hello@hellofresh.com", "support@hellofresh.com"],
  "advertiserPhone": null,
  "advertiserPhones": [],
  "advertiserSocials": {
    "facebook": "https://www.facebook.com/HelloFreshus",
    "instagram": "https://instagram.com/hellofresh",
    "twitter": "https://twitter.com/HelloFresh"
  },
  "contactSource": "https://www.hellofresh.com/",
  "contactFound": true,

  "searchType": "domain",
  "searchTarget": "hellofresh.com",
  "isNew": null,
  "scrapedAt": "2026-08-22T17:34:52.246Z"
}
```

Fields people misread:

- `advertiserName` is the **legal entity Google verified**, not the brand — HelloFresh's US
  ads run under *Grocery Delivery E-Services USA Inc.* The brand is in `domain`.
- `advertiserVerified` / `advertiserCountry` are only known on the brand-name route (they
  come from the autocomplete); `null` elsewhere is "not available", not "unverified".
- `googleCustomerId` groups ads from the same Google Ads account; `adGroupId` clusters ads from
  the same campaign. Both exist only where there is a `previewUrl`.
- `contactFound: false` with `advertiserWebsite` set means either the site answered and
  published no email and no `tel:` link, or a contact was found after your spend cap and
  therefore not attached. Either way you were not charged.
- `isNew` is `true` on every delivered row in monitor mode (by definition) and `null` otherwise.

***

### Input

Fields in the order the Console shows them.

| Field | Type | Default | What it does |
|---|---|---|---|
| **🎯 Who to look up** | | | |
| `domains` | string list | `["hellofresh.com"]` | Advertiser landing domains. The route that carries `domain` and therefore the contact. |
| `advertiserIds` | string list | `[]` | `AR…` IDs. Exact, no domain. |
| `queries` | string list | `[]` | Brand names, resolved via Google's autocomplete. |
| `maxAdvertisersPerQuery` | integer | `3` | Accounts a name may expand into (1–10). |
| `startUrls` | URLs | `[]` | Advertiser / domain-search / creative URLs from the site. |
| **🌍 Filters** | | | |
| `region` | select | `US` | Country dropdown or ANYWHERE. Applied by Google. |
| `adFormat` | select | `ALL` | TEXT / IMAGE / VIDEO. Applied by Google. |
| `lastShownAfter` | date | — | Still live on or after this date; shortens paging. |
| `firstShownAfter` | date | — | Launched on or after this date; per row. |
| **📦 Output shape** | | | |
| `includeRegionDetail` | boolean | `false` | +1 request per ad: `regionsShown`, `variations`, `videoUrl`. Same price. |
| `enrichAdvertiserContacts` | boolean | `true` | Open the advertiser's site for email / phone / socials. Success-billed. |
| `onlyNewAds` | boolean | `false` | Monitor mode — only creatives never delivered before. |
| `monitorStoreName` | string | `google-ads-transparency-monitor` | One memory per watch. |
| **💷 Limits & cost** | | | |
| `maxAdsPerTarget` | integer | `40` | Per domain / advertiser / name. 40 = one request. |
| `maxItems` | integer | `200` | **Your hard cost ceiling.** You are never charged for more than this many rows — a run may stop short of it. |
| **⚙️ Advanced** | | | |
| `proxyConfiguration` | proxy | Apify proxy (datacenter) | Not walled; datacenter by design, one capped RESIDENTIAL retry as the safety net. |

***

### Pricing

**$0.002 per ad returned — $2 per 1,000 — plus $0.03 per advertiser contact found.** No start
fee, no monthly fee from this Actor.

| Run | Ads | Contacts | Cost |
|---|---|---|---|
| The shipped default (HelloFresh, US) | 40 | 1 | **$0.11** |
| One competitor, full US creative set | 1,000 | 1 | **$2.03** |
| Ten competitors × 200 ads, all with a site contact | 2,000 | 10 | **$4.30** |
| Daily monitor, quiet day | 0 | 0 | **$0.00** |

- **`ad-scraped` is charged per unique creative delivered.** An ad reached through two targets
  is billed once — duplicates are dropped on `creativeId` before billing. Rows removed by the
  date filters, the format filter or monitor memory are never pushed and never billed.
- **`advertiser-contact-found` is charged once per advertiser domain, only on success.** A
  domain with nothing public is free, and a domain that appears under 500 ads is billed one
  contact, not 500.
- **Rows are charged as they are pushed** (`Actor.pushData(rows, 'ad-scraped')`), so at a
  spend cap you get whole rows and stop, never a half-billed dataset. A contact found after
  the cap is not attached and not billed.
- Per-region detail and video resolution cost no extra money — only time.

***

### Honest limits

- **No ad copy for text ads.** Google renders Search text ads as images; nothing on the
  Transparency Center exposes the headline or description as text. `imageUrl` is the snapshot.
- **No spend, no impressions, no landing-page URL.** The public Transparency Center publishes
  none of these for commercial ads (political ads are a separate, region-specific surface this
  Actor does not target). `daysShown` and `regionsShown` are the reach signals that exist.
- **Contact enrichment is domain-route only** and measured **9 of 12 on big brands**; small
  and mid-size advertisers publish contact details more readily, big brands hide them behind
  forms. A bot-walled homepage (Zillow, Lululemon on the datacenter rung) is a free miss.
- **Brand-name search is only as good as Google's autocomplete.** Use the domain when you
  have it.
- **`videoUrl` resolves for YouTube-hosted video ads only** — 58% of a Nike sample; the rest
  are other preview formats and stay `null`.
- **Creative assets are linked, not downloaded.** `imageUrl` and `previewUrl` are Google-hosted
  URLs; this Actor does not re-host or store any creative.
- **No login, no cookies, no CAPTCHA solving.** Everything read here is the public, logged-out
  Transparency Center and the advertiser's public website.

***

### How it works, and what it cost to make reliable

The Transparency Center web app calls an un-authenticated RPC
(`/anji/_/rpc/SearchService/SearchCreatives`) with a form-encoded JSON body; this Actor calls
the same endpoint, 40 creatives per page, token-paginated, with the region and format filters
applied server-side. Per-region reach comes from `LookupService/GetCreativeById`; brand names
go through `SearchService/SearchSuggestions`. No browser, no rendering, no cookie jar — which is
why it runs in **256 MB**.

**Transport ladder, measured 2026-08-22 through Apify, fresh proxy session per call:**

| Rung | Result |
|---|---|
| Direct, home IP | 200 with 40 creatives on the first probes — then **HTTP 429** after the day's shared probing. Google rate-limits per IP. |
| Apify proxy, datacenter | **15 / 15** token-paginated pages, 40 creatives each, **600 unique / 0 duplicates**, 0.4–1.3 s per page |
| Apify RESIDENTIAL + country US | **15 / 15**, 600 unique / 0 duplicates, 0.8–6.5 s per page |

Because the per-IP limit is real, every call uses a fresh datacenter session, a 429 or an
interstitial is retried on a new one, and a request that fails all three datacenter attempts
gets one retry through RESIDENTIAL (capped at 30 per run so the worst case stays bounded). A
page that fails every rung stops that target with a warning — nothing is invented and nothing
unfetched is billed.

`robots.txt` on adstransparency.google.com returns 404 (no Disallow). The advertiser-website
crawl reads at most four pages per domain (homepage + up to three contact/about/imprint
pages), same-site links only, and stops at the first email found.

***

### Duplicates — measured in both directions

| Walk | Creatives returned | Unique | Duplicates |
|---|---|---|---|
| `hellofresh.com`, 15 contiguous pages, datacenter | 600 | 600 | **0.0%** |
| `hellofresh.com`, 15 contiguous pages, RESIDENTIAL | 600 | 600 | **0.0%** |
| 3 domains × 200 ads, ANYWHERE | 600 | 600 | **0.0%** |

Google's page token is stable, so a single walk repeats nothing. Duplicates **do** arise when
two targets overlap — a creative URL pasted alongside its own domain search, or a brand name
that resolves to an account already covered by a domain — and the Actor drops them on
`creativeId` before billing. Across runs, use monitor mode.

***

### When a run stops early

This Actor never ends FAILED on a data condition; it stops cleanly and tells you why in the
status message:

- **Run time limit** → the run stops early to stay inside it and the status message says so.
  Every request is clamped to the time remaining and none starts with under 8 s left.
- **Your spend cap** → the run stops cleanly and the status message says why.
- **0 rows** → the message names the cause: nothing in that region / format / date window,
  nothing new since the last monitor run, or the RPC unreachable on every rung.
- **Unknown brand name** → a warning naming the query, with the advice to use the domain.
- **Bad advertiser ID or URL** → skipped with a warning; the rest of the run continues.

***

### Who buys this

- **Agencies and marketing SaaS prospecting advertisers** — the row is an active Google
  advertiser with a contact on it; `daysShown` sorts the serious spenders from the testers.
- **Competitive-intelligence and creative teams** — every creative a rival runs, by format and
  region, with A/B variations and the YouTube link, diffed week over week in monitor mode.
- **Ad-tech, analytics and attribution vendors** — `googleCustomerId` and `adGroupId` let you
  cluster a brand's accounts and campaigns without the brand's permission.
- **Market researchers sizing a vertical** — run the domains of a category and count live
  creatives, regions and launch cadence per player.
- **Brand and compliance teams** — watch your own domain (and your resellers') for ads you did
  not authorise; `regionsShown` shows where they run.

***

### FAQ

**Does this need a Google account, a login or cookies?**
No. The Transparency Center is a public, logged-out surface; so is the advertiser's website.

**Why is the advertiser name a company I have never heard of?**
Google verifies the legal entity, not the brand. `domain` carries the brand.

**Can I get the ad text?**
Not for Search text ads — Google serves them as images. Image and video ads come with a
Google-hosted preview URL that renders the live creative.

**How does the contact enrichment work, and what does it cost?**
The Actor opens the advertiser's own site and reads the public email, `tel:` phone and social
links; role inboxes (`sales@`, `info@`) count, addresses on unrelated corporate domains are
discarded (free-mail boxes such as gmail.com are kept — for a small advertiser that often IS
the office inbox). $0.03 per domain, only when something was found.

**Do I need a residential proxy?**
No. Datacenter held 15 / 15 on the sustained test and is the default; RESIDENTIAL is the
automatic, capped fallback.

**Will a run ever fail with zero rows?**
No. Zero rows ends SUCCEEDED with a status message naming the cause.

***

### Legal & fair use

This Actor reads the public, logged-out Google Ads Transparency Center and the public
websites of advertisers. It does not log in, does not solve or bypass any challenge, does not
download or re-host creative assets, and collects business contact details only (no people
lookups). **You are responsible for complying with Google's terms and with how you use the
data**, including GDPR, PECR/CAN-SPAM and the rules on unsolicited B2B outreach in your
jurisdiction.

Google and the Google Ads Transparency Center are trademarks of Google LLC. This Actor is not
affiliated with, endorsed by, or connected to Google.

***

### Feedback

Found a missing field or want a new filter? Open an issue on the **Issues** tab, and if the
Actor earns it, a review on the **Reviews** tab helps other buyers find it.

# Actor input Schema

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

Bare domains whose ads you want, e.g. hellofresh.com or nike.com (no https://, no path — the Actor strips them if you paste a URL). The Transparency Center matches ads whose landing page is on that domain, so ONE domain can return SEVERAL advertiser accounts (nike.com returns Nike, Inc. and Nike Retail BV). This is the most precise way to target a company and the only one that guarantees a domain for the contact enrichment.

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

Transparency Center advertiser IDs — the AR… token in an advertiser page URL, e.g. AR16735076323512287233 (Nike, Inc.). Use this when you already know the exact advertiser account and want only its ads. The ads of an advertiser ID carry no landing domain, so contact enrichment on this route only works when the ad preview exposes one.

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

Free-text advertiser names, e.g. "HelloFresh" or "Nike". Each name is resolved through the Transparency Center's own autocomplete into up to "Max advertisers per name" advertiser accounts (verified accounts first), and every one of those is walked. Name search is fuzzy — "nike" also matches "nikey" — so prefer a domain when you have one.

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

How many advertiser accounts a brand-name query may expand into. The autocomplete returns up to 10 candidates; the Actor keeps the ones that are verified or whose name starts with your query first. 3 is right for a brand, 10 for a market sweep ("plumber").

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

Paste URLs copied from adstransparency.google.com — an advertiser page (https://adstransparency.google.com/advertiser/AR…?region=US), a domain search (https://adstransparency.google.com/?domain=nike.com\&region=US) or a single creative (…/advertiser/AR…/creative/CR…). The advertiser ID, domain, creative ID and region are parsed out automatically; a region in the URL overrides the Region field for that URL.

## `region` (type: `string`):

The country whose ad inventory to search — the same dropdown as the Transparency Center. An ad is returned when it was shown in that country. ANYWHERE returns ads shown in any country. Measured 2026-08-22: the same domain returned different row sets for US, GB and ANYWHERE, so set this deliberately.

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

ALL (default) returns every format. TEXT = Search text ads, IMAGE = display/image ads, VIDEO = YouTube video ads. Google applies this filter inside the search, so a format you do not want is never fetched or billed.

## `lastShownAfter` (type: `string`):

Keep only ads whose LAST shown date is on or after this day — i.e. ads that are still live or were live recently. Results come back newest-last-shown first, so the Actor stops paging a target as soon as it passes this date, which makes a tight window cheap. Leave empty for no lower bound.

## `firstShownAfter` (type: `string`):

Keep only ads that were LAUNCHED on or after this day — new creatives. Applied per row; it does not shorten paging because results are ordered by last-shown, not first-shown. Leave empty for no bound.

## `includeRegionDetail` (type: `boolean`):

OFF (default) returns what the search page carries: ids, advertiser, format, preview/image URL, first/last shown, days active. ON makes one extra request per ad and adds regionsShown (every country the ad ran in, each with its own last-shown date), regionCount, variationCount and variations (every A/B creative variant with its own preview URL). Same price per ad either way — it costs time, not money. Measured 2026-08-22: 40 ads → 40 detail calls in ~15 s on the datacenter rung.

## `enrichAdvertiserContacts` (type: `boolean`):

ON (default) opens the advertiser's own website — the searched domain, or the landing domain read from the ad preview — and reads the public contact details off the homepage and its contact/about pages: email addresses, phone numbers (tel: links), LinkedIn / Facebook / Instagram / X / YouTube / TikTok profiles. Business contact data only (info@, sales@, the office number) — no people lookups. Charged on its own success-billed event ONCE PER ADVERTISER DOMAIN and ONLY when an email or a phone is found; a site with nothing public, a dead domain or a blocked fetch costs nothing and the columns stay null. This is what turns "who is advertising" into a lead you can write to.

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

ON remembers every creative ID this Actor has delivered in a named key-value store (see "Monitor memory name") and returns only creatives it has never delivered before. The first run seeds the memory and returns everything; a scheduled daily or weekly run then returns just the newly launched ads — and is billed only for those. Pair with an Apify Schedule for a standing new-ad watch on a competitor.

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

Name of the key-value store that holds the seen-creative memory for Monitor mode. Use a different name per watch (e.g. monitor-hellofresh, monitor-nike) so their histories do not mix. Only used when Monitor mode is on.

## `maxAdsPerTarget` (type: `integer`):

Stop walking a single domain / advertiser / name after this many ads. The Transparency Center pages 40 ads at a time, so 40 = one request, 400 = ten. Big brands run thousands of creatives; this is the knob that keeps a competitor sweep affordable. 40 ads = $0.08.

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

Hard cap on unique ad rows delivered across every target in the run — your cost ceiling: 200 = $0.40, 1,000 = $2.00, 10,000 = $20.00. Duplicate creatives (the same ad reached through two targets) are dropped before billing and do not count.

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

Measured 2026-08-22 through Apify: direct, datacenter and RESIDENTIAL rungs all returned HTTP 200 with a full 40-creative page, and the paginated second page too — so the default is the CHEAP datacenter rung. A request that fails its datacenter retries is retried ONCE through RESIDENTIAL automatically (capped per run), so you do not need to set residential yourself.

## Actor input object example

```json
{
  "domains": [
    "hellofresh.com"
  ],
  "advertiserIds": [],
  "queries": [],
  "maxAdvertisersPerQuery": 3,
  "startUrls": [],
  "region": "US",
  "adFormat": "ALL",
  "lastShownAfter": "",
  "firstShownAfter": "",
  "includeRegionDetail": false,
  "enrichAdvertiserContacts": true,
  "onlyNewAds": false,
  "monitorStoreName": "google-ads-transparency-monitor",
  "maxAdsPerTarget": 40,
  "maxItems": 200,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per unique ad creative from the Google Ads Transparency Center: creative ID, advertiser ID and name, landing domain, format (TEXT / IMAGE / VIDEO), image or preview URL, first and last shown dates, days active, Google's internal creative / customer / ad-group IDs, optional per-region reach and variations, and the advertiser's website contact (email, phone, socials) when found. Deduplicated run-wide on creative ID, so an ad reached through two targets is billed once.

# 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": [
        "hellofresh.com"
    ],
    "maxAdsPerTarget": 40,
    "maxItems": 200,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/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 = {
    "domains": ["hellofresh.com"],
    "maxAdsPerTarget": 40,
    "maxItems": 200,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/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 '{
  "domains": [
    "hellofresh.com"
  ],
  "maxAdsPerTarget": 40,
  "maxItems": 200,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call scrapersdelight/google-ads-transparency-scraper --silent --output-dataset

```

## MCP server setup

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