# Google Ads Advertiser Tracker · Top 10 + CPC (`doesaiknow/google-ads-transparency-intel`) Actor

Find who advertises on your keywords in Google Ads. One row per keyword: up to 10 advertisers plus 10 ad domains, the creatives they run with first and last shown dates, CPC and search volume, and a diff of who entered or left since your last run.

- **URL**: https://apify.com/doesaiknow/google-ads-transparency-intel.md
- **Developed by:** [David S](https://apify.com/doesaiknow) (community)
- **Categories:** SEO tools, MCP servers, E-commerce
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.02 / ads transparency keyword

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 Advertiser Tracker: find who advertises on any keyword

Give this Actor a list of keywords and it returns **who is advertising on them in Google Ads** — the
named advertisers, their ad domains, the creatives they run and how long each one has been running —
enriched with **CPC and search volume**, plus a **diff** of who entered and left since your last run.

Built for **PPC agencies and performance marketers**: one billed row per keyword, 500 keywords per run,
up to 10 advertisers and 10 ad domains per keyword, no Google Ads account, no browser automation.

### What does the Google Ads advertiser tracker do?

For every keyword it resolves the advertisers Google publishes in its public Ads Transparency data,
pulls their creatives, and joins CPC, search volume and competition onto the same row. Each creative
carries its own first/last shown dates, so you see not just who bids but **what they keep running** —
the ads that survive months of optimization are the ads that work.

It does **not** return every advertiser on a keyword. The public data gives a top slice — **up to 10
named advertisers plus up to 10 ad domains per keyword** — and this Actor states that limit in every row.

### Why use this Google Ads competitor research actor

- **One row per keyword, not per ad.** Advertisers and creatives nest inside the row, so 20 keywords
  give you 20 rows to read, not 4,000 to pivot.
- **Advertiser identity, not just domains.** Name, stable advertiser ID, verification flag, country,
  approximate total ads count and rank in the line-up.
- **Ad age, not a flat list.** `days_running`, `is_live` and `longest_running_creative` show which ad
  has been paying for itself longest.
- **CPC and volume on the same row** — no second tool for "is this keyword worth fighting for".
- **A diff between runs** turns a data pull into an alert: `new_advertisers`, `lost_advertisers`.
- **Honest coverage.** Roughly **1 in 5 commercial keywords has no advertiser** in this data and about
  **1 in 6** returns ad domains only; those keywords come back as rows that say so.

### How to find advertisers for a keyword in Google Ads

1. Click **Try for free** and open the **Input** tab.
2. Paste your keywords into **Keywords**, one per line, up to 500. They are trimmed, lowercased and
   de-duplicated, so ` Car   Insurance` and `car insurance` count as one.
3. Pick the **Location** and **Language** of the ads, and leave **Recency window** at 7 days.
4. Click **Start**, then read **Overview** for one line per keyword, or **Changes since last run**.
5. Add an **Apify Schedule** — weekly works well — so `diff` has an earlier run to compare against.

```json
{
  "keywords": ["car insurance", "vpn", "cordless impact wrench"],
  "country": "2840",
  "language_code": "en",
  "recencyWindowDays": 7
}
```

### What data you get per keyword

One dataset row per keyword, 23 fields. A shortened real row:

```json
{
  "keyword": "car insurance",
  "location_code": 2840,
  "language_code": "en",
  "status": "rich",
  "not_found": false,
  "coverage_note": "Shows the top 10 advertisers and top 10 ad domains for this keyword — not a complete list of everyone advertising on it.",
  "advertisers_found": 10,
  "ad_domains_found": 10,
  "advertisers": [
    { "advertiser_id": "AR03513828614485311489", "name": "Save Money Car Insurance",
      "verified": true, "approx_ads_count": 52, "advertiser_country": "US", "rank": 1 }
  ],
  "ad_domains": ["uscarinsurance.org", "usacarinsurance.com"],
  "creatives_total": 38,
  "creatives": [
    { "advertiser_id": "AR03513828614485311489", "creative_id": "CR14640892953367674881",
      "title": "Save Money Car Insurance", "format": "text", "days_running": 90, "is_live": true,
      "first_shown": "2026-04-17 21:31:20 +00:00", "last_shown": "2026-07-17 08:22:57 +00:00",
      "transparency_url": "https://adstransparency.google.com/advertiser/AR03513828614485311489/creative/CR14640892953367674881?region=US" }
  ],
  "days_running": 90,
  "is_live": true,
  "format_mix": { "text": 15, "image": 17, "video": 6, "other": 0 },
  "longest_running_creative": { "creative_id": "CR14640892953367674881", "days_running": 90 },
  "cpc": 48.17,
  "search_volume": 823000,
  "competition": "HIGH",
  "cpc_available": true,
  "diff": { "since": "2026-07-13T12:00:00.000Z", "new_advertisers": ["AR12345678901234567"],
            "lost_advertisers": [], "new_creatives": 12, "lost_creatives": 4 },
  "source": "primary",
  "checked_at": "2026-07-20T12:00:00.000Z"
}
```

| Field | Type | What it tells you |
|---|---|---|
| `keyword` | string | The normalized keyword |
| `location_code` | integer | Market checked (2840 = US) |
| `language_code` | string | Language of ads and keyword data |
| `status` | string | `rich` (5+ advertisers), `thin` (1-4), `domains_only`, `no_advertisers_found` |
| `not_found` | boolean | No advertiser and no domain found — a result, not an error |
| `coverage_note` | string | What the row covers, and where it was cut |
| `advertisers_found` | integer | Named advertisers, 0-10 |
| `ad_domains_found` | integer | Ad domains, 0-10 |
| `advertisers[]` | array | `advertiser_id`, `name`, `verified`, `approx_ads_count`, `advertiser_country`, `rank` |
| `ad_domains[]` | array | Normalized, de-duplicated domains |
| `creatives_total` | integer | Creatives across all advertisers here |
| `creatives[]` | array | `creative_id`, `title`, `format`, `first_shown`, `last_shown`, `days_running`, `is_live`, `transparency_url` |
| `days_running` | integer/null | Age of the longest-running creative |
| `is_live` | boolean | A creative ran inside your recency window |
| `format_mix` | object | `text`/`image`/`video`/`other`; adds up to `creatives_total` |
| `longest_running_creative` | object/null | Creative with the highest `days_running` |
| `cpc` | number/null | Advertiser cost per click, 2 decimals |
| `search_volume` | integer/null | Average monthly searches |
| `competition` | string/null | Advertiser competition; `null` = not measured |
| `cpc_available` | boolean | False when no CPC data — not the same as `not_found` |
| `diff` | object/null | `since`, `new_advertisers[]`, `lost_advertisers[]`, `new_creatives`, `lost_creatives` |
| `source` | string | `primary`, `fallback-creatives`, `advertisers-only`, `fallback-cpc` |
| `checked_at` | string | When the keyword was observed |

### Change tracking needs a schedule

The `diff` field answers what a single pull cannot: **who entered the auction and who left since the last
time you looked.** That needs an earlier run, so after every delivered row the Actor stores a small
snapshot — keyword, market, language, observation time, advertiser and creative IDs — for **60 days**.

- **The first run for a keyword × market × language has nothing to compare with, so `diff` is `null`.**
  The key is always there: empty lists mean "measured, nothing moved", `null` means "no earlier
  observation yet".
- **Run it on a schedule to get a diff at all** — roughly **every 7 days** matches how fast advertiser
  line-ups move. One-off runs weeks apart still work; `since` shows the older date.
- **Snapshots are per keyword × market × language**, so a US line-up is never compared against a UK one.
  A keyword with `not_found: true` still stores an observation, so an advertiser returning next week is
  reported in `new_advertisers`.
- **Cached rows keep the `diff` measured at observation time** and never overwrite the snapshot; an
  unreadable snapshot gives `diff: null` and every other field as usual.

### Input parameters

| Input | Type | Default | Notes |
|---|---|---|---|
| `keywords` | array | required | 1-500 keywords; trimmed, lowercased, de-duplicated |
| `country` | select | United States | Market the ads are checked in — 92 countries |
| `language_code` | select | `en` | Language of the ads and keyword data — 45 languages |
| `recencyWindowDays` | integer | 7 | A creative counts as live when last shown within this many days |

Full descriptions live on the **Input** tab. It also runs from the **API** tab, the JavaScript and Python
clients, and through `mcp.apify.com`.

### How much does the Google Ads advertiser tracker cost?

Pay per event — for answers, not runtime.

| Event | When it fires | Price |
|---|---|---|
| Actor start | Once per run | $0.02 |
| Ads transparency keyword | Once per delivered keyword | $0.039 or lower |

- A keyword with **no advertisers is a delivered row and is billed** — "nobody is bidding here" is an
  answer, and often the most useful cell in the sheet.
- A keyword whose data could **not** be retrieved is **not delivered and not billed**, and repeating a
  keyword **within 24 hours re-delivers the cached row unbilled**.

### Google Ads advertiser tracker vs ad spy tools and manual research

| | This Actor | Transparency Center by hand | SaaS ad-spy suites |
|---|---|---|---|
| Input | 500 keywords per run | One advertiser at a time | Keyword or domain |
| Advertiser identity | ID, name, country, rank | Visible, not exportable | Yes |
| CPC + search volume | On every row | No | Usually a separate module |
| Change diff between runs | Built in | Manual | Sometimes, on higher plans |
| Output | Dataset, API, JSON/CSV/Excel | Screenshots | Dashboard, export limits |
| Commitment | Per keyword | Manual work | Monthly subscription |

### What this actor does not do

- It does **not** return a complete list of everyone advertising on a keyword. The public data gives a
  top slice: **up to 10 named advertisers and up to 10 ad domains per keyword**, and `coverage_note`
  says so in every row, including when a longer list was trimmed by rank.
- It does **not** find advertisers where the source has none. **Some keywords return no advertiser at
  all** — roughly 22% of commercial keywords in our sample, and a further ~17% return ad domains only.
  Those rows are delivered and billed with `not_found: true`, never dropped in silence.
- It does **not** return unlimited creatives per advertiser: the list is capped by the source (40 in our
  measurements) and the row says when that cap was hit.
- It does **not** round unknowns to zero. Missing CPC is `cpc: null` with `cpc_available: false`, and
  unmeasured competition stays `null`, never "LOW".
- It does **not** report ad spend, budgets, impressions or win rates. `approx_ads_count` is the source's
  own approximation of how many ads an advertiser runs.
- It does **not** track more than one market per run, and produces no `diff` on the first run.
- It does **not** log into Google, touch your Google Ads account, or bypass any protection.

### FAQ

#### How do I find out who is advertising on a keyword in Google Ads?

Submit the keyword and a market. The row returns up to 10 named advertisers with their IDs, verification
status and country, up to 10 ad domains, and the creatives each advertiser is running.

#### Is this the same as the Google Ads Transparency Center?

It reads the same public data, but the Transparency Center is browsed one advertiser at a time and
exports nothing. This Actor is keyword-first for 500 keywords per run, resolves the advertisers, adds CPC
and volume, and reports what changed since your previous run.

#### Why does a keyword come back with no advertisers?

Because the public data has none for it — roughly one in five commercial keywords. The row is delivered
with `not_found: true` and a note that says so, instead of disappearing.

#### Why is `cpc` null when advertisers were found?

CPC and volume come from keyword planning data, which does not cover every phrase — product names in
particular. That is `cpc_available: false`, and says nothing about who is bidding.

#### Does it work outside the United States?

Yes — 92 markets and 45 languages, one market per run. CPC coverage is narrower than the market list;
where planning data is missing you still get advertisers and creatives.

### Related actors

- [Google Local Pack Tracker](https://apify.com/doesaiknow/google-local-pack-tracker) — who owns the map
  pack for the same commercial phrases.
- [Website Traffic Estimator](https://apify.com/doesaiknow/website-traffic-estimator) — size up an
  advertiser's site once you know who they are.

### Legal & support

- This Actor returns **public advertising transparency data** that Google itself publishes about
  advertisers and the ads they run, plus aggregated keyword planning metrics. It collects no personal
  data and returns no end-user identities.
- Data is retrieved through a licensed data **API** — no login, no account takeover, no anti-bot
  circumvention, no automated browsing of Google.
- Output is a **point-in-time observation** of public data, provided as-is for competitive research —
  not an audited record of advertising activity or spend, and not a prediction.
- Coverage limits are disclosed in the row itself, not buried here: the top-10 cap per keyword, keywords
  with no advertisers, and the creative cap per advertiser.
- You are responsible for using the output in line with the law in your jurisdiction and the terms of the
  platforms you advertise on. Google and Google Ads are trademarks of Google LLC; this Actor is not
  affiliated with or endorsed by Google.
- Questions or a bug? Open an issue on the **Issues** tab; see **API** for integration.

### SEO keywords

google ads advertiser lookup, who advertises on a keyword, google ads transparency data, ppc competitor
analysis, competitor ppc research tool, ad spy software, google ads competitor research, ad creative
research, advertiser id lookup, keyword cpc data, ppc keyword research, competitor ad monitoring,
advertiser tracking tool, google ads intelligence, paid search competitor tracking, bulk keyword
advertiser check, weekly ad change alerts.

Built by doesaiknow.

# Actor input Schema

## `keywords` (type: `array`):

Keywords to track advertisers for. One delivered row per keyword: the top advertisers bidding on it (up to 10 named advertisers plus up to 10 ad domains), their live creatives with first/last shown dates, plus CPC and search volume. Keywords are trimmed, lowercased and de-duplicated, so "  Car   Insurance " and "car insurance" count as one. Up to 500 keywords per run.

## `country` (type: `string`):

Geographic market the ads are checked in — pick a country. Overrides the hidden API-only location code when set.

## `location_code` (type: `integer`):

API only (hidden in the UI): numeric market code used when no country is selected above. Defaults to 2840 (United States).

## `language_code` (type: `string`):

Language of the ads and keyword data. Use the two-letter code only (English is "en"); leave empty to use the selected market's primary language.

## `recencyWindowDays` (type: `integer`):

A creative counts as live when it was last shown within this many days. Drives the is\_live flag on every creative.

## Actor input object example

```json
{
  "keywords": [
    "car insurance",
    "vpn"
  ],
  "country": "2840",
  "language_code": "en",
  "recencyWindowDays": 7
}
```

# Actor output Schema

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

All delivered rows with advertisers, ad domains, creatives, CPC, search volume and the change diff.

## `overview` (type: `string`):

Compact view: keyword, status, advertisers found, creatives found, CPC and search volume.

## `changes` (type: `string`):

Alert view: advertisers that entered or left the auction, plus the longest-running creative per keyword.

## `snapshots` (type: `string`):

One small snapshot per keyword, market and language, kept for 60 days. This is the baseline the diff is measured against, so it outlives the run that wrote it.

## `cache` (type: `string`):

Delivered rows kept for 24 hours. Re-running the same keyword, market and language inside that window re-delivers the row and is not billed.

# 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 = {
    "keywords": [
        "car insurance",
        "vpn"
    ],
    "country": "2840",
    "language_code": "en"
};

// Run the Actor and wait for it to finish
const run = await client.actor("doesaiknow/google-ads-transparency-intel").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 = {
    "keywords": [
        "car insurance",
        "vpn",
    ],
    "country": "2840",
    "language_code": "en",
}

# Run the Actor and wait for it to finish
run = client.actor("doesaiknow/google-ads-transparency-intel").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 '{
  "keywords": [
    "car insurance",
    "vpn"
  ],
  "country": "2840",
  "language_code": "en"
}' |
apify call doesaiknow/google-ads-transparency-intel --silent --output-dataset

```

## MCP server setup

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

```

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/ZeYz4GSPyC9ygtxli/builds/k5fdp8OMJKk8AjQhI/openapi.json
