Google Ads Advertiser Check & Transparency Scraper avatar

Google Ads Advertiser Check & Transparency Scraper

Pricing

from $0.70 / 1,000 ad creatives

Go to Apify Store
Google Ads Advertiser Check & Transparency Scraper

Google Ads Advertiser Check & Transparency Scraper

Check if a domain runs Google Ads, identify advertiser accounts, or export current creatives from Google's Ads Transparency Center. Search by domain, brand, advertiser ID, or public URL; filter by country, format, surface, and date; monitor newly observed results. JSON/CSV/API/MCP.

Pricing

from $0.70 / 1,000 ad creatives

Rating

0.0

(0)

Developer

Brenton Keller

Brenton Keller

Maintained by Community

Actor stats

0

Bookmarked

9

Total users

8

Monthly active users

10 days ago

Last modified

Share

Google Ads Transparency Center Scraper

Pull every ad an advertiser is running on Google (Search, YouTube, Shopping, Maps, Play) from the Ads Transparency Center, as clean JSON/CSV. Search by domain, brand name, advertiser ID or a pasted Transparency Center URL. Filter by country, format, platform and date. Run it on a schedule with "only newly observed ads" to track changes in Google's result window. Use advertiser-check mode to answer "is this company running Google Ads, and how many?" for thousands of domains at a fraction of a cent each.

No browser, no login. Talks to the Transparency Center's own data API directly with a browser-grade TLS fingerprint, paces itself under Google's rate limits and rotates proxies automatically, so runs finish fast and don't fail halfway.

Try it free: every Apify account includes free monthly usage, and 1,000 ads cost $1, so checking a competitor is free.

Start here: a bounded, useful first run

The Store form is prefilled for one US competitor, 20 ads, and full details. That keeps the first run bounded while showing the richest output: creative previews and every rendering variation, dates, and the countries each ad ran in. At the published event price, 20 enriched rows cost at most $0.08, plus normal Apify platform usage; fewer rows cost less.

A note on what a US-only brand will and will not show: impression ranges and surfaces are published by Google only for EU-served and political ads, so a purely domestic advertiser returns null for both. Point the run at an EU-serving brand if those are what you came for.

{
"domains": [
"allbirds.com"
],
"mode": "ads",
"region": "US",
"includeDetails": true,
"maxAdsPerQuery": 20
}

Replace allbirds.com with one competitor domain and click Start. The cap is per query. Domain search means “ads link to this domain,” not “the domain owner bought every ad”; inspect advertiser_name and use its advertiser_id for a precise recurring watchlist.

Competitor ads monitor: schedule it, track Google's result window

Use a daily run to return only creative IDs the watchlist has not observed before, delivered to Slack or email. Prefer advertiser IDs over domains once you identify the right accounts: they avoid reseller/affiliate ads that happen to link to the same domain. Three steps, no code:

  1. Create a task from this actor with the input below. Replace the advertiser IDs and give the watchlist a unique monitorStoreName. Run it once; that first run seeds the list, so do not treat its returned rows as changes.

  2. Schedule it: in the task, open Schedule (or Console → Schedules) and pick daily. Each run charges only creative IDs not previously observed in the capped result window; a quiet day costs a few requests and no result-event charges.

  3. Alerts: in the task's Integrations tab add Slack or Gmail on "run succeeded". Use the run's status message as the notification text; in monitor mode it reports newly observed result-window ads or that none were observed. Slack message template (Handlebars):

    *Google Ads monitor:* {{resource.statusMessage}}
    <https://console.apify.com/storage/datasets/{{resource.defaultDatasetId}}|Open the newly observed ads>

    For "only notify when there is something new", route the run through n8n / Make / Zapier (Apify triggers exist for all three) and filter on the dataset item count.

{
"advertiserIds": [
"AR18378488041124659201",
"AR16735076323512287233"
],
"mode": "ads",
"region": "US",
"onlyNewAds": true,
"monitorStoreName": "footwear-competitors-daily",
"includeDetails": false,
"maxAdsPerQuery": 20
}

Each newly observed row carries is_new: true, first_seen_by_monitor, the creative preview URL, format, first/last shown and (with includeDetails) served countries and surfaces. This means newly observed, not necessarily newly launched. Google's capped top-N result window can reorder between runs, so a creative first shown months or years ago can enter the window and appear for the first time. Use first_shown to identify likely launches; narrow by advertiser ID, region, format, surface or date to make the window more stable. Use one monitorStoreName per watchlist.

What it costs at scale: 50 newly observed ads/day with details = 50 × $0.004 = $0.20/day, ~$6/month; without details, $1.50/month. Result-window reordering can increase this count on broad or heavily capped queries.

Weekly advertiser check: a low-cost retention workflow

Use advertisers mode when the recurring question is simpler: which watched domains have Google ads, under which observed advertiser accounts, and roughly how many creatives? Create a Task with this input, run it once, then schedule it weekly. Keep each week's dataset or export it as CSV to compare snapshots.

{
"domains": [
"allbirds.com",
"warbyparker.com",
"bombas.com"
],
"mode": "advertisers",
"region": "US",
"maxAdvertisersPerQuery": 3
}

Every domain gets an answer: one or more advertiser rows, or one is_advertising: false row. Treat it as observed Transparency Center activity, not ownership or spend. A domain can include affiliates/resellers, creative counts are Google ranges, and absence in one region does not prove global inactivity.

What it does

ModeOne row perUse it for
ads (default)ad creativecompetitor creative research, ad libraries, longevity analysis (which ads run for months), creative monitoring
advertisersmatched advertiserlead enrichment / qualification: is this domain advertising on Google right now, under which account(s), roughly how many ads

Who it's for

  • Performance marketers and agencies tracking competitors' Google ads: result-window change alerts, formats, and how long each creative survives (an ad-spy tool for Google, YouTube and Shopping ads at pay-per-result prices).
  • Sales and lead-gen teams enriching prospect lists (Clay, n8n, Make, Zapier, Google Sheets) with "runs Google Ads: yes/no + volume" as a buying signal.
  • Brand and compliance teams watching for affiliates, resellers or impersonators advertising on their domain.
  • Researchers and journalists studying advertising activity by region and platform.
  • AI agents: every field is stable and typed, and the actor is exposed as an MCP tool via Apify.

Input examples

All ads for a domain, US, last 90 days, YouTube only

{
"domains": ["allbirds.com"],
"region": "US",
"surface": "YOUTUBE",
"dateFrom": "2026-06-01",
"maxAdsPerQuery": 500
}

A brand by name, with full per-country details

{
"queries": ["Nike"],
"maxAdvertisersPerQuery": 1,
"includeDetails": true
}

Paste Transparency Center URLs

{
"startUrls": [{ "url": "https://adstransparency.google.com/advertiser/AR18378488041124659201?region=GB" }]
}

Monitor by advertiser ID instead of domain (see the monitor section above for scheduling and alerts)

{
"advertiserIds": ["AR18378488041124659201", "AR16735076323512287233"],
"onlyNewAds": true,
"monitorStoreName": "footwear-competitors"
}

Lead enrichment: is each domain advertising?

{
"domains": ["allbirds.com", "warbyparker.com", "some-local-plumber.com"],
"mode": "advertisers",
"region": "US"
}

Output

ads mode

{
"creative_id": "CR04143963882324492289",
"advertiser_id": "AR18378488041124659201",
"advertiser_name": "Nike Retail BV",
"format": "TEXT",
"matched_domain": "nike.com",
"first_shown": "2026-07-22T04:31:55+00:00",
"last_shown": "2026-09-01T21:04:44+00:00",
"days_shown": 43,
"impressions": null,
"impressions_low": null,
"impressions_high": null,
"preview_url": "https://tpc.googlesyndication.com/archive/simgad/12774179880874022668",
"preview": { "script_url": "…", "iframe_url": null, "html_snippet": null, "image_url": "…" },
"ad_url": "https://adstransparency.google.com/advertiser/AR18378488041124659201/creative/CR04143963882324492289?region=anywhere",
"advertiser_url": "https://adstransparency.google.com/advertiser/AR18378488041124659201?region=anywhere",
"query": "nike.com",
"query_kind": "domain",
"region": "US",
"matched_total": { "low": 100000, "high": 200000 },
"is_new": true
}

With includeDetails: true each row also gets:

"details": {
"country_reach": [{ "country": "US", "first_served": "2026-07-22", "last_served": "2026-09-01", "impressions": null, "surfaces": [] }],
"eu_reach": { "impressions": { "low": 10000, "high": 50000 }, "first_served": "2026-07-21", "last_served": "2026-09-01" },
"variations": [ { "script_url": "…", "image_url": "…" } ],
"targeting": { "categories": { "1": {"1": true, "2": false} } },
"category": { "gcb_category_id": 10021 },
"contains_synthetic_media": false
},
"countries": ["US", "GB", "DE", "FR", "CA"],
"surfaces": ["SEARCH", "YOUTUBE"]

How often each detail field is actually populated. Measured over 225 enriched rows across 9 US/EU retail domains, so treat the thin numbers as indicative rather than exact:

fieldfillwhat you get
country_reach / countries100%The real reason to enable details — basic mode gives 0%. But 87% of ads list exactly one country.
variations (more than one rendering)84%Every rendering of the creative, where basic mode exposes a single preview_url.
customer_name72%Was byte-identical to advertiser_name in every populated sample, so it is now dropped when it duplicates that column and kept only when it genuinely differs.
targeting18%Not usable as shipped. age/gender/geo are always null and are no longer emitted; what remains is categories, which comes back as undecoded protobuf keys, as shown above.
category.gcb_category_id16%A bare numeric ID. The readable google_product_category fills on 1% of rows and vertical on none.
eu_reach14%EU-served ads only.
country_reach[].impressions, surfaces13%EU-served and political ads only — see below.
served_countries, us_reach, details.first_shown, details.days_shown0%Google does not return these; first_shown and days_shown are already at the top level at 100%.

Fields Google never populates are omitted from details rather than emitted as nulls, so a key's presence means it carries data. If Google starts returning any of them, they pass straight through again.

If you are researching a US-only brand, expect zero impression data and a one-element country list — allbirds.com, bombas.com and casper.com all returned 0% impressions. EU-serving advertisers do much better (56% of Nike's ads carried impressions). Enable details for country coverage and creative variations; do not buy it for targeting or category.

preview_url is not a stable key: Google randomises two query parameters on every response, so the same ad yields a different preview_url run over run (43% of rows in a back-to-back comparison). Use creative_id to dedupe or diff.

Impression and spend ranges are only published by Google for EU-served ads and political ads; elsewhere they are null. Where they exist they are lifted to the top level as impressions_low / impressions_high (global) and eu_impressions_low / eu_impressions_high (EU reach from the details block), so CSV exports and the Overview table show them without digging into details. matched_total is Google's estimate of how many ads match the whole query (useful even when you cap maxAdsPerQuery).

advertisers mode

{
"query": "allbirds.com",
"query_kind": "domain",
"region": "US",
"is_advertising": true,
"advertiser_id": "AR12345678901234567890",
"advertiser_name": "Allbirds Inc",
"creatives_count_low": 1000,
"creatives_count_high": 2000,
"advertisers_on_domain": 1,
"formats_seen": ["IMAGE", "TEXT", "VIDEO"],
"last_shown": "2026-09-02T01:46:39+00:00",
"advertiser_url": "https://adstransparency.google.com/advertiser/AR12345678901234567890?region=US"
}

Domains with no ads return one row with is_advertising: false, so every input gets exactly one answer.

A zero-result row also tells you whether to believe it. When Google's index knows the domain or brand even though no creative matched, the row carries a hint naming what to retry with (see Limits and notes for why this happens and what it costs):

{
"query": "hims.com",
"is_advertising": false,
"creatives_count_low": 0,
"hint": "No ads matched domain hims.com, but Google's index knows this domain and the advertiser 'Hims, Inc.' (AR09131126584697159681). Display/image-only ads often expose no landing domain and are invisible to a domain search, so this may be a filter miss rather than a \"no\" -- to check, retry with queries: [\"hims\"] or advertiserIds: [\"AR09131126584697159681\"]."
}

The field is absent when the "no" is solid: sqlite.org returns no hint, hims.com returns the one above. Filter your export on the presence of hint to pull exactly the rows worth a second look.

Advertiser-check rows for a domain

Each advertiser row for a domain carries the advertiser's billing country from its Transparency Center profile — the single most useful field for telling the domain owner apart from affiliates and resellers — plus its own ad count for that domain (creatives_count_low / creatives_count_high), the unverified flag, and the domain-wide total in domain_creatives_count_low / domain_creatives_count_high. (legal_name is also returned, but in our samples it was always identical to advertiser_name.) This distinguishes the domain owner from resellers and affiliates that also link to the domain: a nike.com check returns Nike Retail BV at 90,000–100,000 creatives, Nike, Inc. at 8,000–9,000 and a Singapore branch at 1,000–2,000, against a domain-wide 100,000–200,000. Costs 2 extra requests per advertiser row; maxAdvertisersPerQuery caps it. Candidates are ranked by how many of the domain's sampled ads each advertiser owns before that cap is applied, so truncating drops the smallest affiliates rather than the owner, and the returned rows are ordered by each advertiser's true ad count for the domain. Google's own ordering is by most recent ad, which put a one-ad affiliate ahead of the owner on ~20% of domains in a narrow region (casper.com and awaytravel.com under region: DE both did); ranking by sample share fixes both of those cases. It is still a heuristic on a 40-ad sample — use country and the creative counts to confirm the owner, and keep maxAdvertisersPerQuery above 1 when the domain may have several legitimate advertisers.

Pricing

Pay per result, no subscription:

EventPrice
Ad creative (ads mode)$1.00 per 1,000
Ad creative with full details (includeDetails)$4.00 per 1,000
Advertiser check row (advertisers mode)$2.00 per 1,000

No per-run start fee. 10 of the 12 competing Ads Transparency actors we surveyed charge one, from $0.00005 to $0.20 per run. That matters most for the scheduled-monitor pattern, where you pay for a small number of rows many times over.

Plus normal Apify platform usage — measured at about $0.05 per 1,000 rows; the actor runs in 256 MB and makes one request per 40 ads.

What counts as a billable row. Rows carrying data are charged, including rows with is_advertising: false — a verified "this domain has no ads in Google's index" is a result you asked for. Failed targets are not charged: a target that errored or was blocked mid-run still gets a row with an error field so the failure is auditable, but that row is written to the dataset with no charge event attached and costs you nothing. A run where every target is blocked fails outright and charges nothing at all. The run's status message reports how many error rows went out free.

In advertisers mode the unit you buy is a row, but the unit you think in is a domain — and they are not the same. A domain that advertises returns up to maxAdvertisersPerQuery rows; a domain with no ads returns exactly one. Measured over a 100-domain run: 1.63 rows per domain, so budget about $0.0033 per domain checked, not $0.002 (a 20-domain run with a higher share of non-advertisers came in at 1.35). Set maxAdvertisersPerQuery: 1 to pin it to exactly one row and $0.002 per domain, at the cost of missing co-advertisers — the single row you keep is now the advertiser with the largest share of the domain's sampled ads, which is the owner in every case we have measured, but it is a sample-based heuristic, not a guarantee.

Limits and notes

  • Google paginates to roughly 1,000 ads per query. Split large advertisers by format, surface or date range to get more.
  • Google keeps about 12 months of ad history.
  • A domain search returns every advertiser whose ads link to that domain, not only the domain owner. That can include resellers, affiliates and demo/test ads (example.com yields real ads from unrelated advertisers). Use advertisers mode to see who they are, or advertiserIds to pin one account. The reverse also happens: a third party linking to a domain makes that domain look like an advertiser (irs.gov and weather.gov both return is_advertising: true), so read the result as "ads point at this domain", not "this company buys ads".
  • is_advertising: false means no creative in Google's index is associated with that domain, which is not the same as "this company runs no Google ads". Display/image-only advertisers frequently expose no landing domain, so they are invisible to a domain query: hims.com returns false while the advertiser "Hims, Inc." is live with current image creatives. The actor now checks this for you: when a domain matches nothing, it makes one extra suggestion lookup, and if Google's index knows the domain or a matching advertiser you get a hint field on the row and the same text in the run's status message, naming the advertiser ID to retry with. No hint means Google's index knows nothing either, so the empty result is a genuine "no". The lookup costs one request and only happens on the empty path; it is skipped in onlyNewAds monitor runs, where an empty result is the normal outcome.
  • Brand-name queries are resolved through Google's own suggestion index; the largest exact/prefix match is used first. Use advertiserIds for precision.
  • Ad-text (keyword) search is only available for political ads in Google's data and is not exposed here.
  • Google rate-limits by IP. Keep the default Apify proxy on for anything beyond a few hundred ads; the actor rotates sessions on every rate-limit response and backs off, so runs complete instead of failing.
  • If Google blocks every request (bot-check page or HTTP 429 after retries), the run fails with a clear error and nothing is charged. Enable Apify Proxy (residential) or raise requestDelaySeconds. If only some targets are blocked, the run succeeds and those targets get a row with an error field — those rows are not charged.
  • Rows whose detail lookup failed (details_error) are charged at the base ad rate, not the enriched rate.
  • Monitor state is per monitorStoreName (a named key-value store in your account). Do not run two monitor runs against the same store at the same time; use one store per watchlist or schedule.

Use as an MCP tool / from code

The actor is callable from any MCP client through Apify's MCP server, and from code with the Apify API. Agent pattern: keep a watchlist, call with onlyNewAds: true on a schedule, and act only on the rows that come back.

curl -X POST "https://api.apify.com/v2/acts/<username>~google-ads-transparency-center/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"domains": ["allbirds.com"], "mode": "advertisers", "region": "US"}'

This actor only reads data that Google publishes for public inspection in the Ads Transparency Center. Use it in line with Google's terms and applicable law; you are responsible for how you use the output.

Support

Open an issue on the actor page with the run ID and input. If Google changes the Transparency Center's data format, affected fields go null rather than failing the run; report which field and we will patch it. If the actor saved you time, a review helps other people find it.