Google Ads Transparency Scraper & Competitor Ad Monitor
Pricing
from $0.80 / 1,000 ad scrapeds
Google Ads Transparency Scraper & Competitor Ad Monitor
Competitor ad monitor: only the new Google ads your competitors started running, daily or weekly. Google Ads checker: does a website run Google ads? Bulk check domains running Google ads, yes or no per domain. Google Ads Transparency data by brand, domain or ID. Search ad text by OCR.
Pricing
from $0.80 / 1,000 ad scrapeds
Rating
0.0
(0)
Developer
Solid Fetch
Maintained by CommunityActor stats
0
Bookmarked
8
Total users
5
Monthly active users
4 days ago
Last modified
Categories
Features
Share
Google Ads Transparency scraper and Google ad library export for marketers and PPC agencies: see the Google ads your competitors run, set up a Google Ads competitor monitor that sends only the new Google ads every week, or check if a website runs Google ads. With readTextAdsWithOcr on it also reads the words of search text ads from their pictures. As a Google Ads checker it takes a list of domains running Google ads and answers yes or no per domain. You get one row per ad or per domain. Ads cost $1.50 per 1,000 on the free plan; a domain without ads is free, a domain with ads costs $0.006.
{ "search": ["Nike, Inc."], "region": "US", "maxAds": 50 }
One row of the result (shortened, see Output for all fields):
{ "advertiserName": "Nike, Inc.", "advertiserId": "AR16735076323512287233", "format": "VIDEO", "firstShown": "2026-07-07T14:21:15.000Z", "lastShown": "2026-10-03T03:04:46.000Z", "daysShown": 89, "transparencyUrl": "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR07320027051652546561" }
Scrape the Google Ads Transparency Center (Google's public ad library) and get every ad a competitor runs, one row per ad. Type a brand name, a domain or an advertiser ID into one Search box, then filter by country, date, format and platform (Google Search, YouTube, Shopping, Maps, Play). Each row has the advertiser, the format, the first and last shown dates, the days shown and a link to the ad. Turn on adDetails for ads shown in the EU and each row also gets the impression range and the audience targeting the advertiser chose. No login and no browser. Use it as a scraper from the Console, as an API, or from an AI agent through the Apify MCP server.
What's new
- 1 November 2026, search text ads by OCR. New option
readTextAdsWithOcr(off by default) reads the headline, description and display URL of search text ads from the ad picture and marks the rowadTextSource: "ocr". See Search text ads by OCR. - 28 October 2026, one Search box and richer rows. The form starts with one
searchfield: a brand name, a domain or website, an advertiser ID or an advertiser page link. The type is detected, so you can mix them. Ad text (includeAdText) is now on by default for the first 50 ads of a run (adTextLimit), and every ad row hasadvertiserCountryandadvertiserAdCountMax. The form groups the monitor and watch fields in one section. - 28 October 2026, ad text. Headline, long headline, description, call to action and landing pages of video and display ads. See Ad text.
- 27 October 2026, overview with a cost estimate.
overviewOnlysaves one free row per search with Google's ad total andestimatedCostUsd. See Preview before you pay. - 26 October 2026, domain watch and winners filter.
watchChangestells you which domains started or stopped advertising, andminDaysShownkeeps only ads that ran for a long time. See Watch a domain list for changes and Find a competitor's winning ads. - 22 October 2026, monitor groups.
monitorGroupsmonitors several clients in one run. See One run for many clients.
How do I see a competitor's Google ads?
Type the competitor's brand name, website or advertiser ID into the Search box and run the actor. You get one row per ad with its format, first and last shown dates, days shown, the advertiser's country, a link to the ad and, for video and display ads, the headline and description. No login; you pay per saved ad.
How do I check if a website runs Google Ads?
Set the mode to Domain check and put the website in the Search box. You get one row per domain with hasAds true or false, whether an ad ran in the last 30 days, the likely owner of the ads and a rounded ad count. A domain without ads is free; one with ads costs $0.006.
How do I get alerted when a competitor starts a new Google ad?
Turn on onlyNewSinceLastRun, save the input as a task and schedule it daily or weekly. The first run saves the current ads as a free baseline. Every later run returns only the ads that started since the run before, and can post them to Slack or a webhook. A run with nothing new costs only the start fee.
Who is advertising, and who is NOT
Give the actor a list of domains and get one row per domain with a verdict: does it run Google ads, who owns those ads, and is it still running them? The domains with hasAds: false are as useful as the ones with ads, for example prospects for a web design or PPC offer.
{ "mode": "domainCheck", "domains": ["nike.com", "example-shop.pl"], "region": "Poland" }
hasAdsis the yes or no, andactiveLast30Daystells whether the latest ad ran in the last 30 days. A domain withhasAds: false, or withhasAds: trueandactiveLast30Days: false, is a domain that is not advertising now.advertiserNameis the likely owner of the domain, not the agency, reseller or first advertiser Google lists. For nike.com it is "Nike Retail BV" even when WPP Media's ads come first.ownerMatchsays how sure that is:name-match(the advertiser's name fits the domain's brand) orlargest-advertiser(a guess by ad count).detailed: trueaddslast30DaysFormatCounts, the ads of the last 30 days by format.
More in Check which domains advertise.
What you get for your money
- The brand you asked for, not its namesakes. A name search resolves to the brand's own advertiser account (for Allegro, Allegro sp. z o. o.), and switches to the brand's domain when that has many more ads, as with Temu or Shein. Unrelated companies that share the name stay out of your results and your bill.
maxAdscounts per brand you type. Ask for 50 Allegro ads and you get at most 50, even if Google lists several Allegro-like accounts.- Reach and targeting at the same price per ad. With
adDetailson, EU ads get impression ranges, targeting and per-country dates. It costs no extra per ad; the run takes a few seconds longer. - Days served as Google reports them, so the number matches what you see in the Transparency Center.
What people use it for
- Competitor research. Put in the brands you compete with and see which ads they ran, in which format and for how long.
- Video ads. With the Video format, most video ads come with their YouTube link and thumbnail.
- One platform only. Set
platformtoYOUTUBE,SEARCH,SHOPPING,MAPSorPLAYto get only the ads Google showed there, for example a competitor's YouTube ads or their Shopping ads. - Shopping prices. For shopping ads that carry product data, the actor returns the product title, price, currency and seller. Turn on
onlyWithProductsto keep only those ads. - Who advertises for a domain. Search by a domain to see the advertisers whose ads send traffic to it.
- Market scans by country. Filter by country and dates, for example the last 30 days in Poland.
- New competitor ads every day. Turn on
onlyNewSinceLastRunand schedule the run: each run returns only the ads that appeared since the run before. - Reach and targeting of ads shown in the EU. Turn on
adDetailsto get the impression range of each ad, the audience targeting categories the advertiser used, and first and last shown dates per country.
How to use it
- Type one or more brand names, domains or advertiser IDs into the Search box, one per line. Advertiser page URLs from the Transparency Center work too. The actor works out which kind each entry is.
- Optionally pick a country, a date range or a number of recent days, a format (text, image, video) and a platform (Google Search, YouTube, Shopping, Maps, Play).
- Run it and download the results as JSON, CSV or Excel, or read them through the API.
{"search": ["Nike", "Allegro", "Temu", "zalando.pl"],"region": "Poland","shownInLastDays": 30,"maxAds": 200}
search is sorted into names, domains, advertiser IDs and Transparency Center links: an entry like AR16735076323512287233 is an ID, zalando.pl or https://www.zalando.pl/ is a domain, and everything else (Nike, Nike, Inc.) is a brand name. Through the API search can also be one text separated by line breaks or commas; a name with a comma goes on its own line, or is sent in a list. The separate lists advertiserNames, domains, advertiserIds and startUrls still work, and their values are added to the Search box.
Each name, each domain and the list of advertiser IDs is a separate search. maxAds applies to each search, and one run takes up to 500 searches. An ad found by two searches is saved and charged once, and its searchQuery field names the search that found it first.
If a name matches no advertiser, the run goes on with the other searches and lists the name in its status message. If no name in the run matches, the run still finishes successfully with no ads and suggests close advertiser names that Google knows, for example No advertiser matched "Adiddas". Did you mean: adidas?
How brand names are matched
Google suggests advertisers for a name. The actor picks the advertiser whose name starts with your word and has the most ads, and it prefers an advertiser from the country you selected. The log lists the other matching advertisers, and you can pass their IDs to get them too.
Some brands run their ads under another company name. Temu has no advertiser called Temu: in a sample of 100 ads that lead to temu.com, 91 came from ELEMENTARY INNOVATION PTE. LTD. and 9 from Whaleco Inc. When no advertiser carries the name, or when the best one is a small namesake with fewer than 1,000 ads and more ads lead to the brand's domain, the actor searches by that domain instead. A domain search returns every advertiser whose ads lead to the domain, so it can include resellers and app publishers. The fields resolvedBy and matchedAdvertiser show which way each search went. For one exact advertiser, use its ID.
Preview before you pay
Not sure how many ads a brand runs, or what pulling them all would cost? Turn on overviewOnly in ads mode. The run saves one free row per name, domain or ID list instead of the ads, from the first page of Google's results only. No ad event is charged; you pay only the start fee.
{"advertiserNames": ["Nike", "Allegro"],"region": "Poland","overviewOnly": true}
Each row has type: "overview" and:
totalAdsMin,totalAdsMax: the rounded range Google reports for the search (for example 8,000 to 9,000), andapproximateTotalAds, the top of that range.nullwhen Google gives no range.sampleSizeandsampleFormats(text,image,video,unknown): what the first page holds. It is a sample of the most recently shown ads, not the whole list.activeLast30Days: ads of the sample last shown in the last 30 days.earliestFirstShownandlatestLastShown: the date range of the sample.region: the country you searched,nullfor all countries.advertiserName,advertiserIdandmatchedAdvertisersay which account the search resolved to.estimatedCostUsd:approximateTotalAdstimes the current price of one ad on your plan, which is what saving every ad of the search would cost. Left out when Google gave no range or the price is not known (a run that is not pay per event).maxAdsis not applied, so for a capped run use the smaller of the two numbers.
The country, date, format and platform filters apply to the overview, so the total and the cost match the run you plan. maxAds, minDaysShown, adDetails and downloadCreatives are not used, and overviewOnly cannot be combined with onlyNewSinceLastRun. The rows are free, one request per search (plus the name lookup for a brand name), and a restarted run writes each row once. When the numbers look right, run the same input again without overviewOnly.
Monitor new ads
Turn on onlyNewSinceLastRun and each run returns only the ads that the same search has not returned before and that started running since the last run. Schedule it and it works as a daily alert for new competitor ads.
{"advertiserNames": ["Nike", "Adidas"],"region": "Poland","maxAds": 300,"onlyNewSinceLastRun": true}
- The first run is a free baseline. No earlier state exists, so it saves every matching ad as the baseline without charging the ad event for them, and the log and the run's status message say so ("not charged"). You pay only the start fee for that run. From the second run on, only new ads are saved and charged. The baseline is one per list of seen ads: a new search, country or
monitorNamestarts a new free baseline. - You pay only for new ads. Ads seen by earlier runs are skipped before saving and are not charged. A run with nothing new costs only the start fee.
- What counts as new. An ad is new when it is not in the list of seen ads and its first-shown date is not earlier than 2 days before the start of the previous run. An ad that is not in the list but started long before is an old ad that this run happened to read for the first time, because Google returned a different slice of a large advertiser. It is added to the list, not saved and not charged. The log and the status message say how many were skipped this way. An ad without a first-shown date counts as new. The first run has no earlier run to compare with and returns every ad, and so does the run after a first run that stopped at the maximum cost. A later run that stops at the maximum cost, or that searched nothing because no input value could be used, leaves the comparison point where it was, so the ads it did not get to stay new.
- How deep each run looks, and what it can miss. Google lists the ads by last-shown date, newest first, not by first-shown date, and every ad that runs today has today as its last-shown date.
maxAdsis how many ads each search reads. For a brand that runs more ads at once thanmaxAds, which ones you get among the running ads is up to Google, so a new ad can fall outside the ads read that day and show up in a later run, as long as it started within the last 2 days before that run, or not at all. SetmaxAdsabove the number of ads the brand runs at the same time when you can; for brands with hundreds of thousands of ads, use a country, format or platform filter to narrow the search. - Older lists keep working. A list saved before this rule existed has no start time of its own, so the first run after the update takes the day the list was last saved as the previous run.
- One list per search. The list of seen ads belongs to the names, domains, IDs, country, format and platform. Changing
maxAds, dates oradDetailskeeps the same list, changing the country or a name starts a new baseline. GivemonitorNamea name to choose the list yourself, for example to share it between two tasks. - Still running ads. With
includeLastShownChanges, the run also saves ads seen before whose last-shown date moved since the last run. - One summary row per target, free. Each monitor run also saves one row per name, domain or ID list with
type: "summary":advertiserName(the likely owner of the ads read, not the first advertiser listed),advertiserId,newAds,totalActiveAds(ads read by this run that were last shown in the last 30 days),formats(how many of text, image and video the ads have),lastAdDateandbaseline(trueon the first run, whennewAdsis 0). Summary rows are saved without a charge event and are in the dataset view "Monitor summary". They count the ads this run read, up tomaxAdsper search. - Slack or webhook digest. Set
webhookUrl(a Slack incoming webhook or any URL that accepts a JSON POST) and the run posts one digest at its end: per targetnewAdsand up to 5 sample ads withadvertiser,format,firstShownandlink, and a Slack-readytextfield. Nothing is posted for a run without new ads, including the baseline run, unlesswebhookOnEmptyistrue. If the post fails, the run still succeeds: the failure is logged as a warning and the status message saysWEBHOOK FAILED. The URL is stored as a secret input and is never written to the log. - Each new row has
changeType(neworlastShownChanged) andpreviousLastShown. - If a run stops at the maximum cost per run, the ads it did not save stay new for the next run.
- The list is kept in the key-value store
google-ads-transparency-stateon your account and holds up to 50,000 ads per name, so a highermaxAdsis lowered to 50,000 divided by the number of searches, and the run's status says so. When the list is full, the ads seen longest ago are forgotten and count as new if they come back. Two runs with the same name at the same time do not lose each other's ads.
To schedule it in Apify Console:
- Fill in the input above and click Save as a new task.
- Go to Schedules in the left menu, create a schedule with a time, for example every day at 8:00, and add the task to it.
- Run the task once by hand to save the baseline.
- To get the new ads by email, Slack or a webhook, add an integration on the task for "Run succeeded".
One run for many clients
An agency does not need one task per client. Put every client into monitorGroups and one scheduled run checks them one after the other:
{"monitorGroups": [{ "name": "Client A", "domains": ["rival-one.com", "rival-two.com"], "advertiserNames": ["Rival Three Ltd"] },{ "name": "Client B", "advertiserIds": ["AR16735076323512287233"] }],"region": "US","maxAds": 300,"webhookUrl": "https://hooks.slack.com/services/XXX"}
- Each client is a monitor of its own. Its list of seen ads is saved under the client name (
Client Ais saved asClient-A, with characters a state key cannot hold replaced by-; a top-levelmonitorNamebecomes a prefix), so the first run is a free baseline per client and a new competitor for Client B never resets Client A. - Grouped by client. Every ad row and every summary row has
clientName, and the webhook posts one digest per client (titledGoogle Ads monitor for client "Client A", withclientNamein the JSON), only for clients with new ads unlesswebhookOnEmptyis on. - Same settings for all groups.
region, dates,format,platform,maxAds,adDetails, the webhook and the proxy are set once at the top level and apply to every group.maxAdsapplies to each search of each group. - Limits. Up to 20 groups, up to 500 searches per group. Groups run one after the other, not in parallel. If the run reaches the maximum cost, the groups after that point are not checked and the status message says how many. A failure (Google blocking the run, for example) fails the run, and the clients after it are not checked; the clients before it are saved, and starting the run again does not charge them twice. A group name may be used only once, and the top-level
domains,advertiserNames,advertiserIds,startUrlsmust stay empty. - An existing monitor can be taken over: a group named like an earlier
monitorNamecontinues that list of seen ads, so moving a client from its own task intomonitorGroupsdoes not start a new baseline.
Large monitors
A monitor remembers up to 50,000 ads (all its searches together) so it can tell what is new. Three things to know before you point one at big advertisers:
- Split by domain when the total is big. One task with several large brands shares that 50,000. Put each big advertiser or domain into its own task (or its own
monitorGroupsclient, which keeps a separate list per client) so each one has the full room, a new baseline for one never touches the others, and one failure does not stop the rest. Do this when the searches add up to more than about 50,000 ads, or when one brand is far bigger than the rest. UseoverviewOnlyfirst to see each brand's total. maxAdsis lowered automatically. IfmaxAdstimes the number of searches is above 50,000, the run lowersmaxAdsto 50,000 divided by the number of searches and says so in a log warning at the start of the run ("maxAds" 5000 was lowered to 2500 for each of the 20 searches). When the list is full the ads seen longest ago are forgotten and count as new if they come back, so a list that is too small makes old ads look new.- What it costs. The first run of a monitor is free: it saves the baseline with no ad charge, so a 10,000-ad baseline costs only the start fee. After that you pay for new ads only. At $1.50 per 1,000 ads (free plan; $1.20 Bronze, $1.00 Silver, $0.80 Gold) saving the same 10,000 ads in a normal run would be $15 ($12, $10, $8). A typical weekly run is a small fraction of that: for example 300 new ads in a week is about $0.45 on the free plan ($0.24 on Gold), and a week with nothing new costs only the start fee. Your own number is the new ads a week times the price;
overviewOnlyand the first weeks of runs show it.
Find a competitor's winning ads
An ad that an advertiser keeps paying for is usually one that works. Every ad row has daysShown (how many days Google served it) and isLongRunning, which is true from 30 days on and null when Google gave no days. Set minDaysShown to keep only ads served at least that long:
{"advertiserNames": ["Nike"],"region": "Poland","minDaysShown": 90,"maxAds": 50}
- Ads below
minDaysShownare not saved and not charged, andmaxAdscounts the ads that pass, so 50 means 50 long-running ads. An ad whose number of days is unknown is left out too. - Google lists the most recently shown ads first, and long-running ads that stopped a while ago can be deep in the list. The actor reads pages until it has
maxAdsads that pass, and stops a search after 10 pages in a row without one (the log says so). RaisemaxAdsto look deeper. daysShownis days Google served the ad, not a measure of results. A long run is a hint that the ad pays off, not proof. Sort the dataset bydaysShownin your sheet to get the longest first.- Works together with
onlyNewSinceLastRun: an ad that is still too young is not remembered, so it comes back as new once it passesminDaysShown.
Check which domains advertise
Set mode to domainCheck to answer one question for a list of domains: does this domain advertise on Google? Each domain gets one row from one small request to Google, with no preview or detail requests.
{"mode": "domainCheck","domains": ["nike.com", "https://www.zalando.de/damen/", "example-shop.pl"],"region": "Poland"}
A row for nike.com, built from a recorded Google response:
{"domain": "nike.com","input": "https://www.nike.com/pl/","hasAds": true,"advertiserName": "Nike, Inc.","advertisers": [{ "id": "AR16735076323512287233", "name": "Nike, Inc." },{ "id": "AR18378488041124659201", "name": "Nike Retail BV" }],"adCountEstimate": { "min": 100000, "max": 200000 },"latestShown": "2026-10-03T08:58:03.000Z","formats": ["TEXT"],"sampleFormatCounts": { "text": 5, "image": 0, "video": 0 },"sampleSize": 5,"likelyOwner": { "id": "AR16735076323512287233", "name": "Nike, Inc.", "ids": ["AR16735076323512287233"] },"ownerMatch": "name-match","otherAdvertisers": [{ "id": "AR18378488041124659201", "ids": ["AR18378488041124659201"], "name": "Nike Retail BV", "nameMatch": true }],"activeLast30Days": true,"daysSinceLastAd": 10,"adsLast30Days": 5,"adsLast30DaysIsSample": true,"sampleIsCapped": true,"firstSeen": "2023-11-16T22:23:39.000Z","checkedAt": "2026-10-13T12:00:00.000Z","region": null,"error": null}
inputis the value exactly as you sent it (https://www.nike.com/pl/), so you can map a row back to your list.domainis the cleaned host.hasAdsistruewhen the Transparency Center lists at least one ad that leads to the domain, in the chosen country if you setregion. It is not limited to ads running today: look atlatestShownto see how recent they are.advertiserslists up to 5 advertisers from the first 20 ads Google shows for the domain, with their IDs, so you can pass an ID to the ads mode to get that advertiser's ads.adCountEstimateis the rounded total Google reports for the domain, as a range. It is{ "min": 0, "max": 0 }for a domain without ads.formatsare the formats seen among those first 20 ads (TEXT,IMAGE,VIDEO), not across every ad of the domain.advertiserNameis the name oflikelyOwner: the company that probably owns the domain, never an agency, reseller or whichever advertiser is listed first.nullfor a domain without ads or one that could not be checked.detailed(off by default) addslast30DaysFormatCounts(text,image,video) to each row: the fetched ads last shown in the last 30 days, by format. It uses the page already fetched, so it costs no extra request and no extra charge. There is no split by channel (Search, YouTube, Shopping, Display, Play): the list Google returns for a domain does not say where an ad ran, and finding out would take one request per ad.likelyOwnerandownerMatchsay who probably owns the domain, from the same first 20 ads.name-match: the advertiser's name starts with the domain's brand or has it as a word (for nike.com, Nike Retail BV; for booking.com, Booking.com B.V.), and with several such companies the one with the most ads on the page wins, except that a branch (a name with Podruznica, Branch, Filiale, Sucursal and the like, as in "Samsung Electronics Austria GmbH Podruznica Zagreb") loses to any other company of the brand. Advertiser IDs that carry the same name (booking.com, mcdonalds.com and hm.com each have one company under two IDs) count as one company, andlikelyOwner.idslists all of its IDs, the one with the most ads first (id).largest-advertiser: no name fits, so it is the advertiser with the most ads on the page, which is a guess. Both arenullfor a domain without ads. It is a guess from names and a 20-ad sample, not a verified owner, and a brand whose company has a different name (Temu, run by Whaleco) getslargest-advertiser.otherAdvertiserslists up to 5 more companies from the page, one entry per name with all its advertiser IDs inids(idis the one with the most ads), each withnameMatch.falsemeans the name does not fit the brand, which is how agencies and resellers (for nike.com, WPP and M Media Group) show up;trueis another company of the brand.activeLast30DaysistruewhenlatestShownis at most 30 days beforecheckedAt, andfalsefor a domain with no ads.daysSinceLastAdis the whole UTC days fromlatestShowntocheckedAt, ornullwithout ads. Both rest on the latest last-shown date among the first 20 ads, which Google lists newest first.sampleFormatCountscounts the ads on the fetched page astext,imageandvideo, andsampleSizeis how many ads that is (at most 20); ads of an unknown format are in none. It is a sample, not the domain's totals: Google's answer carries no per-format totals, and getting them would take extra requests per domain. Do not read it as a distribution of the domain's 80,000 ads. (This field was calledformatCountsuntil 21 October 2026; it was one day old, and the old name suggested totals, so it was renamed without an alias. Update a saved view or Clay column that used it.)- Counts are from the first N ads, not from every ad. Google returns the 20 most recent ads of a domain, so
sampleFormatCounts,adsLast30Days,latestShownandfirstSeendescribe those ads only.sampleIsCappedsays how far to trust them:truemeans Google has more ads than the page held (the counts are lower bounds andfirstSeenmay be later than the domain's first ad),falsemeans the page held every ad and the counts are complete. The only total in Google's answer isadCountEstimate, a rounded range over all ads ever listed for the domain (ended ones included); itsminis a lower bound. Google's answer has no earlier first-shown date than the ones in the page, so there is no true first-ever date in this mode. adsLast30Daysis how many of the fetched ads were last shown within 30 days ofcheckedAt.adsLast30DaysIsSampleistruewhen Google has more ads than the page held, so the number is a lower bound;falsemeans the page held every ad.firstSeenis the earliest first-shown date among the fetched ads, not necessarily the domain's first ad. All three come from the page already fetched, with no extra request.- A domain that could not be checked has
nullin all the sample and activity fields (sampleIsCappedtoo),likelyOwner,ownerMatch,activeLast30DaysanddaysSinceLastAd. - Up to 10,000 domains per run, about 100 a minute (1,500 domains took 15 minutes in a test run on 8 November 2026). The default timeout of 2 hours fits a full list of 10,000; if a run times out, the domains saved so far stay in the dataset, so run the rest in a new run. Full URLs work: the scheme, path and
www.are removed, international names are saved as punycode (żółć.plbecomesxn--kda4b0koi.pl, however you write it), and a domain listed twice is checked once, with theinputof its first mention. - Not a domain, not checked, not charged. IP addresses, e-mail addresses (
mailto:foo@bar.com), empty strings and names without a public suffix (localhost,printer.local) each get a row withhasAds: nulland the reason inerror. The check looks at the last label (a plain top-level domain such as.com,.plor a punycode one), it does not use the full public suffix list. - You pay only for domains that run Google ads. A domain with no ads found (
hasAds: false) is saved as a free row, and so is a domain that could not be checked (Google kept failing after the retries, or the value is not a domain:hasAds: nullwith anerror). Only a domain withhasAds: trueis charged. One failing domain does not stop the run; after 10 failed domains in a row the run stops, saves the rest as not checked and fails, since Google is then blocking it. - Two domains are checked at the same time. The run goes back to one request at a time after any failed domain or any CAPTCHA/429 from Google, and rows are always saved in input order.
- Only
domains,region,detailedand the watch fields (watchChanges,monitorName,onlyChanges,webhookUrl,webhookOnEmpty) are used in this mode. The ads-mode filters,maxAds,minDaysShownandonlyNewSinceLastRunare ignored, and the log names the ones you set. See Watch a domain list for changes.
Watch a domain list for changes
Turn a list of domains into a weekly alert. Set watchChanges to true, give the run a monitorName and schedule it (for example every Monday). Each run compares every domain with the answer the previous run of the same monitorName stored, in the named key-value store google-ads-transparency-state on your account (record domainwatch-<monitorName>).
{"mode": "domainCheck","domainsText": "shop-one.com\nshop-two.pl\nhttps://www.shop-three.de/","region": "PL","watchChanges": true,"monitorName": "prospects-weekly","webhookUrl": "https://hooks.slack.com/services/..."}
Each row gets three more fields:
change:first-check(no earlier answer for this domain under thismonitorName),started-advertising(no ads before, ads now),stopped-advertising(ads before, none now),owner-changed(ads both times, but the likely owner is another company; names are compared ignoring case and punctuation) orunchanged. It isnullfor a domain that could not be checked, and that domain keeps its earlier stored answer.previousOwnerandpreviousCheckedAt: the advertiser and the time of the earlier answer, ornullwhen there was none.
What is saved and charged:
onlyChanges(on by default withwatchChanges): domains that areunchangedare left out of the dataset. They are not saved and not charged, even when they run ads. TurnonlyChangesoff to get a row for every domain.- Charging is the same as in a plain domain check. A saved row of a domain with ads (
hasAds: true) is charged; a domain with no ads and a domain that could not be checked are free. So withonlyChangeson you pay for the domains that started advertising, changed owner or were checked for the first time with ads, and with it off for every domain with ads. No new price or event is involved. - The first run is the baseline: every domain is
first-check, and no digest is sent (nothing changed yet) unlesswebhookOnEmptyis on. webhookUrlworks here too. At the end of a run with changed domains (started-advertising,stopped-advertising,owner-changed), one JSON digest is posted:kind: "domainWatch",monitorName,checked,changedDomains,changes(domain, change, advertiserName, previousOwner) and a Slack-readytext.webhookOnEmptyposts it also when nothing changed. A failed post is a warning and a note in the status message, not a failed run.- Every run stores the answers of the domains it saved or left out as unchanged, at the end and every 50 domains. A run that stops at the maximum cost stores only the domains it got to, and a domain whose row was not saved is reported again next time.
What "stopped advertising" means. It means the Transparency Center showed no ad for the domain when the run checked it, in the country you set with region (all countries without it). It is not a statement about the advertiser's budget or about Google Ads in general: Google updates the Transparency Center with a delay, ads from before the window it lists can drop out, and a pause of a few days can look like a stop. Read it as "no ad is listed now"; latestShown and daysSinceLastAd in the row say how recent the last ad was. The same delay applies to started-advertising.
Who is NOT advertising on Google: warm and cold leads
Split a prospect list in one run. Paste the domains, set region to the market of the prospects and read two columns:
{"mode": "domainCheck","domainsText": "shop-one.com\nshop-two.pl\nhttps://www.shop-three.de/","region": "PL"}
| Lead | How to filter the rows | Pitch |
|---|---|---|
| Warm | hasAds: true and activeLast30Days: true | Already pays for Google ads: audit, creative refresh, better reporting. |
| Lapsed | hasAds: true and activeLast30Days: false | Advertised before and stopped: win-back, new campaign. |
| Cold | hasAds: false | Never shows in the Transparency Center: start advertising. |
Only the warm and lapsed rows are charged; the cold rows, the ones you may want most, are free. Without region, a domain that advertises only abroad counts as warm, so set it. hasAds: false means no ad is listed now; a domain can still advertise in a way the Transparency Center does not list, so treat cold as a lead to check, not a fact. Schedule the same list with watchChanges (see above) to be told when a cold lead starts advertising.
Look up advertisers
Set mode to advertisers to find the advertiser accounts behind brand names and domains, with their IDs, before you scrape their ads or to map a list of brands to Transparency Center accounts. Each advertiser is one row.
{"mode": "advertisers","advertiserNames": ["Nike", "Allegro"],"domains": ["zalando.de"],"region": "Poland"}
The first row for Nike, built from a recorded Google response:
{"query": "Nike","queryType": "name","advertiserId": "AR18378488041124659201","name": "Nike Retail BV","country": "NL","adCountEstimate": { "min": 80000, "max": 90000 },"isBestMatch": true,"transparencyUrl": "https://adstransparency.google.com/advertiser/AR18378488041124659201","region": null,"checkedAt": "2026-10-13T12:00:00.000Z","error": null}
- A name returns every advertiser Google suggests whose name contains that name as a whole word: Nike finds Nike Retail BV, Nike, Inc. and NIKE SRL, but not Nikesh. The best match comes first, then the others by ad count.
isBestMatchmarks the account the ads mode searches for that name (see How brand names are matched). When the ads mode would search the brand's domain instead, as for Shein or Temu, no row of that name is marked. Domains never have a best match.- A domain returns the advertisers on the first 40 ads that lead to it, in the chosen country if you set
region. Theircountryisnull, since Google lists it only with name suggestions. countryis the country of the advertiser account as Google lists it.adCountEstimateis Google's rounded count of the advertiser's ads in all countries, as a range.- An advertiser found by two names or domains is saved and charged once, under the first one that found it.
- Up to 500 names and domains per run. Duplicates are looked up once, and full URLs work for domains.
- Advertiser lookups are free for now; a per-advertiser price will be announced on the Pricing tab two weeks ahead. A name or domain without advertisers is saved once with
advertiserId: nulland the reason inerror, and is not charged. A lookup that could not be done (Google kept failing after the retries, or the value is not a name or a domain) is saved the same way, free. After 10 failed lookups in a row the run stops, saves the rest as not looked up and fails, since Google is then blocking it. - Only
advertiserNames,domainsandregionare used in this mode. The other filters are ignored, and the log names the ones you set.
Input
| Field | Description |
|---|---|
search | Brand names, domains, advertiser IDs or Transparency Center links, one per entry, type detected for you. In domainCheck mode the domains are checked; in advertisers mode the names and domains are looked up. Added to the older lists below. |
mode | ads (default) returns ads. domainCheck returns one row per domain in domains saying whether it advertises. See Check which domains advertise. advertisers returns one row per advertiser account matching advertiserNames or running ads for domains. See Look up advertisers. |
advertiserNames | Brand or advertiser names, one search each. Names that differ only in case or punctuation (Nike, nike, NIKE ) are searched once, and the log says so. |
advertiserNamesText | The same names as one text, one per line (or separated by commas when there is no line break). For tools that send a column as one value, see Use it from Clay, Google Sheets or Make. |
advertiserIds | Advertiser IDs (AR followed by 20 digits) or advertiser page URLs. All IDs are searched together. An ID that is not valid does not stop the run: it is saved as a free row with searchQuery (your value) and error, and the valid IDs are searched. |
startUrls | Links copied from adstransparency.google.com: an advertiser page or a creative page (that advertiser is searched) and a domain search page (that domain is searched). A region=PL in the links sets region when it is empty. Other links are saved as free rows with an error. |
domains | Domains the ads point to, one search each. In domain check mode, the domains to check (up to 10,000). Full URLs work: the actor keeps the host and drops www.. |
domainsText | The same domains as one text, separated by line breaks, spaces, commas or semicolons. |
region | Country as a two-letter code (US, PL, DE, UK) or an English name (Poland, United Kingdom). The Console form starts with US; a run through the API without region searches all countries. |
dateFrom, dateTo | Ads shown at some point inside this range (YYYY-MM-DD). Google allows this filter only for the last 364 days. If you set one date, the actor fills the other. |
shownInLastDays | Ads shown at some point in the last N days, today included (1 to 364). An ad that started months ago and still runs is included; for only new ads use onlyNewSinceLastRun. Use it instead of fixed dates in saved tasks and schedules, so the range moves with every run. |
format | ALL, TEXT, IMAGE or VIDEO. |
platform | ALL, SEARCH, YOUTUBE, SHOPPING, MAPS or PLAY: only ads Google showed on that platform. |
videoLinks | Adds videoUrl and thumbnailUrl to video ads. On by default for the Video format, off for the others. |
previewImages | On by default. For image ads that Google only shows through a preview, reads that preview file (about 0.2 MB, 4 at a time) and fills imageUrl. It adds roughly 0.25 s per such ad to the run and does not change the price. Turn it off for the fastest run. |
includeAdText | On by default. Reads the preview file of each video and image ad (4 at a time) and adds the ad's words: headline, longHeadline, description, displayUrl, callToAction, landingUrl/landingUrls, and adTextStatus. A preview already read for videoUrl or imageUrl is not read again: at most one preview request per ad. The price per ad stays the same. Bounded by adTextLimit. See Ad text. |
adTextLimit | 50 by default. Only the first N ads of a run have their preview read for ad text, so a large run does not get slower; the later ads come back with adTextStatus: "not-fetched" (unless their text was already there). 0 reads every ad. Also caps the pictures read by readTextAdsWithOcr. |
readTextAdsWithOcr | Off by default. Reads the headline, description and display URL of classic search text ads from the picture of the ad with OCR, and marks the row adTextSource: "ocr". The words can be wrong: see Search text ads by OCR. No landing URL, same price per ad. |
maxAds | Ads per search. Maximum 100,000. When you do not send it, each search stops at 100 ads and the log opens with a warning that names the cap and the most a run can charge. |
adDetails | Off by default. Reads each ad's detail page and adds impressionsMin, impressionsMax, impressionsAvailableFrom, targeting and regionStats. This is one extra request per ad, so runs take longer. The price per ad stays the same. |
flattenRegions | Off by default, needs adDetails. Adds flat columns per country for the countries in each ad's regionStats: impressions_DE_min, impressions_DE_max, shown_DE_first, shown_DE_last (any two-letter country code in place of DE). See Flat columns per country. |
downloadCreatives | Off by default. Saves each ad's image as a file in the run's key-value store and adds creativeFileUrl. Stored files are free for now (a per-file price will be announced on the Pricing tab with the usual two weeks' notice). See Download the ad images. |
overviewOnly | Off by default. Ads mode: saves one free overview row per search (rounded total, formats, active ads, estimated cost) instead of the ads. See Preview before you pay. |
onlyNewSinceLastRun | Off by default. Saves only ads this search has not returned in earlier runs. See Monitor new ads. |
monitorName | Optional name of the list of seen ads, for onlyNewSinceLastRun; required for watchChanges, where it names the stored answers of the domain list. Empty means one list per search. The old name stateKey still works. |
includeLastShownChanges | With onlyNewSinceLastRun, also saves ads seen before whose last-shown date moved. |
webhookUrl | With onlyNewSinceLastRun or watchChanges: a Slack incoming webhook or any URL. One JSON digest is posted at the end of a run with new ads. A failed post is a warning and a note in the status message, not a failed run. |
monitorGroups | Several clients in one run: [{ "name": "Client A", "domains": [...], "advertiserNames": [...], "advertiserIds": [...] }]. Replaces the Search box and the brand, domain and ID fields. See One run for many clients. |
webhookOnEmpty | Also post the digest when no ad is new (and on the baseline run). Needs webhookUrl. |
detailed | Domain check only. Adds last30DaysFormatCounts to each row. |
minDaysShown | Ads mode. Keep only ads Google served for at least this many days (1 to 3,650). The others are not saved or charged and maxAds counts the ads kept. See Find a competitor's winning ads. |
watchChanges | Domain check mode, off by default. Compares each domain with the previous run of the same monitorName and adds change, previousOwner and previousCheckedAt. Needs monitorName. See Watch a domain list for changes. |
onlyChanges | With watchChanges, on by default: domains that did not change are not saved and not charged. Off: every domain gets a row. |
onlyWithProducts | Keep only shopping ads that come with product data. The other ads are skipped before saving, so you are not charged for them, and maxAds counts the ads kept. |
proxyConfiguration | Apify Proxy is on by default. When Google answers with a CAPTCHA page, the actor retries on a new IP. With the default Apify Proxy, the first CAPTCHA moves the run to Apify residential proxy when the run has access to it. |
Output
A real video ad row:
{"searchQuery": "Nike, Inc.","resolvedBy": "advertiser","matchedAdvertiser": "Nike, Inc.","advertiserId": "AR16735076323512287233","advertiserName": "Nike, Inc.","advertiserCountry": "US","advertiserAdCountMax": 10000,"domain": null,"creativeId": "CR07320027051652546561","format": "VIDEO","firstShown": "2026-07-07T14:21:15.000Z","lastShown": "2026-10-03T03:04:46.000Z","daysShown": 89,"imageUrl": null,"videoUrl": "https://www.youtube.com/watch?v=grj7FyOMWh8","thumbnailUrl": "https://i.ytimg.com/vi/grj7FyOMWh8/hqdefault.jpg","landingUrl": "https://www.nike.com/ca/retail","previewScriptUrl": "https://displayads-formats.googleusercontent.com/ads/preview/content.js?client=ads-integrity-transparency&...","transparencyUrl": "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR07320027051652546561","headline": null,"description": null,"displayUrl": null,"businessName": null,"sitelinks": [],"products": [],"impressionsMin": null,"impressionsMax": null,"impressionsAvailableFrom": null,"targeting": null,"regionStats": null,"creativeFileUrl": null}
| Field | Description |
|---|---|
searchQuery | The name, domain or ID list that found the ad. |
resolvedBy | How the search found its target: advertiser (a name matched an advertiser), domain (a name was searched as the brand's domain), id or domain-input (you gave IDs or a domain). |
matchedAdvertiser | The advertiser name, domain or ID list the search ran on. |
advertiserId, advertiserName | Advertiser as Google shows it. The AR… ID is the public advertiser ID of the Transparency Center. |
advertiserCountry, advertiserAdCountMax | Country Google lists for the advertiser account and the top of the rounded number of ads the account has in all countries. Read from the advertiser list the actor already uses for a brand name search; for a domain, ID or link search the actor looks up each advertiser once (at most 20 extra requests per run). null when Google does not list the account. |
domain | Filled when the search ran on a domain. |
creativeId, transparencyUrl | The ad's ID and its page in the Transparency Center. |
format | TEXT, IMAGE, VIDEO, or UNKNOWN for a format code the actor does not know. |
isLongRunning | true when daysShown is 30 or more, false below it, null when the number of days is unknown. |
firstShown, lastShown, daysShown | First and last time Google served the ad, in UTC, and the number of days Google served the ad, as the Transparency Center reports it (when Google leaves it out, the calendar days from the first to the last, both included). |
imageUrl | An image of the ad, when Google has one. Most text ads have it with Google's own image link. Image ads without one get Google's thumbnail of the ad image or product photo (encrypted-tbn…gstatic.com) from their preview, unless previewImages is off or the preview could not be read. Text ads that carry only a preview, and video ads, keep it empty. |
creativeFileUrl | With downloadCreatives on, the link to the ad's image file in the run's key-value store. Null when downloadCreatives is off, the ad has no image or the download failed. |
videoUrl, thumbnailUrl | YouTube link and thumbnail of a video ad. |
landingUrl | The landing page URL of the ad, when Google's preview includes it. Filled for some video and image ads only (from the preview file the actor already reads for videoUrl and imageUrl); null for text ads and for every ad whose preview has no advertiser link. No extra requests and no extra cost. |
previewScriptUrl | Google's preview script for ads that Google renders in a browser. It is a JavaScript file that draws the ad in a browser, not an image. |
products | Shopping ads: { title, price, currency, seller, url } for each product. |
headline, description, displayUrl, businessName, sitelinks | Ad text, only when Google puts it into the preview. With includeAdText on, also read from the preview file of video and HTML/display ads. With readTextAdsWithOcr on, headline, description, displayUrl and businessName of search text ads are read from the picture of the ad. |
adTextSource, adTextOcrError | Only with readTextAdsWithOcr on. adTextSource is ocr (read from the picture, can contain errors), preview (from the ad's preview) or null (no text). adTextOcrError says why a search text ad has no text (download or OCR failed, the picture shows no text ad, limit reached) and is null otherwise. |
longHeadline, callToAction, landingUrls, adTextStatus | Only with includeAdText on. landingUrls lists every landing page the preview carries. adTextStatus is resolved (the ad has a headline, long headline or description, also when readTextAdsWithOcr read it from the picture), image-only (Google keeps only a picture of the ad, or its preview has no text), not-fetched (no preview, or skipped after repeated failures on the page) or failed (the preview could not be read). |
EU ad details
With adDetails on, every row gets these fields. A real Allegro search ad shown in Poland and Germany:
{"impressionsMin": 20000,"impressionsMax": 25000,"impressionsAvailableFrom": null,"targeting": {"included": ["demographics", "locations", "contextualSignals"],"excluded": ["contextualSignals", "customerLists"]},"regionStats": [{"country": "PL","firstShown": "2023-03-01","lastShown": "2026-10-03","impressionsMin": 20000,"impressionsMax": 25000,"platforms": [{ "platform": "YOUTUBE", "impressionsMin": 0, "impressionsMax": 1000 },{ "platform": "GOOGLE_SEARCH", "impressionsMin": 20000, "impressionsMax": 25000 }]},{"country": "DE","firstShown": "2023-03-04","lastShown": "2026-08-23","impressionsMin": 0,"impressionsMax": 1000,"platforms": [{ "platform": "GOOGLE_SEARCH", "impressionsMin": 0, "impressionsMax": 1000 }]}]}
| Field | Description |
|---|---|
impressionsMin, impressionsMax | Google's impression range for the ad, summed over the countries where Google reports impressions. The top range is open, so an ad shown more than 10 million times has impressionsMin 10000000 and impressionsMax null. |
impressionsAvailableFrom | Google publishes impressions about 90 days after an ad starts. Until then the range is null and this field holds the date Google gives for it. |
targeting | Audience targeting categories from the advertiser's latest campaign: demographics, locations, contextualSignals, topicsOfInterest and customerLists. A category can be in both lists when some of its criteria were included and others excluded. Categories the advertiser did not use are in neither list. Google shows the categories only, not the exact ages, places or topics. |
regionStats | One entry per country where the ad ran, the countries with the most impressions first: country (two-letter code), firstShown and lastShown dates, the impression range in that country and its split by platforms (GOOGLE_SEARCH, YOUTUBE, GOOGLE_SHOPPING, GOOGLE_MAPS, GOOGLE_PLAY). |
These fields are null when adDetails is off. If the details of an ad cannot be read, the ad is still saved without them and its regionStats is null, while an ad whose details were read always has a regionStats list. The log says how many ads on each page were affected, and the run's status message gives the total. After 20 failed detail requests in a row the actor stops asking for details for the rest of the run and says so in the status message.
Flat columns per country
regionStats is a list inside each row, which a spreadsheet cannot show as columns. Set flattenRegions to true (with adDetails) and every row also gets four columns for each country that ad ran in, taken from regionStats:
{"impressions_DE_min": 20000,"impressions_DE_max": 25000,"shown_DE_first": "2026-05-12","shown_DE_last": "2026-10-04","impressions_PL_min": 50000,"impressions_PL_max": 60000,"shown_PL_first": "2026-04-02","shown_PL_last": "2026-10-04"}
- Only the countries an ad ran in get columns, so ads that ran in different countries have different columns; a Google Sheets or CSV export joins them into one wide table with empty cells. A country with no published impressions has
nullin its two impressions columns. regionStatsstays in the row, and withflattenRegionsoff (the default) the output is unchanged. It costs no extra request and no extra charge.
What we measured on 2026-10-04:
- Google gives impressions and targeting for ads shown in the EU. We also saw impression ranges for Norway, Iceland and Turkey. For other countries, such as the US, Brazil or Japan,
regionStatshas only the last shown date and the ad has no impressions or targeting. - On the newest 37 ads of Allegro, Zalando, mBank, InPost and Lidl, all 37 had targeting and 19 had an impression range. The other 18 had started less than 90 days before, and each of them had an
impressionsAvailableFromdate. - A US Nike text ad and a US Nike video ad came back with one country entry, the US, with its last shown date only.
What to expect from the other fields, from our test runs:
- Dates, advertiser, format and the ad page link are filled for every ad.
- 21 of 30 Samsung video ads and 26 of 40 video ads leading to nike.com came with a YouTube link. The other video ads had an image instead, or their preview held no YouTube video.
- Product data came with 12 of 40 Allegro image ads in Poland. Brands that do not run shopping ads have none.
- Without
includeAdText, ad text is rare: Google put a headline into the preview link for about 1 to 2 ads in 100. See Ad text for what the option adds.
Ad text
With includeAdText on (the default), the actor reads the text of video ads and HTML/display ads from their preview file: headline, long headline, description, visible URL, call to action and landing page. A real Samsung video ad:
{"headline": "Buy Now Starting At $249.99","longHeadline": "Immerse Yourself Fully Into Hi-Res Audio: Buy Samsung Galaxy Buds4 Pro Now From $249.99.","description": "Get Closer To The Sounds You Love. Save On Galaxy Buds4 Pro.","displayUrl": "samsung.com","callToAction": "Shop now","landingUrl": "https://www.samsung.com/us/audio-sound/galaxy-buds4-pro/buy/galaxy-buds4-pro-white-sku-sm-r640nzwaxar/","landingUrls": ["https://www.samsung.com/us/audio-sound/galaxy-buds4-pro/buy/galaxy-buds4-pro-white-sku-sm-r640nzwaxar/"],"adTextStatus": "resolved"}
Classic search text ads have no text in the preview. Google archives them as a picture of the ad (imageUrl on tpc.googlesyndication.com/archive/simgad/…). By default this actor does not read the words off that picture, so these ads come back with adTextStatus: "image-only" and null headline, description and displayUrl; save imageUrl if you need the copy. Turn on readTextAdsWithOcr to have the actor read the picture for you, see Search text ads by OCR. Image ads whose preview is only a picture or a product photo get image-only too.
The text is part of the ad: no extra charge. A preview read for videoUrl or imageUrl is reused, so each ad costs at most one preview request. Image ads are already read for their picture (previewImages), so ad text adds requests only for video ads, and only for the first adTextLimit (50) ads of a run, 4 at a time. Set includeAdText to false for the fastest run, or adTextLimit to 0 to read every ad of a large run.
Search text ads by OCR
Set readTextAdsWithOcr to true and the actor reads each classic search text ad from its picture with OCR (Tesseract). It fills headline, description, displayUrl and businessName, sets adTextStatus to resolved, and writes adTextSource: "ocr" on the row so you can tell these words from the ones Google puts in a preview.
- Measured after adding languages, 2 November 2026. bosch.de with
regionDE: 15 of 15 read (9 of 15 before). allegro.pl withregionPL: 9 of 15 read, 34 Polish letters such as ł, ą and ź in the text (1 before); 5 pictures still showed no text ad layout. Both runs on the default 256 MB, peak about 170 MB. - Languages. English is always read. With a
region, the language of that country is added: German (DE, AT, CH), Polish (PL), Portuguese (PT, BR), French (FR, BE, CH), Spanish (ES, MX, AR, CO, CL) and Italian (IT, CH). With noregion, or an English-speaking one, only English is read, so accents of other languages are still lost then. One added language about doubles the time per picture (measured: English 0.26 s, English plus German 0.49 s, English plus all six 1.4 s) and adds about 25 MB of memory per OCR process. - It is opt-in and off by default. With it off, rows, charges and speed are exactly as before.
- Measured on 1 November 2026. 20 text ads of geico.com on the default 256 MB: 18 read from the picture, 1 already had text in its preview, 1 picture was not a text ad layout. The run took 133 seconds, about 6 seconds per ad. Give the run more memory for a large list.
- A second check on the same day. 60 text ads of hubspot.com, allstate.com, bosch.de and allegro.pl with 1 GB: 40 read from the picture, 1 from its preview, 18 pictures were not a text ad layout and 1 had no picture, in 245 seconds. allstate.com had 14 of 15 read, allegro.pl 5 of 15. English and German read well; Polish and Portuguese letters came out wrong, such as "Sprawdz" for "Sprawdź". Both checks were made before the languages above were added, with English only.
- OCR can be wrong. It can drop or change words and punctuation, and it reads English best: accented letters and symbols are still sometimes misread, and always when the run has no
region, and a text that Google cut with "..." stays cut. TreatadTextSource: "ocr"text as a good draft, not as exact copy. - There is no landing URL. A search text ad picture shows only the display URL, so
landingUrlstays null anddisplayUrlis not a landing page. Sitelinks, prices and ratings that are drawn in the picture are not extracted. - A picture that cannot be read does not fail the run or the row. The text stays null,
adTextSourceis null andadTextOcrErrorsays why. The log line "Search text ads read by OCR" counts the pictures read, unreadable and failed. After 5 failures in a row the actor stops reading and the other ads say so. - Only search text ads without text are read. Video, display and image ads, and text ads whose text is already in the preview, are not downloaded for OCR.
- Same price per ad. There is no extra charge.
adTextLimit(50 by default) also caps how many pictures are read, so set it to0to read every text ad of a large run. Pictures are read one at a time on the default 256 MB of memory, and a few at a time with 1 GB or more. The picture is read in memory and not stored; withdownloadCreativeson it is downloaded once more to be stored.
Your own use of the text, and the advertiser's rights in it, are your responsibility.
Download the ad images
Google's image links can stop working when an ad is taken down. Turn on downloadCreatives to keep a copy: the actor downloads the image in imageUrl of each saved ad (the archived ad image or the picture from its preview) and stores it in the run's default key-value store under creative-<creativeId>.<ext>, for example creative-CR13820127804068462593.png. The row gets creativeFileUrl, the link to that record:
{"creativeId": "CR13820127804068462593","format": "IMAGE","imageUrl": "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9Gc...","creativeFileUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/creative-CR13820127804068462593.png"}
- Video ads are not downloaded as video. They keep their YouTube link in
videoUrl; only when Google has an archived still image of a video ad inimageUrlis that image stored. - Only JPEG, PNG, GIF, WebP and AVIF files up to 5 MB are stored. Each download gets 20 seconds, 4 run at a time.
- A failed download leaves
creativeFileUrlnull, is not charged and never fails the run. After 5 failed downloads in a row the actor stops downloading for the rest of the run, saves the remaining ads withcreativeFileUrlnull and says so in the status message. - With
previewImagesoff, only images that Google archives are stored, not the pictures from ad previews. - Stored files are free for now; a per-file price will come later with two weeks' notice. Ads without an image cost the same as before.
For agencies
Three recipes, with the exact input for each.
1. Pitch: show a prospect what their competitors run. One run, every ad of three competitors from the last 90 days, with format, dates and a link to each ad. Cost: the number of ads saved times the per-ad price (300 ads is about $0.45 on the free plan). Turn on includeAdText to get the words of video and display ads; for search text ads it returns the picture link, so open transparencyUrl or turn on readTextAdsWithOcr to read their words from the picture.
{"advertiserNames": ["Competitor A", "Competitor B", "Competitor C"],"region": "US","shownInLastDays": 90,"maxAds": 100}
Use domains instead of advertiserNames when a competitor's name is common: the ads that lead to the domain are returned whichever account runs them.
2. Monday new-ads digest per client. Schedule this task for Monday 08:00. The first run saves a free baseline per client; every later run saves and charges only the new ads and posts one Slack message per client.
{"monitorGroups": [{ "name": "Client A", "domains": ["rival-one.com", "rival-two.com"] },{ "name": "Client B", "advertiserNames": ["Rival Three Ltd"] }],"region": "US","maxAds": 300,"webhookUrl": "https://hooks.slack.com/services/XXX"}
A competitor with more live ads than maxAds can show new ads late, because Google lists the most recently shown ads first and maxAds is how deep each run looks. Raise maxAds for big advertisers.
3. Warm-lead list from domains. Paste your prospect list; you pay only for the domains that run Google ads. hasAds: true with activeLast30Days: true is a warm lead, hasAds: true with activeLast30Days: false is a lapsed advertiser, and hasAds: false is a "start advertising" pitch, at no charge. Set region to the market of your clients: without it, a domain that advertises only abroad counts as warm. Up to 10,000 domains per run.
{"mode": "domainCheck","domainsText": "shop-one.com\nshop-two.pl\nhttps://www.shop-three.de/","region": "PL"}
The counts in each row come from the first 20 ads (sampleIsCapped says if there are more), which is plenty for local businesses. The actor returns no contact details; add a contact finder after it in Clay or n8n.
Ready-made searches
Open one of these, change the input and run it:
- See every Google ad a competitor is running
- Find who advertises on Google for a domain
- Track Google Shopping ad prices for a retailer
- Download a brand's Google and YouTube video ads
- See which Google ads a brand runs in one country
- Get all ads of a Google advertiser ID
- Find a brand's Google ads from the last 30 days
Use from an AI agent (MCP), Clay or n8n
The actor is solidfetch/google-ads-transparency-scraper. An agent that uses the Apify MCP server calls it with call-actor and one of these inputs; Clay (HTTP API column) and n8n (Apify node, "Run actor") send the same JSON. Everything except search is optional.
- A competitor's ads:
{"search": ["nike.com"], "maxAds": 50} - Does this website run Google ads:
{"mode": "domainCheck", "search": ["example-shop.pl"]} - A list of domains:
{"mode": "domainCheck", "search": "nike.com\nzalando.pl\nallegro.pl"} - Only new ads since the last run:
{"search": ["Nike"], "onlyNewSinceLastRun": true, "monitorName": "nike-weekly", "maxAds": 200} - What a pull would cost first:
{"search": ["Nike"], "overviewOnly": true}
Set maxAds and the maximum cost per run (maxTotalChargeUsd in the API URL, or the run options of the tool). In Clay, send the cell value as search; each row of the dataset carries searchQuery, and domain check rows carry your value in input, so you can map results back. See Use it from code or AI agents for the full call.
Use it from Clay, Google Sheets or Make
These tools call the actor through the Apify API and send a spreadsheet column as one value, so the text fields take a whole column. This call asks "does this domain run Google ads?" for a list of domains and returns one row per domain, with your own value in input:
curl -X POST "https://api.apify.com/v2/acts/solidfetch~google-ads-transparency-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>&maxTotalChargeUsd=1" \-H "Content-Type: application/json" \-d '{"mode": "domainCheck", "domainsText": "nike.com\nzalando.pl\nallegro.pl", "region": "PL"}'
domainsTextandadvertiserNamesTexttake one text: one value per line, or separated by commas, semicolons or spaces for domains. A brand name with a comma (Nike, Inc.) goes on its own line. The same fields work inadsandadvertisersmode. ThedomainsandadvertiserNameslists keep working, and also take a single string through the API.- Map rows back with
input. Every domain check row carries the value you sent. Values that are not domains (an IP address, an e-mail address, an empty cell) come back as free rows withhasAds: nulland anerror, so the row count matches your list, apart from repeated domains, which are checked once. - Set the limits in the caller. In
adsmode sendmaxAds(ads per search) and the maximum cost per run: addmaxTotalChargeUsd=<dollars>to the API URL, or set it in the integration. A tool that does not passmaxAdsgets the default of 100 ads per search, and the run log starts with a warning that says so. Domain check mode charges one answer per domain that runs ads and needs only the maximum cost. - Pasted Transparency Center links go in
startUrls({"startUrls": [{"url": "https://adstransparency.google.com/advertiser/AR16735076323512287233"}]}) inadsmode. - Google Sheets and Make read the rows from the finished run's dataset, see Send results to Make, Zapier, n8n or a CRM.
Send results to Make, Zapier, n8n or a CRM
Long runs do not need to keep a Make or Zapier scenario waiting:
- Start the actor or one of its saved tasks on a schedule, or from your scenario with the asynchronous "Run Actor" step.
- Trigger the next step on the finished run. In Make use the Apify module "Watch Actor Runs", in Zapier the Apify trigger for a finished actor run, in n8n the Apify node.
- Read the rows with "Get Dataset Items" using the run's dataset ID.
For your own endpoint, open the actor's Integrations tab and add an HTTP webhook on "Run succeeded". Apify retries failed deliveries, and the payload contains resource.defaultDatasetId, which you can read at https://api.apify.com/v2/datasets/<datasetId>/items?format=json. Add &format=csv for a spreadsheet, or &flatten=<field> to turn nested objects into flat columns.
Pricing
You pay $1.50 per 1,000 saved ads on the free plan, $1.20 on Bronze, $1.00 on Silver and $0.80 on Gold and above. Every run also pays a start fee of $0.00005 per run start (per GB of memory), including runs that fail or save nothing. A run that saves no ads costs only that start fee. The first run of a monitor (onlyNewSinceLastRun) saves its baseline ads without a charge, and monitor summary rows are free. If a run fails partway, the ads it saved before the failure stay in the dataset and stay charged. There is no extra charge for platform usage, and the run stops when it reaches the maximum cost per run that you set. If the platform migrates or restarts a run, the ads, domain answers and advertisers saved before are not saved or charged again and still count toward maxAds, and free rows are written once. The $5 of monthly free credit covers about 3,300 ads.
With downloadCreatives on, stored image files are free for now. A per-file price will be added later, announced on the Pricing tab two weeks ahead. Failed downloads and ads without an image will never be charged for a file.
Domain check mode is charged per domain instead of per ad: $6 per 1,000 domains on the free plan, less on paid plans, from 28 October 2026. You pay only for domains that run Google ads. Domains with no ads found and domains that could not be checked are saved as free rows and never charged. Until 28 October domain checks are free; you pay only the small start fee.
Advertiser lookup mode is free for now; a per-advertiser price will be added later with two weeks' notice. Names and domains without advertisers, and lookups that failed, will never be charged.
FAQ
Why do I get fewer ads than the Transparency Center website shows? The website shows a rounded estimate ("about 9,000 ads"). The actor returns the ads Google actually lists, and maxAds limits each search.
Can I get ads older than a year? Yes, leave the dates and shownInLastDays empty. Google allows the date filter only for the last 364 days.
Why are impressions empty for some EU ads? Google publishes the impression range about 90 days after an ad starts. impressionsAvailableFrom shows the date. Ads that ran only outside the EU have no impressions or targeting at all.
Does it need a Google account or cookies? No. It reads the same public data as the website.
Can I run it on a schedule? Yes. Save your input as a task in Apify with shownInLastDays instead of fixed dates and add a schedule, for example every Monday. Turn on onlyNewSinceLastRun to get only the ads that are new since the last run.
Use it from code or AI agents
curl -X POST "https://api.apify.com/v2/acts/solidfetch~google-ads-transparency-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \-H "Content-Type: application/json" \-d '{"advertiserNames": ["Nike"], "maxAds": 50}'
Cap the cost of an API run. Pass the maximum cost per run in the URL as maxTotalChargeUsd, and maxAds in the input, so a run cannot save and charge more than you planned:
curl -X POST "https://api.apify.com/v2/acts/solidfetch~google-ads-transparency-scraper/runs?token=<YOUR_TOKEN>&maxTotalChargeUsd=2" \-H "Content-Type: application/json" \-d '{"advertiserNames": ["Nike"], "maxAds": 500}'
The same query parameter works on run-sync-get-dataset-items and run-sync. In Clay, Make and n8n, set the maximum cost per run in the actor's run options. When neither maxAds nor a maximum cost is set, the log opens with a warning that names the 100-ad default per search and the most the run can charge. To see the cost first, run once with overviewOnly (Preview before you pay). The run stops when it reaches the maximum cost, and the ads saved before that stay in the dataset.
The actor is pay per event with limited permissions, so it works through the Apify MCP server in Claude, Cursor and other AI agents, and with Apify integrations for Make, n8n, Zapier and Google Sheets.
Notes
- A name, domain or ID list that finds no ad is named in the run's status message (
Saved 4 ads. No ads for: shop.nike.com), and a name that matches no advertiser is listed too. An input value that cannot be searched (a bad advertiser ID, a link that is not a Transparency Center page) is saved as a free row withsearchQueryanderror. - The country filter uses Google's own targeting data.
- If Google changes its response format, the run fails with an error instead of finishing with an empty dataset.
- Found a problem? Open an issue on the actor page and include the input you used.
- When a run fails, an error report with the error message and the shape of the input (which fields are set, how long the lists are), never your input values or your results, is sent to the developer to fix bugs.
This is an independent tool and is not affiliated with Google.
Related actor
Core Web Vitals Competitor Benchmark compares the real-user page speed (LCP, INP, CLS) of the same competitors, from Google's Chrome UX Report.
If this actor saved you time, a short review on its Store page helps other people find it. If something broke or a field is missing, open an issue and I will answer within a day.
More from Solid Fetch
- Businesses Without Website Leads: Local businesses without a website, with phone and address, by city.
- FMCSA New Trucking Companies: New US trucking companies and insurance leads from the FMCSA register.
- EU Company Registry: Company and VAT number lookup and new-company feeds from several European registers.
- Google Trends Monitor: Keyword comparison, spike alerts and trending searches from Google Trends.
- Core Web Vitals Benchmark: Page speed of your site and its competitors from the Chrome UX Report.
- Polish IT Jobs Scraper: IT job offers from solid.jobs with salaries in PLN, plus salary reports.
- UK Operator Licences: UK goods vehicle and bus operator licences, with market lists and weekly changes.
- Bulk Email Validator: Email list cleaning with MX, disposable-domain and typo checks.
- ATS Jobs Feed: Jobs and hiring signals from Greenhouse, Lever, Ashby and SmartRecruiters boards.
Did this help? A review on this page, or an issue saying what is missing, helps other people find it.