# Google Ads Transparency Center & Competitor Ad Scraper (`scrapebench/google-ads-transparency-center-scraper`) Actor

Every ad an advertiser is running, from Google's Ads Transparency Center — with first and last shown dates, the creative image and the rich-media preview URL.

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

## Pricing

$0.0015 / ad creative

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 Center & Competitor Ad Scraper

### Pain points

- The Ads Transparency Center publishes no API. Its own front end talks to an internal RPC whose fields are numbered rather than named, so every field has to be identified by hand before any of it can be read.
- Google throttles that endpoint per IP and answers a spent one with a redirect to its /sorry/ page rather than an error — so an unproxied scraper returns an ordinary-looking empty result instead of saying it was refused.
- An unrecognised region code is not rejected either: it comes back as an empty creative list, which reads exactly like 'this advertiser runs no ads in that country'.
- Google states an advertiser's library size as a RANGE — 8,000 to 9,000 — never an exact number, so any tool that reports a single total is reporting something Google did not say.
- Google states an advertiser's library size only for the region you asked about, so without carrying that figure on the row there is no way to tell whether a run holds all of an advertiser's ads or two per cent of them.
- Google publishes no impression or spend figures anywhere in this service, so any tool offering them for a Google ad is not getting them from here.

### What we solve

- The RPC field map was read off the live service and is documented in the source, so the numbering is not folklore.
- A refusal is caught by checking where the response landed, not just what it says, and returns a free marker row saying the run was blocked — never zero rows on a successful-looking run.
- The proxy is on by default, because a per-IP throttle is invisible to every check that looks at the response body.
- Region codes are validated before the request, against 48 countries verified one by one.
- Both bounds of Google's stated library size ride on every row, so a capped run can be read against the source's own figure.
- Creative image URL, rich-media preview URL, first shown and last shown are all returned.

### Summary

Give it an advertiser domain (nike.com), a Google advertiser id (AR16735076323512287233) or a company name, and it returns one row per ad creative: advertiser id, verified advertiser name, domain, creative id, format, the creative's own hosted image URL or its rich-media preview URL, and the dates Google states the ad was first and last shown — which together give you how long a creative has been running, the usual proxy for whether it is working. Every row also carries Google's own stated size for that advertiser's library in that country, so you can always see what fraction of it a run covered. Reads any of 48 country libraries, all verified against the live service. Built for performance marketers auditing competitors' creative, agencies preparing pitches, and ad-intelligence tools that need the underlying data rather than a dashboard.

### Who it's for

- Performance marketers auditing what competitors are running and for how long
- Agencies building competitive creative decks for pitches
- Ad-intelligence and market-research tools needing the underlying rows
- Brand and compliance teams checking who is advertising against their name

### How to use

Set the input, run the actor, and collect results from the run's dataset (export to JSON/CSV/Excel, or pull via the Apify API). Example input:

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

See **Inputs** below for every available field.

### What you get

One row per record:

| Field | Description |
|---|---|
| `advertiser_id` | Google's own advertiser identifier (AR…) — stable across runs and regions |
| `advertiser_name` | The verified advertiser name as Google publishes it |
| `advertiser_domain` | The advertiser's domain as stated on the creative |
| `creative_id` | Google's identifier for this specific ad (CR…) |
| `format_code` | Google's own numeric format code, passed through unlabelled |
| `creative_type` | image or rich\_media — derived from what the payload contains, not from the format code |
| `creative_image_url` | Google's own hosted image for the ad, taken verbatim from the response |
| `creative_preview_url` | For rich-media ads, Google's renderable preview URL |
| `display_width / display_height` | The size the ad is displayed at, which is not the asset's own resolution |
| `first_shown / last_shown` | When Google says the ad first and last ran, as ISO 8601 UTC. The gap between them is the creative's run length |
| `days_running` | Whole days between first and last shown, worked out for you — the usual proxy for whether a creative is performing |
| `region` | Which country library the row came from |
| `advertiser_creatives_low / _high` | Google's own stated range for the advertiser's library size in that region — it never publishes an exact count |
| `matched_by` | How the advertiser was resolved: domain, advertiser\_id or name\_search |
| `advertiser_url` | The advertiser's Transparency Center page |
| `source_url` | This creative's own page on the Transparency Center |

Sample:

```json
{
  "source_url": "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR10365997061744099329?region=US",
  "advertiser_id": "AR16735076323512287233",
  "advertiser_name": "Nike, Inc.",
  "advertiser_domain": "nike.com",
  "creative_id": "CR10365997061744099329",
  "format_code": 1,
  "creative_type": "image",
  "creative_image_url": "https://tpc.googlesyndication.com/archive/simgad/2588413232821661199",
  "creative_preview_url": null,
  "display_width": 348,
  "display_height": 180,
  "first_shown": "2023-11-16T22:59:37Z",
  "last_shown": "2026-09-07T05:25:23Z",
  "days_running": 1025,
  "region": "US",
  "advertiser_creatives_low": 8000,
  "advertiser_creatives_high": 9000,
  "matched_by": "domain",
  "advertiser_url": "https://adstransparency.google.com/advertiser/AR16735076323512287233?region=US"
}
```

### Inputs

| Field | Required | Type | Default | Description |
|---|---|---|---|---|
| `query` | no | string | `"nike.com"` | An advertiser domain (nike.com), a Google advertiser id (AR16735076323512287233), or a company name to search for. Which of the three you gave is recorded on every row in 'matched\_by'. Kept forever alongside 'Advertisers' — saved tasks and API callers send it. |
| `queries` | no | array | `["nike.com", "geico.com"]` | Look up many advertisers in ONE run instead of starting a run per advertiser. Takes the same values as 'Advertiser'. Combined with it if you fill both, and duplicates are removed. The result cap applies PER advertiser, so one large advertiser cannot use up the run and leave the others looking like they run no ads. |
| `region` | no | string | `"US"` | The country whose ad library to read, as an ISO country code — US, GB, DE, FR, JP, BR and so on. This is not cosmetic: Google keeps a separate library per country and the same advertiser has very different volumes in each. An unrecognised code is refused up front, because Google answers one with an empty list rather than an error, which would read as 'this advertiser runs no ads here'. |
| `maxResults` | no | integer | `200` | Cap on creatives returned PER advertiser, not per run. Large advertisers run enormous libraries — Google stated 8,000-9,000 creatives for one of them in the US alone on 2026-09-07 — so this is your spend control. Every row carries Google's own stated range for that advertiser and region, so a cap can never read as completeness. |
| `proxyConfiguration` | no | object | `{"useApifyProxy": true}` | ON by default, and it should stay on. Google throttles the Transparency Center per IP and answers a spent one with a redirect to its /sorry/ page rather than an error — measured from an ordinary connection during development. A shared cloud IP is often already spent, so running without a proxy is the most likely reason for a run that returns nothing. |

### Pricing (Pay Per Event)

You pay per result (`creative`) — **no charge for empty runs**. Example: **1000 ad creatives** at *$0.0015/result* ≈ **$1.50**.

The default input returns up to 200 creatives per advertiser, so a two-advertiser default run is 400 creatives and costs $0.60. You are charged per creative returned, not per advertiser submitted. Apify platform usage (compute) is billed separately per your plan.

### Use cases

- Competitor creative audit — pull every ad a rival is running in your market with the dates each has been live.
- Creative longevity — sort by first shown against last shown to see which creatives a competitor keeps running and which they dropped in a week.
- Cross-market comparison — run the same advertiser across several country libraries to see where their volume actually is.
- New-creative alerts — run on a schedule and watch for creative ids that were not in the previous run.

### Why this actor

- Returns the creative preview URL, the creative image URL, and both the first-shown and last-shown dates, with days\_running worked out for you.
- Carries Google's own stated library range for the advertiser and region on every row, so 'is this all of them?' is answerable rather than assumed.
- Catches Google's per-IP throttle by checking where the response landed, and says so in a free unbilled row instead of returning an ordinary-looking empty result.
- Validates the region before asking, because Google answers an unknown one with an empty list that reads as 'no ads here'.
- Says on every row how the advertiser was resolved — domain, advertiser id, or name search — so a wrong-looking advertiser can be traced.
- $1.50 per 1,000 creatives, against $0.12 to $200 across this category on the Store (2026-09-07).

### Limitations & updates

Reads what Google's Ads Transparency Center publishes and nothing beyond it. There are no impressions, no spend, no click and no audience figures in this source, for any advertiser — if you need those for Google ads they do not exist here. Advertiser library sizes are stated by Google only as bucketed ranges, never as exact counts. Political ads carry extra disclosures on Google's own site that this Actor does not currently return. 48 country libraries are supported and were each verified on 2026-09-07; other regions may work with a numeric Google region code but are not verified. Creative image URLs are Google's own hosted assets and serve to browser-style clients only. One undocumented integer on each creative row is deliberately not returned rather than guessed at. Google throttles this endpoint per IP, so the proxy is on by default and a throttled run returns a free marker row rather than an empty result. Data is whatever the Transparency Center shows at the moment of the run.

### FAQ

**Does this give me impressions, spend or reach?**

No, and neither can anything else reading this source: Google publishes no impression count, no spend figure and no audience reach anywhere in the Ads Transparency Center for a normal commercial ad. What it does publish is which creatives ran, in which country, and the dates each was first and last shown — and the gap between those two dates is the most useful signal available here, because an advertiser who keeps a creative live for months is telling you it works. The creative row does carry one undocumented integer that this Actor deliberately does not return; it is between 5 and 1,737 across the ads sampled, it matches neither the image area nor its dimensions, and shipping it labelled 'impressions' would be inventing a number.

**Why is the proxy on by default when other Actors here have it off?**

Because Google throttles this endpoint per IP, and it does so invisibly. A spent IP does not get an error or a 429 — the request is redirected to google.com/sorry/, so a scraper that only checks the response body sees a perfectly valid page and reports an empty result. That happened during development from an ordinary connection and persisted for several minutes. Shared cloud IPs are frequently already spent by someone else, so an unproxied run is the likeliest reason for a run that comes back with nothing. This Actor also checks where each response landed rather than only what it contains, so when it is throttled it says so in a free row instead of pretending the advertiser has no ads.

**Why does a row say 8,000 to 9,000 instead of a number?**

Because that is what Google says. It publishes an advertiser's library size as a bucketed range and never as an exact count, so both bounds are carried on every row rather than one of them being presented as a total. It is the only honest way to answer 'did I get all of them?': compare your row count against the range. It also varies enormously by country — on 2026-09-07 the same advertiser was stated at 8,000-9,000 creatives in the US, 20,000-30,000 in Germany and 400-500 in Japan.

**Which countries can I search?**

48 were verified one by one against the live service on 2026-09-07 and every one returned creatives: the US, Canada, Mexico, Brazil, Argentina, Chile, Colombia, the UK, Ireland, Germany, France, Italy, Spain, Portugal, the Netherlands, Belgium, Switzerland, Austria, Sweden, Norway, Denmark, Finland, Poland, Czechia, Greece, Hungary, Romania, Ukraine, Turkey, Israel, the UAE, Saudi Arabia, Egypt, South Africa, Nigeria, India, Japan, South Korea, Singapore, Malaysia, Thailand, Indonesia, the Philippines, Vietnam, Taiwan, Hong Kong, Australia and New Zealand. Give a standard two-letter country code. Anything unrecognised is refused with a free row naming the valid ones, rather than being sent to Google and coming back as a misleading empty list.

**Can I search by company name instead of by domain?**

Yes. A value that is not a domain and not an advertiser id is put through Google's own advertiser search, and the best match is used — the row then says matched\_by: name\_search so you know a resolution step happened. Names are ambiguous, though: searching 'Nike' returns NIKE SRL in Italy and several unrelated advertisers alongside Nike, Inc., and the run log lists how many candidates were considered. Where you know the domain or the advertiser id, use it — those are exact.

**Why can't I fetch the creative image with curl?**

Google serves those images to browsers and refuses plain command-line clients — a request with default headers hangs rather than returning an error. The URL is real and is taken verbatim from Google's own response, not assembled by this Actor; one checked on 2026-09-07 rendered as a 696×458 image in a browser. Use browser-style headers if you are downloading them programmatically, or just open one in a tab. Note also that display\_width and display\_height are the size the ad is shown at, not the asset's own resolution — that same creative is served as 348×489 in the ad slot.

**What happens if Google changes the RPC?**

You get a free, unbilled row with notice\_type 'source\_changed'. This matters more here than for a normal scraper: the endpoint identifies its fields by NUMBER rather than by name, so if Google renumbers them the response is still valid JSON of the right shape and every value silently lands in the wrong column. The Actor checks that the rows it got still carry a creative id where one belongs, and stops rather than emitting a dataset that looks fine and is wrong.

**How many ads can one run return?**

As many as the advertiser has, in pages of 40, with the cap applied per advertiser rather than per run — so a batch of ten cannot be used up by the first large one. Libraries get big: Google stated 8,000-9,000 creatives for a single advertiser in the US alone. Set the cap to control spend, and read it against the advertiser\_creatives range on the rows to see what fraction you covered.

**Is this the same data as the Transparency Center website?**

Yes — it is the same service, read through the same internal endpoint the website's own front end calls, so what you get is what the site would show for that advertiser and country. Nothing here is inferred, modelled or enriched from anywhere else, and the source page for every single row is on the row so you can open it and check.

**Do I need a Google account or an Ads account?**

No. The Transparency Center is public and this Actor sends no credentials of any kind. It reads only what Google publishes about ads that ran; there is no account data, no audience data and no personal information involved.

### Which actor to choose

Other Actors in the portfolio that pair with this one:

- **Shopify Store Intel** — The advertisers you are tracking run Shopify storefronts and you want the store side too.
- **Business Email & Phone Contact Details Scraper** — You have the advertiser domains and now need contact details for outreach.

### Guides & use cases

Written up on **[scrapebench.dev](https://scrapebench.dev)** — the bench that runs and verifies this actor against the live source every night:

- **How-to:** [How to run Google Ads Transparency Center & Competitor Ad Scraper](https://scrapebench.dev/guides/how-to-google-ads-transparency-center-scraper/)
- **Use case:** [How Long Has That Ad Been Running? Creative Longevity from the Transparency Center](https://scrapebench.dev/use-cases/google-ads-creative-longevity/)

More actors, coverage and nightly verification results: **[scrapebench.dev](https://scrapebench.dev)**

### Works with AI assistants (MCP)

Callable as an MCP tool, so Claude, Cursor, VS Code Copilot and other MCP clients can run it directly. Grab the config from the [MCP tab](https://apify.com/scrapebench/google-ads-transparency-center-scraper/api/mcp) on this page — Apify hosts the server and keeps that snippet current, and OAuth signs you in on first connect, so no API token goes in your config file.

Then just ask:

> "Show me every ad nike.com is running in the US right now, with the dates each was first and last shown."

An advertiser can be a domain, a Google advertiser id (AR…), or a company name — a name goes through Google's own advertiser search first, and the row says so in matched\_by. Region is a two-letter country code; each country is a separate ad library with very different volumes. Runs started this way bill exactly like any other run.

# Actor input Schema

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

An advertiser domain (nike.com), a Google advertiser id (AR16735076323512287233), or a company name to search for. Which of the three you gave is recorded on every row in 'matched\_by'. Kept forever alongside 'Advertisers' — saved tasks and API callers send it.

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

Look up many advertisers in ONE run instead of starting a run per advertiser. Takes the same values as 'Advertiser'. Combined with it if you fill both, and duplicates are removed. The result cap applies PER advertiser, so one large advertiser cannot use up the run and leave the others looking like they run no ads.

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

The country whose ad library to read, as an ISO country code — US, GB, DE, FR, JP, BR and so on. This is not cosmetic: Google keeps a separate library per country and the same advertiser has very different volumes in each. An unrecognised code is refused up front, because Google answers one with an empty list rather than an error, which would read as 'this advertiser runs no ads here'.

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

Cap on creatives returned PER advertiser, not per run. Large advertisers run enormous libraries — Google stated 8,000-9,000 creatives for one of them in the US alone on 2026-09-07 — so this is your spend control. Every row carries Google's own stated range for that advertiser and region, so a cap can never read as completeness.

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

ON by default, and it should stay on. Google throttles the Transparency Center per IP and answers a spent one with a redirect to its /sorry/ page rather than an error — measured from an ordinary connection during development. A shared cloud IP is often already spent, so running without a proxy is the most likely reason for a run that returns nothing.

## Actor input object example

```json
{
  "query": "nike.com",
  "queries": [
    "nike.com",
    "geico.com"
  ],
  "region": "US",
  "maxResults": 200,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Every creative found for each advertiser: format, image or preview URL, first and last shown.

# 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",
    "queries": [
        "nike.com",
        "geico.com"
    ],
    "region": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapebench/google-ads-transparency-center-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",
    "queries": [
        "nike.com",
        "geico.com",
    ],
    "region": "US",
}

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapebench/google-ads-transparency-center-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/UsUQoRRSTaNuEE0os/builds/w3hbRdLFg4f4NJqAH/openapi.json
