# Google Ads Transparency Scraper (`adatlas/google-ads-transparency-scraper`) Actor

Search Google's Ads Transparency Center by brand, domain or advertiser ID - no advertiser URL needed. Get ad copy, landing page URLs, and how many days each ad actually served. Political ads add the disclosed spend range, impressions and targeting.

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

## Pricing

from $0.90 / 1,000 scraped ads

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/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 — spy on competitor ads, creatives and spend

Scrape the [Google Ads Transparency Center](https://adstransparency.google.com) by **brand name
or domain** — no advertiser URL to hunt down first, no login, no Google API key — and export
every ad with its copy, its landing page, how long it actually ran, and, for political ads, the
disclosed spend range. Results download as **JSON, CSV or Excel**, or come straight back over
the Apify API.

Ad-spy suites charge $100–500 a month for a view of this data. This reads it from Google's own
public archive and bills per ad, so a one-off competitor teardown costs cents.

### What you can do with it

- **Competitor teardown** — every ad a rival is running, with copy and landing pages
- **Find the ads that actually work** — `days_served` and `serving_density` separate evergreen
  winners from things a brand tested for a week (no other Transparency Center actor exposes this)
- **Political and issue-ad monitoring** — disclosed **spend ranges**, impressions and targeting
- **Landing-page and offer tracking** — where the money is actually sending traffic
- **Creative libraries** — image and video creative URLs for swipe files

### What you get

Every row carries:

- `advertiser_id`, `advertiser_name`, `creative_id`, `ad_url`
- `format` — `image`, `text` or `video`
- `first_shown`, `last_shown`
- **`days_served`** — how many days the ad actually ran. Google's own page words this as
  "Ran for: 280 days, nonconsecutively", and it is often far below the calendar span
- **`span_days`** and **`serving_density`** — days served divided by the span. `0.99` is an
  always-on evergreen; `0.15` is a short seasonal burst
- `image_url` for image ads

With **Extract ad copy** on (the default), ads that expose text also carry:

- `headline`, `body`, `visible` (the display URL) and **`destination`** — the real landing page
- `copy_source` and `enrich_status`, so you can see where each row's copy came from and how many
  rows got any

With **Fetch full ad detail** on:

- `variation_count`, and a per-country `regions[]` breakdown with impression ranges and dates
- For political and issue ads: **`spend_min` / `spend_max`** (a disclosed range, in whole
  currency units), `impressions_in_region`, `impressions_all_regions`, and `targeting`

### How much copy actually comes back

Being straight about this, because it varies by ad type and it is the reason to choose this
actor over a cheaper one.

Only some ads have a fetchable preview at all — image ads served as a bare creative do not.
So the number that decides your result is **how many of an advertiser's ads are text or search
ads**, and that varies enormously by advertiser. Measured on 100-ad runs:

| Advertiser | Ads | With a preview | Landing page | Headline / body |
|---|---|---|---|---|
| HubSpot | 100 | 47 | 45 | 1 |
| Squarespace | 100 | 6 | 6 | 6 |
| Nike | 100 | 5 | 1 | 1 |

The rule of thumb: **once an ad has a preview, a landing page almost always comes with it;
headline and body come back for text and search ads, not for display ads.** Nike is the
worst case worth knowing about — a brand that advertises almost entirely with images will
return long-running dates and creative IDs, but very little text.

Copy is billed only when a headline or body actually arrives, so an image-heavy run does not
pay for empty columns.

### Why `days_served` is the interesting number

An ad that served 1,445 days out of a possible 1,449 is an ad that makes money — nobody keeps
paying for four years to run a loser. Set `minDaysServed: 90`, or `minServingDensity: 0.8`, and
the output stops being "every ad this brand ever tested" and becomes "the ones that worked".

### Input

```json
{
  "queries": ["nike.com", "shopify", "AR16735076323512287233"],
  "regions": ["US", "CA"],
  "maxAdsPerAdvertiser": 100,
  "maxAdvertisersPerQuery": 3,
  "includeAdCopy": true,
  "includeDetail": false,
  "minDaysServed": 90
}
```

`queries` takes a brand name, a domain, a Google advertiser ID, or a pasted Ads Transparency
Center URL — so if you already have advertiser URLs from another tool, they work unchanged.

`regions` takes two-letter country codes (`US`, `CA`, `GB`, `DE`, …). Numeric Google geo-target
IDs work too.

A brand name can match several advertiser entities — "shopify.com" matches Shopify Inc. plus
three unrelated advertisers with "shopify" in the name. The largest are used and the rest are
named in the log, so nothing is silently dropped or silently billed.

#### Every option

| Option | Default | What it does |
|---|---|---|
| `queries` | `hubspot.com` | Brand, domain, advertiser ID or Transparency Center URL. Omit it entirely and the run falls back to a demo query. |
| `regions` | `["US"]` | Two-letter ISO codes, or numeric Google geo-target IDs. Country *names* are rejected. |
| `maxAdsPerAdvertiser` | `100` | Caps each advertiser, and therefore the cost. |
| `maxAdvertisersPerQuery` | `1` | How many matching advertisers one query may expand to. |
| `includeAdCopy` | `true` | Fetch each ad's preview for headline, body, display URL and landing page. |
| `includeDetail` | `false` | Adds creative variations, per-country impressions, and political spend and targeting. One extra request per ad. |
| `onlyPolitical` | `false` | Keep only ads carrying election-advertising disclosure. Implies full detail, and most commercial brands return nothing. |
| `minDaysServed` | `0` | Drop ads that served fewer days than this. |
| `minServingDensity` | `0` | Drop ads below this days-served ÷ span ratio. |
| `enrichWorkers` | `8` | Parallel preview fetches. Lower it if a run starts backing off. |
| `proxyConfiguration` | none | Optional here — the Chrome TLS fingerprint is what gets us served, not the IP. Datacenter proxies are plenty; residential costs more than the run itself. |

### Output

See `sample_output.json` for full rows. Abridged:

```json
{
  "advertiser_name": "National Democratic Redistricting Committee",
  "headline": "A.G. Eric Holder's NDRC - End Gerrymandering",
  "body": "Eric Holder, A.G. for President Obama, founded the NDRC to protect our democracy.",
  "destination": "https://secure.actblue.com/",
  "format": "image",
  "first_shown": "2022-03-04",
  "last_shown": "2024-01-01",
  "days_served": 665,
  "span_days": 669,
  "serving_density": 0.994,
  "is_political": true,
  "political_confidence": "high",
  "spend_min": { "currency": "USD", "amount": 500 },
  "spend_max": { "currency": "USD", "amount": 600 },
  "impressions_in_region": { "min": 70000, "max": 80000 }
}
```

### Watch a competitor on a schedule

Competitor monitoring is worth more as a diff than as a snapshot. Save the run as a task, give
it a schedule, and each run appends to the same dataset so you can see what launched and what
was retired:

1. Run it once with the advertisers you care about.
2. **Save as a new task**, then **Schedule** → daily or weekly.
3. Point the task's **Integrations** at Google Sheets, Slack, a webhook or your warehouse.

A daily 100-ad watch on one advertiser costs about **$3.30 a month**.

### Use it from your own code

```bash
curl -X POST "https://api.apify.com/v2/acts/adatlas~google-ads-transparency-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"queries":["hubspot.com"],"regions":["US"],"maxAdsPerAdvertiser":50}'
```

Official clients exist for Python and JavaScript, and the actor also works over MCP, so an AI
agent can call it directly.

### Pricing

Pay per event, so a run that finds nothing costs almost nothing:

| Event | Price | Charged |
|---|---|---|
| `apify-actor-start` | $0.0005 | once per run, by the platform, whatever the run returns |
| `ad-scraped` | $0.0009 | per ad delivered |
| `ad-copy-enriched` | $0.0006 | only when headline or body text actually came back |
| `ad-detail-fetched` | $0.0015 | only when the detail request succeeded |

A 100-ad run with copy enabled typically lands near **$1.10 per 1,000 ads**.

You are never charged for a column that came back empty. Image ads carry no extractable
text, so on an image-heavy advertiser the copy event fires on only a few rows — and the
landing page, where Google exposes one, rides along at no extra cost.

### FAQ

**Do I need a Google account or an API key?**
No. The Ads Transparency Center is a public archive; this actor uses no login and accesses
nothing private.

**Can I search by brand name instead of an advertiser URL?**
Yes — that is the main difference from most alternatives. Brand names, domains, advertiser IDs
and pasted Transparency Center URLs all work.

**Can I get competitor ad spend?**
For political and issue advertising, yes: Google discloses a spend range and this actor returns
it. Commercial advertisers have no disclosed spend anywhere in the Transparency Center — any
tool claiming otherwise is estimating.

**Which countries are supported?**
48 markets by two-letter code, plus any numeric Google geo-target ID.

**How do I get only the long-running ads?**
`minDaysServed: 90` or `minServingDensity: 0.8`.

**Can I export to CSV or Excel?**
Yes — every Apify dataset exports to JSON, CSV, Excel, XML and HTML table.

### Notes and limits

- Data comes from Google's public Ads Transparency Center, an archive Google publishes to meet
  advertising-disclosure rules. No login is used and nothing private is accessed.
- Spend, impression and targeting figures exist only for political and issue advertising, and
  only in the jurisdictions where disclosure is required. Commercial ads have none of them —
  that is Google's disclosure boundary, not a gap in this actor.
- Impressions come in two flavours because Google reports them two ways:
  `impressions_in_region` matches the number shown on the ad's own page for that country, while
  `impressions_all_regions` is the global total.
- `maxAdsPerAdvertiser` caps each advertiser. Large brands have thousands of ads on file, so a
  run returns the cap, not the entire archive.
- Ad delivery platform is reported as `platform_code` with a label only where the code has been
  confirmed against Google's own UI. Unconfirmed codes ship as a number rather than a guess.

# Actor input Schema

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

One per line. Accepts a brand name (`nike`), a domain (`nike.com`), a Google advertiser ID (`AR16735076323512287233`), or a pasted Ads Transparency Center URL.

## `regions` (type: `array`):

Two-letter country codes such as US, CA, GB, DE. Numeric Google geo-target IDs also work. Must be ISO 3166-1 alpha-2 codes, not country names — "Germany" is rejected, "DE" is correct.

## `maxAdsPerAdvertiser` (type: `integer`):

Large brands have thousands of ads on file. This caps what each advertiser contributes, and therefore what the run costs.

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

A brand name can match several advertiser entities. The largest are taken first; the rest are listed in the log.

## `includeAdCopy` (type: `boolean`):

Fetches each ad's preview to pull the headline, body copy, display URL and destination URL. Adds one request per ad. Not every ad exposes text — image ads often carry only a landing page.

## `includeDetail` (type: `boolean`):

Adds creative variations and a per-country impression breakdown, and for political ads the disclosed spend range and targeting. One extra request per ad.

## `onlyPolitical` (type: `boolean`):

Keeps only ads carrying election-advertising disclosure. Implies full detail, so it costs one extra request per ad and most commercial brands will return nothing.

## `minDaysServed` (type: `integer`):

Google reports how many days each ad actually ran. Set 90 to keep only long-running ads — the ones an advertiser kept paying for.

## `minServingDensity` (type: `number`):

Days served divided by the calendar span, 0 to 1. Near 1 means an always-on evergreen ad; a low value means short bursts.

## `enrichWorkers` (type: `integer`):

Lower this if you see the run backing off.

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

Optional. Datacenter proxies are enough; residential is not needed and costs far more than the run.

## Actor input object example

```json
{
  "queries": [
    "hubspot.com"
  ],
  "regions": [
    "US"
  ],
  "maxAdsPerAdvertiser": 100,
  "maxAdvertisersPerQuery": 1,
  "includeAdCopy": true,
  "includeDetail": false,
  "onlyPolitical": false,
  "minDaysServed": 0,
  "minServingDensity": 0,
  "enrichWorkers": 8,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# 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": [
        "hubspot.com"
    ],
    "regions": [
        "US"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("adatlas/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": ["hubspot.com"],
    "regions": ["US"],
    "proxyConfiguration": { "useApifyProxy": True },
}

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

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": [
    "hubspot.com"
  ],
  "regions": [
    "US"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call adatlas/google-ads-transparency-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=adatlas/google-ads-transparency-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/3iGknRaYySLCzg2w8/builds/CvFR7PLHFVbbfbGBG/openapi.json
