# Google Ads Transparency Scraper & API: Competitor Ads (`friendlyapi/google-ads-transparency-scraper`) Actor

Competitor domains in, Google ad intelligence out: every creative with first-shown and last-shown dates, a report on what they just launched and what has run long enough to be working, which affiliates bid on their domain, and a feed of ads launched, stopped or resumed since your last run.

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

## Pricing

from $0.60 / 1,000 ad creatives

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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 & API: competitor ads, and what changed

Give it competitor domains. It reads **Google's Ads Transparency Center**, the
public repository Google is required to publish, and returns their live ad
creatives with **first-shown and last-shown dates**, a report on what they
just launched and what has been running long enough to obviously be working,
**who else is bidding on their domain**, and a change feed of ads launched,
stopped and resumed since your last run.

No login, no cookies, no personal data. The subjects are advertisers, which
are businesses, and ad creative, which is public commercial communication.

### Why this instead of the other ad scrapers

| | Ad scrapers | This actor |
|---|---|---|
| **What you hand it** | A Transparency Center advertiser URL you look up by hand first | **A domain.** `nike.com` |
| Output | A list of ads | An answer: launch velocity, what's working, what stopped |
| Timing | Whatever is live right now | First-shown and last-shown per creative, so **days running** is exact |
| Competitive change | Diff it yourself | `watch_changes` emits launched / stopped / resumed events |
| Affiliates on your brand | Invisible | `who_advertises` names every advertiser pointing ads at a domain |
| Rate limits | Your problem | Rotating proxy session per request, handled |

**Start with the first row, because it is the one that costs you an
afternoon.** The other Transparency Center scrapers take a URL of the form
`adstransparency.google.com/advertiser/AR<id>/...`, which means that before you
can run anything you have to open Google yourself, search the brand, work out
which of the results is the right company, and copy the address bar. Do that
for a list of fifty competitors and you have spent a morning on data entry.

It is worse than tedious, because **a domain is not an advertiser.** nike.com
carries eight separate advertiser accounts. notion.so's only advertiser is a
company in Malaysia. When several accounts share a name there is no correct
one to pick, which is why the manual instructions for those tools tell you to
give up on that case. This actor resolves every advertiser behind a domain and
returns all of them, and that ambiguity is not an edge case to route around,
it *is* the finding: it is how you catch affiliates bidding on your brand.

**The insight this is built on:** a marketer's daily question is not "show me
Nike's ads". It is *"who started spending on something new this week"*. That
is a change feed, and nobody in this category ships one.

### The four modes

#### 1. `ad_report` (recommended): a competitor's whole posture in one object

```json
{"mode": "ad_report", "domains": ["hubspot.com", "nike.com"]}
```

Returns, per domain:

```json
{
  "advertiser_domain": "hubspot.com",
  "advertisers_on_this_domain": 3,
  "largest_advertiser_name": "Hubspot, Inc.",
  "largest_advertiser_share": 0.88,
  "creatives_total": 51,
  "creatives_running": 19,
  "creatives_stopped": 32,
  "by_format": {"image": 51},
  "launched_last_7d": 0,
  "launched_last_30d": 0,
  "launched_prior_30d": 0,
  "newest_creative_at": "2026-03-18T23:45:39Z",
  "median_lifespan_days_retired": 230,
  "median_age_days_running": 411,
  "max_region_count": 1509,
  "longest_running": ["...top 5 with links..."],
  "most_recent_launches": ["...top 5 with links..."],
  "signals": [
    "still serving ads but has not launched a new creative in 90 days",
    "18 creatives have been running for 6 months or more, which usually means they work"
  ]
}
```

Those `signals` are the product. They are plain-English readings of the
numbers directly above them, so you can always check the working.

#### 2. `watch_changes`: only what moved

```json
{"mode": "watch_changes", "watch_id": "my-competitors",
 "domains": ["hubspot.com", "salesforce.com", "pipedrive.com"]}
```

First run sets a baseline silently. Every later run emits only events:

```json
{"event": "launched", "advertiser": "pipedrive.com", "creative_id": "CR07...",
 "first_shown_at": "2026-08-09T04:12:00Z", "format": "image",
 "transparency_url": "https://adstransparency.google.com/advertiser/AR.../creative/CR..."}
{"event": "stopped",  "advertiser": "hubspot.com", "days_running": 412}
{"event": "resumed",  "advertiser": "salesforce.com", "creative_id": "CR11..."}
```

Schedule it daily and you have a competitive alerting system.

**Two honesty rules are built in.** If Google rate-limits or errors during a
run, that advertiser emits **no events** and keeps its baseline, and the
failure is named in `fetch_errors`; an outage will never be reported to you
as "they killed all their ads". And because `last_shown_at` advances every
day for every live ad, it is deliberately excluded from change detection, so
an unchanged account is silent instead of alerting you every morning.

#### 3. `who_advertises`: who is bidding on this domain

```json
{"mode": "who_advertises", "domains": ["nike.com"]}
```

A domain is **not** an advertiser. nike.com's 130 creatives come from **8
different advertisers**: Nike Retail BV holds 63%, and the rest are Nike
Japan, media agencies and affiliates. This mode names all of them, which is
how brand-bidding and affiliate abuse get caught.

#### 4. `fetch_ads`: every creative as rows

```json
{"mode": "fetch_ads", "domains": ["shopify.com"], "running_only": true,
 "max_ads_per_domain": 200}
```

One row per creative with the image or preview URL, dimensions, format,
first/last shown, days running, region count and a link to the ad in Google's
own Transparency Center.

### What we will tell you that others will not

- **A domain is not an advertiser.** Every response reports how many distinct
  advertisers run ads to the queried domain and what share the biggest holds.
  Query `notion.so` and you get a Malaysian company that is not Notion; we
  say so instead of labelling it "Notion's advertising".
- **We never claim spend.** Google's library publishes when a creative was
  first and last served, not what it cost. Every number here is derived from
  those two dates, and the `basis` field on every report says so.
- **Absent means absent.** A domain with no ads returns "no ads in the Ads
  Transparency Center", not an empty object dressed up as a result.
- **Failures are named.** Rate limits and errors appear per domain, so "they
  run no ads" and "we could not check" stay different answers.

### Coverage and limits, stated plainly

- **Source:** Google Ads Transparency Center. This covers Google Search,
  Display, YouTube and Shopping ads. It does not cover Meta, TikTok or
  LinkedIn.
- **What the library publishes:** advertiser identity, creative, format,
  first and last shown dates, and a region count. Not spend, not impressions,
  not targeting.
- **Region:** the default returns worldwide results. The `region_code` field
  is an advanced passthrough; we deliberately do not publish a guessed
  code-to-country mapping, because a wrong one would silently narrow your
  results without telling you.
- **Rate limits are real.** Google limits hard by IP, so this actor rotates a
  proxy session per request. Leave the proxy setting on.
- **Domains per run:** 200 for reports and ads, 500 for watching.

### Pricing

Pay per event, no subscription:

| Event | When |
|---|---|
| `ad_report` | Per advertiser report produced |
| `ad_creative` | Per creative returned in `fetch_ads`. **No rounding.** $0.90 per 1,000 ads on the free tier, falling to $0.60 |
| `advertiser_resolved` | Per domain resolved in `who_advertises` (misses are free) |
| `advertiser_watched` | Per advertiser checked in a `watch_changes` run |
| `ad_event` | Per launched / stopped / resumed event emitted |

A daily watch on 20 competitors is 20 `advertiser_watched` per day plus the
handful of real events. Dedicated ad-intelligence suites charge $49 to $249 a
month flat.

Not affiliated with or endorsed by Google. Reads only the public,
unauthenticated Ads Transparency Center, which Google publishes for exactly
this purpose. All trademarks belong to their owners.

# Actor input Schema

## `mode` (type: `string`):

ad\_report = the answer: how much a competitor is advertising, what they just launched, what works; fetch\_ads = every creative as rows; who\_advertises = who is running ads to a domain (catches affiliates bidding on your brand); watch\_changes = only what launched, stopped or resumed since the last run

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

One entry per competitor: the destination domain you want to see advertising for, e.g. "hubspot.com". Full URLs are fine. Up to 200 for reports and ads, 500 for watching. Note that a domain is not an advertiser: several parties often run ads to the same domain, and the output names all of them. Very large advertisers (thousands of creatives) take longer, so start with a couple.

## `watch_id` (type: `string`):

Stable name for this competitor set, e.g. 'my-competitors'. It names the stored snapshot the next run diffs against. The first run with a new watch\_id sets the baseline and emits no events; schedule the actor and later runs emit only real changes.

## `max_ads_per_domain` (type: `integer`):

Cap per domain in fetch\_ads mode. Large advertisers list hundreds.

## `running_only` (type: `boolean`):

Skip creatives that Google has not served in the last 2 days. Useful for 'what are they running right now'; leave off to see the full history including what they retired.

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

Google's Ads Transparency Center rate-limits hard by IP address and does not forgive quickly, so this actor rotates a fresh proxy session per request. Leave the default on. Running without a proxy works for a single small domain and will be throttled beyond that.

## `proxy_country` (type: `string`):

Two-letter country code for the proxy exit, default US. This is not cosmetic: Google localises the Transparency Center by caller country, so the region counts and the creative set depend on where you appear to be. Keep it CONSTANT between watch runs, or the change feed will report differences that are only your own vantage point moving.

## `region_code` (type: `integer`):

Google's internal region selector for the Transparency Center. The default (2586) is the documented value and returns worldwide results. Only change this if you know the code you want; we do not publish a guessed mapping, because a wrong one would silently narrow your results.

## Actor input object example

```json
{
  "mode": "ad_report",
  "domains": [
    "hubspot.com",
    "nike.com"
  ],
  "watch_id": "my-competitors",
  "max_ads_per_domain": 200,
  "running_only": false,
  "proxy": {
    "useApifyProxy": true
  },
  "proxy_country": "US",
  "region_code": 2586
}
```

# Actor output Schema

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

Ads, reports, or change events depending on mode.

## `csv` (type: `string`):

For spreadsheets and BI tools.

## `runInConsole` (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 = {
    "mode": "ad_report",
    "domains": [
        "hubspot.com",
        "nike.com"
    ],
    "watch_id": "my-competitors",
    "max_ads_per_domain": 200,
    "proxy": {
        "useApifyProxy": true
    },
    "proxy_country": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("friendlyapi/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 = {
    "mode": "ad_report",
    "domains": [
        "hubspot.com",
        "nike.com",
    ],
    "watch_id": "my-competitors",
    "max_ads_per_domain": 200,
    "proxy": { "useApifyProxy": True },
    "proxy_country": "US",
}

# Run the Actor and wait for it to finish
run = client.actor("friendlyapi/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 '{
  "mode": "ad_report",
  "domains": [
    "hubspot.com",
    "nike.com"
  ],
  "watch_id": "my-competitors",
  "max_ads_per_domain": 200,
  "proxy": {
    "useApifyProxy": true
  },
  "proxy_country": "US"
}' |
apify call friendlyapi/google-ads-transparency-scraper --silent --output-dataset

```

## MCP server setup

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