# Google Ads Transparency Scraper - Competitor Ads Library (`flash_scraper/google-ads-transparency-scraper`) Actor

See competitor Google Ads: every creative Google's own Ads Transparency Center lists for an advertiser - format, preview, first and last shown dates, days active and the countries it ran in. Only-new-creatives alerts turn a schedule into a competitor-ad feed.

- **URL**: https://apify.com/flash\_scraper/google-ads-transparency-scraper.md
- **Developed by:** [Flash Scrape](https://apify.com/flash_scraper) (community)
- **Categories:** Marketing, Automation
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 job 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?

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

## Competitor Ads Scraper - Google Ads Transparency Library

See a **competitor's Google Ads** — every creative Google's own Ads Transparency Center lists for an advertiser, with first-shown and last-shown dates and days active.

See **every ad a competitor runs on Google** — Search, Display and YouTube — straight from Google's own [Ads Transparency Center](https://adstransparency.google.com), the public accountability tool where every advertiser's creatives are listed. Give it a domain (`nike.com`), a brand term (`nike` — auto-resolved to its top advertiser domain) or an advertiser ID (`AR…`), and get one clean row per ad: advertiser, format, preview, **first-shown and last-shown dates**, and days active.

No API key, no login, no browser — the actor calls the same keyless endpoint the Transparency Center's own page uses, so the data is exactly what Google publishes, at the moment you run it.

> Watching competitors on Facebook/Meta instead? The Ad Library niche is well covered on the Store — this actor is for the Google side, where fresh, honest tooling is scarce.

### What you get

One row per ad creative. Measured on `nike.com` (100 rows, 2026-08-15):

| column | fill | notes |
|---|---|---|
| `advertiser_id`, `advertiser_name` | 100% | e.g. `AR18378488041124659201`, "Nike Retail BV" — ads run by resellers/agencies for the same domain show up under their own advertiser |
| `domain` | 100% | the advertiser's verified domain |
| `creative_id` | 100% | Google's `CR…` id — stable, deduplicated |
| `format` | 100% | `text` / `image` / `video` |
| `preview_url` | 100% | archived screenshot for text ads, render URL for display/video |
| `first_shown`, `last_shown` | 100% | real dates — the sample's `last_shown` was the run date itself |
| `days_active` | 100% | how long the creative has been running — long-running ads are the ones making money |
| `transparency_url` | 100% | the ad's own public page on Google's Transparency Center |
| `query` | 100% | which of your targets produced the row |
| `regions`, `region_stats`, `impressions_min`/`max` | with `includeDetails` | countries where the ad ran, plus Google's published EU impression range **per country** in `region_stats` (e.g. France: 50,000–60,000). The top-level `impressions_min`/`impressions_max` are the **SUM** of those per-country bounds across every country the ad ran in — not a single published range; `max` is `null` unless every listed country publishes an upper bound. `null` where Google publishes no record |

Example row (real):

```json
{
  "advertiser_id": "AR18378488041124659201",
  "advertiser_name": "Nike Retail BV",
  "domain": "nike.com",
  "creative_id": "CR16074515335549550593",
  "format": "image",
  "preview_url": "https://tpc.googlesyndication.com/archive/simgad/3436141099225117854",
  "first_shown": "2025-07-01",
  "last_shown": "2026-08-15",
  "days_active": 410,
  "transparency_url": "https://adstransparency.google.com/advertiser/AR18378488041124659201/creative/CR16074515335549550593",
  "query": "nike.com",
  "source": "google-ads-transparency"
}
```

### Quick start

Press **Start** with the form as it comes and you get up to 50 of Nike's ads shown in the last 30 days (those are the Console prefills). An API call with a bare `{}` uses the field defaults instead: no recency filter, up to 100 ads. Then point it at your own market:

```json
{
  "queries": ["asics.com", "on-running.com", "hoka.com"],
  "activeWithinDays": 30,
  "maxItems": 300
}
```

`activeWithinDays: 30` is the filter that matters: it keeps only ads **last shown in the past month** — a competitor's *currently running* campaigns instead of a year of history. Long `days_active` + recent `last_shown` = the proven winners they keep paying for.

### Input reference

| field | default (Console form starts at) | what it does |
|---|---|---|
| `queries` | 1-domain sample | Domains (exact) or brand terms (auto-resolved via Google's own suggestions). Your list replaces the sample entirely. One exception: a list that byte-equals the built-in sample (`["nike.com"]`) **alongside** `advertiserIds` is read as the injected schema default and dropped — the run says so in its status message. Re-enter it as `www.nike.com` or add a second entry to keep it. |
| `advertiserIds` | `[]` | `AR…` ids — grab them from any run's rows to watch specific advertisers precisely. A full Transparency Center URL works too (the `AR…` id is extracted). Anything without an `AR…` id is rejected, named in the status message, and **never** silently replaced by the sample: if none of your ids is usable the run delivers 0 rows and charges nothing. |
| `format` | `any` | Keep only `text`, `image` or `video` ads. Applied before billing. |
| `activeWithinDays` | `0` = off (Console form: `30`) | Keep only ads last shown within N days. Undated ads are kept, never guessed away. |
| `onlyNewCreatives` | `false` | Competitor-ad alert mode — see below. |
| `maxItems` | `100` (Console form: `50`) | Total cap across all targets, split round-robin. Your cost ceiling. |
| `region` | `""` (worldwide) | Two-letter ISO 3166-1 country code — keeps only ads with a published record for that country, from each ad's own detail record. A code that is not a real ISO country stops the run with 0 rows and no charge rather than quietly delivering a worldwide pull. See the region limits below. |
| `includeDetails` | `false` | Fills `regions`, `region_stats`, `impressions_min`/`impressions_max` — countries where each ad ran + Google's published EU impression ranges per country in `region_stats`; the top-level `impressions_min`/`impressions_max` are cross-country **totals** of those bounds. One paced request per delivered ad. |
| `proxy` | Apify Proxy | Google rate-limits this endpoint per IP (measured: ~15 rapid requests → 429). The default automatic proxy rotates to a fresh session on every rate-limit; your own proxy URLs are honored. |

Every filter runs **before billing** — a filtered ad is never charged.

### 🔔 Competitor-ad alerts: `onlyNewCreatives` + a schedule

Schedule the actor (daily or weekly) with `onlyNewCreatives: true` and the same targets:

- the **first run is the baseline** — it delivers everything and says so;
- every later run delivers **only creatives not seen before** — i.e. the competitor's *new* ads;
- a quiet run delivers nothing, **bills nothing**, and says "Nothing new" plainly.

A competitor launching new creatives is a competitor testing a new offer, product or angle — this is the cheapest way to hear about it first. Watch memory lives in a named key-value store (`google-ads-monitor`) in your own account. A watch is identified by its targets **and** its filters (`format`, `activeWithinDays`, `region`), so two schedules on the same domain with different filters keep separate memories. Every creative the watch still sees has its timestamp refreshed, so the automatic prune (90 days unseen / 50k keys) only forgets ads that have actually stopped — a long-running ad never resurfaces as a false "new" alert.

### Use it from your code

```javascript
// JavaScript — apify-client
const { ApifyClient } = require('apify-client');
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('flash_scraper/google-ads-transparency-scraper').call({
  queries: ['nike.com'], activeWithinDays: 30, maxItems: 200,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
## Python — apify-client
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("flash_scraper/google-ads-transparency-scraper").call(run_input={
    "queries": ["nike.com"], "activeWithinDays": 30, "maxItems": 200})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

```bash
## curl — run synchronously and get the dataset back
curl -X POST "https://api.apify.com/v2/acts/flash_scraper~google-ads-transparency-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"queries": ["nike.com"], "maxItems": 50}'
```

### n8n / Make / Zapier

All three have a native Apify node/module: pick **Run actor** → `flash_scraper/google-ads-transparency-scraper`, pass the JSON input above, and read the default dataset when the run finishes. `onlyNewCreatives` + a schedule trigger pipes every new competitor ad into Slack, a sheet or your CRM — and quiet days cost nothing.

### Honest limits

- **Google's data, Google's rules.** The Transparency Center keeps roughly the last year of ads and publishes no spend and no exact impression counts — nobody scraping it can get those. What Google does publish is impression **ranges** for ads shown in the EU (a DSA requirement) — `includeDetails` ships exactly those, as ranges, and `null` everywhere Google publishes nothing.
- **Region filtering is record-based.** Google's search endpoint has no region parameter, so `region: "FR"` checks each candidate ad's own per-country record (one paced request per ad) and keeps the ones that ran there — slower than a native filter, but exact and honest. Because every candidate costs one paced request, the region check inspects at most **1000 candidates per run** (≈20 minutes at that ceiling) and stops as soon as `maxItems` ads have matched — so any `maxItems` the schema accepts is reachable, and the run never pays for candidates it cannot deliver. The status message says exactly how many were checked; lookups that were rate-limited are reported as *not checked* rather than counted as "Google publishes nothing here".
- **Ad text for text ads** ships as an archived screenshot URL (`preview_url`), not extracted copy — that is how Google serves them.
- Brand-term resolution is best-effort (it uses Google's own suggestion service); the exact domain is always the reliable input.
- The endpoint rate-limits per IP; the default proxy handles it. Very large pulls simply take longer — pacing is deliberate.

### Related actors

- [Multi Job Board Scraper — LinkedIn, Indeed, Glassdoor + 9 more](https://apify.com/flash_scraper/multi-jobboard-scraper)
- [Workday Jobs Scraper — company careers, no API key](https://apify.com/flash_scraper/workday-jobs-scraper)
- [Local Business Leads — any category, any city, MX-verified emails](https://apify.com/flash_scraper/local-business-leads)

***

### More from Flash Scrape

Same publisher, same rules: no API keys, pay per row, filters run before billing.

- [Multi Job Board Scraper](https://apify.com/flash_scraper/multi-jobboard-scraper) - LinkedIn, Indeed, Glassdoor and 9 more boards in one deduplicated table
- [Local Business Leads Scraper](https://apify.com/flash_scraper/local-business-leads) - local businesses in any category and city with MX-verified emails, phones and socials
- [Remote Jobs Aggregator](https://apify.com/flash_scraper/remote-job-aggregator) - 10 remote job boards in one feed, only-new-jobs monitoring
- [Creator Leads Scraper](https://apify.com/flash_scraper/creator-leads-scraper) - YouTube, TikTok and Instagram creator emails in one run

# Actor input Schema

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

One per line: a domain like 'nike.com' (exact) or a brand term like 'nike' (auto-resolved to the top matching advertiser domain). Supplying your own list replaces the sample entirely. Note: a list that byte-equals the sample (\['nike.com']) sent ALONGSIDE advertiserIds is read as the injected schema default and dropped - the run's status message says so; re-enter it as 'www.nike.com' or add a second entry to keep it.

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

Google advertiser IDs starting with 'AR' (every delivered row carries its advertiser\_id, so grab them from a first run and watch specific advertisers precisely). A pasted Transparency Center URL works too - the AR... id is extracted. Entries with no AR... id are rejected and named in the status message; if none of the ids you give is usable the run delivers 0 rows and charges nothing, it never falls back to the sample domain.

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

Only keep ads of one format. 'any' keeps everything.

## `activeWithinDays` (type: `integer`):

Only keep ads LAST SHOWN within the last N days - the honest way to see a competitor's currently running campaigns instead of a year of history. 0 = no filter (the Console form starts at 30). Ads whose last-shown date Google doesn't state are kept, never guessed away.

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

Two-letter ISO 3166-1 country code (US, FR, GB, DE, MA, TW...). Keeps only ads with a published per-country record for that country - checked against each ad's own detail record (one paced request per checked ad, so filtered runs take longer). The check inspects at most 1000 candidates per run and stops as soon as maxItems ads have matched. A code that is not a real ISO country stops the run with 0 rows and no charge instead of quietly delivering a worldwide pull. Empty = worldwide.

## `onlyNewCreatives` (type: `boolean`):

The first run is the baseline (delivers everything and says so). Every later run with the same targets + filters (format, activeWithinDays, region - each watch keeps its own memory) delivers ONLY creatives not seen before; a run where nothing is new delivers nothing, bills nothing, and says 'Nothing new' plainly. Creatives that are still running have their timestamp refreshed every run, so a long-lived ad is never forgotten and re-alerted. Memory lives in a named key-value store ('google-ads-monitor') in your own account.

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

Total cap across ALL targets in the run, split round-robin so one giant advertiser cannot eat the whole budget. Your cost ceiling. Default 100; the Console form starts at 50.

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

Fetch each delivered ad's detail record and fill the regions, region\_stats, impressions\_min and impressions\_max columns - the countries where the ad ran and Google's published EU impression ranges. region\_stats carries Google's per-country published range; the top-level impressions\_min/impressions\_max are the SUM of those per-country bounds across all countries the ad ran in, not a single published range. One extra paced request per delivered ad. (Automatically on when Region is set.)

## `proxy` (type: `object`):

Google rate-limits this endpoint per IP (measured: ~15 rapid requests earn a 429), so the default Apify Proxy with automatic fresh-session rotation is recommended. Your own proxy URLs are honored.

## Actor input object example

```json
{
  "queries": [
    "nike.com"
  ],
  "advertiserIds": [],
  "format": "any",
  "activeWithinDays": 30,
  "region": "",
  "onlyNewCreatives": false,
  "maxItems": 50,
  "includeDetails": false,
  "proxy": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (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 = {
    "queries": [
        "nike.com"
    ],
    "activeWithinDays": 30,
    "maxItems": 50,
    "proxy": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("flash_scraper/google-ads-transparency-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "queries": ["nike.com"],
    "activeWithinDays": 30,
    "maxItems": 50,
    "proxy": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("flash_scraper/google-ads-transparency-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "queries": [
    "nike.com"
  ],
  "activeWithinDays": 30,
  "maxItems": 50,
  "proxy": {
    "useApifyProxy": true
  }
}' |
apify call flash_scraper/google-ads-transparency-scraper --silent --output-dataset

```

## MCP server setup

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