# Google Ads Transparency Scraper - Competitor Ads by Domain (`tidytools/ads-transparency`) Actor

Competitor ads from Google's Ads Transparency Center by advertiser or domain: text, image and video ads, first and last shown, countries, ad text. Monitor new ads. $1.50 per 1,000 ads, failed searches free.

- **URL**: https://apify.com/tidytools/ads-transparency.md
- **Developed by:** [Yukai Lin](https://apify.com/tidytools) (community)
- **Categories:** Marketing, SEO tools, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does Google Ads Transparency Scraper do?

**Enter competitors (name, advertiser ID or link) and/or domains, get one row per Google ad: advertiser, format, first and last shown, days shown, image and a link to the ad. $1.50 per 1,000 ads.** Searches that fail or find nothing are free.

It reads Google's public **Ads Transparency Center** (adstransparency.google.com), the library where Google shows every ad a verified advertiser runs on Search, YouTube, Display, Maps and Play, and turns it into rows you can sort, filter and export:

- **By advertiser**: a name ("Nike, Inc.", "HubSpot"), an advertiser ID (`AR...`) or a Transparency Center link.
- **By domain**: `canva.com` returns the ads that send people to canva.com, from every advertiser.
- **Filters**: country the ad was shown in, format (text, image, video), date range or "last N days".
- **Per ad**: advertiser, format, **first and last shown**, **days shown**, target domain, the ad image (or video preview), and a link to the ad in the Transparency Center.
- **Optional details**: every image or video variation and **each country the ad ran in, with the last date it ran there**.
- **Optional ad text**: the **headline, description and display URL** of each ad as text. When Google's own ad preview data has them, they are copied exactly (`adTextSource: "preview"`); otherwise the ad picture is read with an AI vision model (`adTextSource: "image-ocr"`).
- **Monitoring**: "Only ads not seen in earlier runs" returns just the new ads of your competitors on each scheduled run.

Real counts from a run on 3 October 2026 (region US): Nike, Inc. 7,000-8,000 ads, Shopify Inc. 8,000-9,000, HubSpot 600-700; ads pointing to canva.com 10,000-20,000, monday.com 7,000-8,000, zapier.com 600-700. The Actor returns the newest ones first, up to your limit, and puts Google's total in the run summary.

**Honest by design.** Requests identify themselves (`TidyToolsAds/1.0 (+https://apify.com/tidytools)`), are paced to about two per second, and use the same public data the Transparency Center web page loads. No login, no cookies, no proxies, no CAPTCHA solving. The site has no robots.txt rules against this (checked 3 October 2026). If Google refuses a request, the search is reported as `blocked` and not charged.

### Use cases

- **Competitor ad research**: what is a competitor advertising right now, in which formats and countries, and since when?
- **Ad copy ideas**: the headline and description text of a competitor's search ads (with "Read the ad text").
- **New-ad alerts**: schedule a daily or weekly run with "Only ads not seen in earlier runs" and get only the new creatives, by e-mail, Slack, webhook or Google Sheets.
- **Agency prospecting**: is a prospect advertising on Google at all, and how much (Google's ad-count range per advertiser)?
- **Market and brand-safety research**: who advertises against a brand's domain, and in which countries.

### How much does it cost?

| Event | Price | When |
|---|---|---|
| Ad | **$1.50 / 1,000 ads** | one ad returned |
| Ad details | **$2.00 / 1,000** | optional: variations and countries with last-shown dates, charged only when they come back |
| Ad text | **$4.00 / 1,000** | optional: headline, description and display URL read from a text or image ad, charged only when text was found |

**Examples:** the latest 100 ads of 10 competitors = 1,000 ads = **$1.50**. With details and ad text: $7.50. A weekly "new ads only" monitor that finds 40 new ads costs $0.06 that week.

**No start fee.** Searches that fail or find nothing, ads already seen in monitoring mode, duplicates and invalid lines are free. Larger Apify plans get the usual volume discount (Silver 10%, Gold and above 20%).

For comparison (Apify Store, checked 3 October 2026): the most-used Transparency Center Actors charge $0.0015-$0.002 per ad; across all of them the range is $0.001 (plus a $0.005 start fee) to $0.012 per ad, and separate ad-detail events cost $0.003-$0.05.

#### Control your cost

- **Max ads per advertiser or domain** (default 100) caps every search; big brands have thousands of ads.
- **Before it starts**, the run logs its worst-case cost (searches x max ads x price) and compares it with your **maximum charge per run**. When the maximum is reached the run stops cleanly: `SUMMARY.status` is `LIMIT_REACHED` and `SUMMARY.notProcessed` lists the searches that were not finished.
- Every row has `chargedEvents`; failed rows have `charged: false` and an `error` that ends with "(not charged)".

### How to use it

1. Enter **Advertisers** (names, IDs or links) and/or **Domains**, one per line.
2. Optional: **Shown in country** (two-letter code such as US, GB, DE), **Ad format**, **Shown in the last N days**.
3. Optional: turn on **Ad details** and/or **Read the ad text**.
4. Click **Start**. The **Ads (overview)** tab shows the ad images with advertiser, format and dates; **Ads (all fields)** has everything.

#### Input example

This is the input the form is prefilled with; a run with it takes about a minute and costs only cents:

```json
{
    "advertisers": [
        "Nike, Inc."
    ],
    "domains": [
        "notion.so"
    ],
    "region": "US",
    "maxAdsPerSearch": 20
}
```

#### Output example

Real rows from 3 October 2026 (shortened). A text ad found by domain, with details and ad text:

```json
{
    "input": "zapier.com",
    "searchType": "domain",
    "region": "US",
    "advertiserId": "AR07656268315995668481",
    "advertiserName": "Zapier, Inc",
    "creativeId": "CR05925856452345331713",
    "format": "text",
    "firstShown": "2025-04-25T01:23:10.000Z",
    "lastShown": "2026-10-03T04:56:29.000Z",
    "daysShown": 468,
    "targetDomain": "zapier.com",
    "imageUrl": "https://tpc.googlesyndication.com/archive/simgad/16983613173943704328",
    "adUrl": "https://adstransparency.google.com/advertiser/AR07656268315995668481/creative/CR05925856452345331713?region=US",
    "variationCount": 1,
    "regionsShown": [
        { "country": "DE", "geoId": 2276, "lastShownDate": "2026-10-02" },
        { "country": "US", "geoId": 2840, "lastShownDate": "2026-10-02" },
        { "country": "GB", "geoId": 2826, "lastShownDate": "2026-10-02" }
    ],
    "countriesShown": ["DE", "IL", "CA", "US", "AU", "IT", "MX", "GB"],
    "chargedEvents": ["ad", "ad-details"],
    "charged": true
}
```

The `adText` of a HubSpot search ad from the same day (run with "Read the ad text"):

```json
{
    "advertiserName": "Hubspot, Inc.",
    "format": "text",
    "adText": {
        "headline": "See Your Score in Minutes - More Than an AI Search Score",
        "description": "See where competitors beat you in AI search and what to do. Try it free for 28 days.",
        "displayUrl": "www.hubspot.com",
        "callToAction": "Try it Free for 28 Days Get Your Free AEO Score",
        "text": "See Your Score in Minutes - More Than an AI Search Score See where competitors beat you in AI search and what to do. Try it free for 28 days. Try it Free for 28 Days Get Your Free AEO Score"
    },
    "adTextSource": "image-ocr",
    "chargedEvents": ["ad", "ad-text"]
}
```

A monday.com search ad without an archived image, read exactly from Google's ad preview data (17 October 2026):

```json
{
    "format": "text",
    "imageUrl": null,
    "adText": {
        "headline": "monday CRM",
        "description": "Get started with monday CRM to manage, automate, and centralize the entire sales cycle.",
        "displayUrl": "www.monday.com/",
        "callToAction": "",
        "text": "monday CRM | Get started with monday CRM to manage, automate, and centralize the entire sales cycle. | www.monday.com/"
    },
    "adTextSource": "preview",
    "chargedEvents": ["ad", "ad-text"]
}
```

Video ads have a `previewUrl` (the Transparency Center's player script) instead of an image. The run SUMMARY lists, per search, the advertisers used (with Google's ad-count range) and Google's total number of matching ads (`totalAdsMin`-`totalAdsMax`).

#### Advertiser names

A name is matched against Google's advertiser suggestions. **Most ads** (default) uses the matching advertiser with the most ads, so "Nike" becomes Nike, Inc. (9,000-10,000 ads) rather than a small advertiser called "Nike". **All matches** uses every advertiser whose name contains your text (up to 10), useful for brands with one advertiser account per country. For an exact advertiser, paste its ID or Transparency Center link.

### Use it from code

```bash
curl -X POST "https://api.apify.com/v2/acts/tidytools~ads-transparency/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domains":["canva.com"],"region":"US","maxAdsPerSearch":50}'
```

**With AI agents (MCP):** connect Apify's MCP server (`https://mcp.apify.com?tools=tidytools/ads-transparency`) to Claude, Cursor or any MCP client and ask, for example, "What Google ads is Zapier running in Germany this month?". Minimal input: `{"advertisers": ["Zapier"]}`. Searches that fail or find nothing are not charged and carry an `errorType`.

### Limitations

- The Transparency Center shows ads of **verified advertisers** from about 2018 on; it is not a full archive of every ad ever shown. Political-ads details (spend, impressions) are a separate Google report and are not included.
- Ad counts from Google are ranges ("7,000-8,000"). Results come newest first; the Actor does not sort them differently.
- "Read the ad text" first copies the text from Google's ad preview data (exact, `adTextSource: "preview"`; keyword-insertion templates such as `{KeyWord:Nike Shoes}` are kept as Google stores them). Most text ads only have an archived picture, which is read with an AI model (`adTextSource: "image-ocr"`); very small or stylized text can be misread. Shopping product ads without a picture get no text, and video ads are not transcribed (both not charged).
- The data comes from a Google web page without a public API contract. If Google changes it, results can stop until the Actor is updated; failures are not charged.

### Responsible use

- The output is about ads: advertiser, creative, dates, countries. The Actor does not look up people, contact details or profiles.
- Verified advertisers can be individuals (sole traders, consultants) whose legal name Google shows. Use such names only for advertising research, and follow the privacy law that applies to you (for example GDPR in the EU).
- Ad creatives belong to their owners. The Actor returns links to Google's archived copies; do not republish creatives as your own.

### FAQ

**Does the Actor collect usage data?** The Actor sends one anonymous usage counter per run (input type, item count range, outcome) to improve the tool; no URLs, content or account data.

**Is this allowed?** The Transparency Center is a public page that Google built so anyone can see who advertises. This Actor reads it at a gentle pace with an honest user agent and no login. You are responsible for how you use the results; Google's terms of service apply to Google's sites.

**Why do some searches return fewer ads than Google's total?** The **Max ads per advertiser or domain** limit (default 100). Raise it to get more.

**Do I pay for an advertiser that has no ads?** No. It shows up as a row with `errorType: "no_results"` and `charged: false`. An unknown advertiser name or ID gives `errorType: "not_found"`, also free.

**What happens if my input is wrong (empty lists, an unknown country code or ad format, a date that does not exist such as 2026-02-31, a number field with text, a line that is not a domain)?** The run does not fail: it writes a row with `errorType: "invalid_input"`, `charged: false` and the reason, ends normally and costs nothing. A bad *line* (a link that is not a Transparency Center advertiser, text that is not a domain) is reported on its own row and the valid lines in the same run are still searched; a bad *setting* (country, format, dates, numbers, yes/no switches) stops the run before anything is searched, so nothing is silently replaced by a default. API callers may send numbers and `true`/`false` as text.

**What if the same advertiser is entered twice (for example "Nike" and "nike inc")?** Each ad is returned and charged once per run; repeats are counted in `SUMMARY.duplicateAdsMerged` / `duplicateInputsMerged`, and a later spelling that only finds ads already returned gets its own free row (`errorType: "duplicate_input"`, `duplicateOf` names the first input).

**Why does a domain search show other advertisers' names?** A domain search returns every ad that points to that domain from every advertiser, including resellers and affiliates, not only the company that owns the domain. Use the advertiser search for one company's own ads.

**Why does a second "new ads only" run return nothing?** Ads returned before under the same monitor name are skipped and are free; an empty dataset means no new ads.

### Support

Open an issue in the **Issues** tab with the input and the run link. Issues are checked regularly.

# Actor input Schema

## `advertisers` (type: `array`):

Advertiser names ("Nike"), advertiser IDs (AR + 20 digits) or Ads Transparency Center links (https://adstransparency.google.com/advertiser/AR...), one per line. A name is matched against Google's advertiser list: by default the matching advertiser with the most ads is used (see "Advertiser name matching"). Invalid lines become a result row with errorType "invalid_input" (not charged).

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

Websites, one per line (nike.com or https://www.nike.com/us/): returns the ads that point to that domain, from every advertiser. Up to 500 advertisers and domains per run.

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

Only ads shown in this country: a two-letter code such as US, GB, DE, FR, AU, JP or TW. "anywhere" (default) returns ads shown in any country.

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

Only text ads (Google Search), image ads (display) or video ads (YouTube).

## `lastDays` (type: `integer`):

Only ads shown in the last N days (0 = any time). Ignored when "Shown from" is set.

## `dateFrom` (type: `string`):

Only ads shown on or after this date.

## `dateTo` (type: `string`):

Only ads shown on or before this date.

## `maxAdsPerSearch` (type: `integer`):

Stop after this many ads for each advertiser or domain line (newest first). Large brands have thousands of ads; the run SUMMARY shows Google's total count for every search.

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

One extra lookup per ad: every image or video variation, and each country the ad was shown in with the last date shown there. Charged as "Ad details" only when they come back.

## `extractAdText` (type: `boolean`):

The Transparency Center shows text ads as pictures. With this on, the words of each text or image ad (headline, description, display URL, call to action) are read from its image with an AI vision model. Charged as "Ad text" only when text was found; video ads are skipped.

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

Competitor monitoring: return (and charge) only ads that earlier runs with the same monitor name did not return. Schedule the run daily or weekly and connect a webhook, Slack or Google Sheets integration.

## `monitorName` (type: `string`):

Separate monitors keep separate lists of seen ads. Only used with "Only ads not seen in earlier runs".

## `advertiserMatch` (type: `string`):

How a name is turned into advertisers. "Most ads": the matching advertiser with the most ads ("Nike" -> Nike, Inc.). "All matches": every advertiser whose name contains the text, up to the limit below (useful for brands with one advertiser account per country).

## `maxAdvertisersPerName` (type: `integer`):

With "All matches": at most this many advertisers per name.

## `maxConcurrency` (type: `integer`):

Ad details and ad text lookups at the same time. Requests to Google are paced (about 2 per second) whatever this is.

## Actor input object example

```json
{
  "advertisers": [
    "Nike, Inc."
  ],
  "domains": [
    "notion.so"
  ],
  "region": "US",
  "format": "all",
  "lastDays": 0,
  "maxAdsPerSearch": 20,
  "includeDetails": false,
  "extractAdText": false,
  "onlyNewAds": false,
  "monitorName": "default",
  "advertiserMatch": "most-ads",
  "maxAdvertisersPerName": 5,
  "maxConcurrency": 2
}
```

# Actor output Schema

## `ads` (type: `string`):

No description

## `full` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `details` (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 = {
    "advertisers": [
        "Nike, Inc."
    ],
    "domains": [
        "notion.so"
    ],
    "region": "US",
    "maxAdsPerSearch": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("tidytools/ads-transparency").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 = {
    "advertisers": ["Nike, Inc."],
    "domains": ["notion.so"],
    "region": "US",
    "maxAdsPerSearch": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("tidytools/ads-transparency").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 '{
  "advertisers": [
    "Nike, Inc."
  ],
  "domains": [
    "notion.so"
  ],
  "region": "US",
  "maxAdsPerSearch": 20
}' |
apify call tidytools/ads-transparency --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tidytools/ads-transparency"
        }
    }
}
```

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/h2sgUDYx9j6G4FmKM/builds/N1PZ8RK9vmQFeSixC/openapi.json
