How to Monitor Competitor Ads in Google Ads Transparency Center

Uses Google Ads Transparency Scraper — Competitor Ads | $0.60/1K

Short answer: List your competitors' domains or advertiser names, run the ActorForge Ads Transparency actor daily with only-new mode, and route the new ads into Slack, a sheet or a weekly review.

Google's Ads Transparency Center lets anyone see the ads an advertiser runs across Search, YouTube, Display, Shopping and Maps. It is a great research tool, but it has no alerts and no export, so keeping up with five competitors means clicking through five advertiser pages and trying to remember what you saw last week.

The Ads Transparency actor reads the same public ad library and returns one row per ad, with format, first and last shown dates, landing domain, media URLs and a link to the ad's page. Its monitoring mode remembers every ad it has delivered and only returns new ones on the next run, without charging for the rest.

This guide builds a daily competitor ad monitor: choosing how to identify competitors, setting filters, scheduling, sending new ads somewhere useful and analyzing the history.

Step by step

  1. Decide how to identify each competitor

    You have three options. domains (for example nike.com) returns every ad Google shows for that domain, including ads placed by agencies and resellers. advertiserNames (for example HubSpot) matches the company name as shown in the Transparency Center and keeps all of a brand's legal entities. advertiserIds takes the AR… ID from an adstransparency.google.com/advertiser/AR… link and is the most precise. Domains are the easiest starting point; switch to IDs if a domain search pulls in advertisers you do not care about.

  2. Narrow by market and format

    Use regions with two-letter country codes to keep only ads shown in your markets, and formats (text, image, video) if you only care about, say, video creatives. dateFrom and dateTo limit results to ads shown in a date window, useful for a first backfill. Leave filters empty to see everything.

  3. Cap the volume per competitor

    maxAdsPerAdvertiser counts ads read newest first for each domain, name or ID, and maxResults caps the whole run. For a daily monitor a small per-advertiser cap such as 20 to 50 keeps runs fast and cheap, because new ads appear at the top of the list. Raise it once for an initial backfill if you want the history.

  4. Switch on monitoring mode

    Set onlyNewSinceLastRun to true and choose a monitorName per watch list, for example uk-competitors. Every delivered ad is remembered in the key-value store ads-seen-<monitorName>, and later runs skip it at no charge. Decide whether you need includeDetails: it adds countries shown, impression ranges and best-effort headline and description text for $0.001 per ad, which is worth it for creative research but not essential for alerts.

    {
      "domains": ["competitor-one.com", "competitor-two.com"],
      "advertiserNames": ["HubSpot"],
      "regions": ["GB"],
      "formats": [],
      "maxAdsPerAdvertiser": 30,
      "maxResults": 500,
      "includeDetails": false,
      "onlyNewSinceLastRun": true,
      "monitorName": "uk-competitors"
    }
  5. Schedule it daily

    Save the input as an Apify task and attach a schedule with a cron expression like 0 8 * * *. The first run returns the current set of ads up to your caps; from then on, each run returns only ads that appeared since. Add a webhook on run success that calls your own endpoint with the run's dataset ID.

  6. Send new ads to Slack

    In the webhook handler, fetch the run's dataset and post a short summary to a Slack incoming webhook. Empty runs post nothing, so the channel only lights up when a competitor launches something.

    import os
    import requests
    
    # Called from your webhook handler with the finished run's dataset ID
    def post_new_ads(dataset_id: str, slack_webhook_url: str) -> None:
        items = requests.get(
            f"https://api.apify.com/v2/datasets/{dataset_id}/items",
            params={"clean": "true"},
            headers={"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"},
            timeout=60,
        ).json()
        if not items:
            return
        lines = [f"*{len(items)} new competitor ads*"]
        for ad in items[:20]:
            lines.append(f"{ad.get('advertiserName')} | {ad.get('format')} | "
                         f"{ad.get('landingDomain')} | {ad.get('previewUrl')}")
        requests.post(slack_webhook_url, json={"text": "\n".join(lines)}, timeout=30)
    
  7. Analyze format mix and long-running creatives

    Once you have a backfill, load it into pandas to see each advertiser's format mix and which creatives have stayed live longest. Long-running ads are usually the ones a competitor keeps paying for, so they are a good place to start when studying their messaging.

    import os
    import pandas as pd
    from apify_client import ApifyClient
    
    client = ApifyClient(os.environ["APIFY_TOKEN"])
    run = client.actor("actor-forge/ads-transparency").runs().list(desc=True, limit=1, status="SUCCEEDED").items[0]
    ads = pd.DataFrame(client.dataset(run["defaultDatasetId"]).iterate_items())
    
    ads["firstShown"] = pd.to_datetime(ads["firstShown"])
    ads["lastShown"] = pd.to_datetime(ads["lastShown"])
    ads["daysLive"] = (ads["lastShown"] - ads["firstShown"]).dt.days
    
    # Format mix per advertiser
    print(ads.pivot_table(index="advertiserName", columns="format",
                          values="previewUrl", aggfunc="count", fill_value=0))
    
    # Long-running creatives are usually the ones that perform
    print(ads.sort_values("daysLive", ascending=False)
             [["advertiserName", "format", "daysLive", "landingDomain", "previewUrl"]]
             .head(20))
    

Sample input

{
  "domains": [
    "nike.com"
  ],
  "advertiserNames": [
    "HubSpot"
  ],
  "regions": [],
  "maxAdsPerAdvertiser": 20,
  "maxResults": 200,
  "includeDetails": false,
  "onlyNewSinceLastRun": true,
  "monitorName": "competitors-daily"
}

Sample output

Rows look like this, one per ad creative, with a link to the ad's own Transparency Center page:

advertiserNameformatfirstShownlastShownregionslandingDomainpreviewUrl
Nike, Inc.text2022-11-302026-10-19USnike.comhttps://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR00144196208338927617
INITIATIVE MEDIA B.V.video2023-10-172026-10-19FR, BE, DE, IT, ES, …nike.comhttps://adstransparency.google.com/advertiser/AR11154049584043393025/creative/CR03397377851928870913
Hubspot, Inc.text2025-11-032026-10-19US, DE, FR, IN, BR, …hubspot.comhttps://adstransparency.google.com/advertiser/AR10072600183532683265/creative/…
Argos Limitedtext2025-…2026-10-19GBargos.co.ukhttps://adstransparency.google.com/advertiser/AR08556219150266007553/creative/CR07375270183734083585

Run it from code

cURL

curl -X POST "https://api.apify.com/v2/acts/actor-forge~ads-transparency/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["nike.com"], "advertiserNames": ["HubSpot"], "regions": [], "maxAdsPerAdvertiser": 20, "maxResults": 200, "includeDetails": false, "onlyNewSinceLastRun": true, "monitorName": "competitors-daily"}'

JavaScript (Node.js)

// Node.js 18+ (built-in fetch). Set APIFY_TOKEN in your environment.
const input = {
  "domains": [
    "nike.com"
  ],
  "advertiserNames": [
    "HubSpot"
  ],
  "regions": [],
  "maxAdsPerAdvertiser": 20,
  "maxResults": 200,
  "includeDetails": false,
  "onlyNewSinceLastRun": true,
  "monitorName": "competitors-daily"
};

const res = await fetch(
  "https://api.apify.com/v2/acts/actor-forge~ads-transparency/run-sync-get-dataset-items",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.APIFY_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(input),
  }
);
const items = await res.json();
console.log(items.length, items[0]);

Python

# pip install apify-client   (set APIFY_TOKEN in your environment)
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run_input = {
    "domains": [
        "nike.com"
    ],
    "advertiserNames": [
        "HubSpot"
    ],
    "regions": [],
    "maxAdsPerAdvertiser": 20,
    "maxResults": 200,
    "includeDetails": False,
    "onlyNewSinceLastRun": True,
    "monitorName": "competitors-daily"
}

run = client.actor("actor-forge/ads-transparency").call(run_input=run_input)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

The run-sync-get-dataset-items endpoint waits for the run (up to 5 minutes) and returns the dataset rows directly. For longer runs, start the run asynchronously and read the dataset when it finishes, as the Python example does. Replace APIFY_TOKEN with your own token from the Apify console.

What it costs

Ads cost $0.0006 each and details $0.001 each, so a first run of 100 ads is $0.06, or $0.16 with details. After that, a daily monitor that finds around 20 new ads costs about $0.012 a day, roughly $0.36 a month.

Charged forWhenPricePer 1,000Worked example
Ad resultCharged per ad delivered$0.0006$0.60100 ads = $0.06; 1,000 ads = $0.60; a daily monitor returning ~20 new ads = $0.012 a day
Ad detailCharged per ad, only when 'Include ad details' is on (one extra lookup: regions, impressions, ad copy)$0.001$1100 ads with details = $0.06 + $0.10 = $0.16
Actor Start (platform start fee)Charged when the Actor starts running. Number of events charged depends on Actor memory (one event per GB, minimum one event).$0.00005—One run at 256 MB = $0.0000125

Ready to try it? Google Ads Transparency Scraper — Competitor Ads | $0.60/1K runs on Apify, pay per result.

Run on Apify → Read the full actor page

Tips and pitfalls

FAQ

Will I see ads from agencies running campaigns for my competitor?

Yes, if you search by domain. A domain search returns ads from every advertiser that advertises that domain, including agencies and resellers. An advertiser-name or ID search returns only that advertiser's ads.

Can I reset the monitor and start over?

Use a new monitorName. The seen-ads memory is stored per name, so a fresh name starts with an empty history while the old one stays intact.

Can I see how many impressions a competitor's ad got?

With includeDetails on, the actor returns the impression ranges Google publishes per country, along with first and last shown dates per country. These are ranges, not exact numbers.

How far back can I go?

The actor reads ads newest first as the Transparency Center lists them, up to maxAdsPerAdvertiser per search. Use dateFrom and dateTo to focus on a window, and raise the cap for a one-off backfill.

Actors used in this guide

Google Ads Transparency Scraper — Competitor Ads | $0.60/1K

Scrape competitor ads from the Google Ads Transparency Center by domain, advertiser name or ID: format, first/last shown, regions, impressions, ad…

$0.60 per 1,000 · ad result

Related guides

How to Track Google Trends Trending Searches by Country

Run the ActorForge Google Trends actor with a list of country codes, schedule it daily with only-new mode, and keep a small pandas history to see…

How to Export Shopify Store Product Data to CSV or JSON

Give the ActorForge Shopify Store Scraper a store URL, let it page through the public products.json feed, then download the dataset as CSV or JSON…

How to Audit Every URL in Your Sitemap and Find Orphan Pages

Run the SEO Audit Crawler with useSitemap on and maxPages above your site's size; it audits every page it can reach plus every sitemap URL, and…

All guides