← Guides

How-to

How to run Google Ads Transparency Center & Competitor Ad Scraper

Step-by-step: the exact input, what each field does, and the rows that come back from Google Ads Transparency Center.

You want to see what a competitor is actually running: which creatives, in which country, and for how long. Google publishes all of it in the Ads Transparency Center, and for one advertiser you should just go and look — it is free, and it is the same data. The problem starts at scale. The site paginates an advertiser’s library forty ads at a time, and a large advertiser’s library is enormous: Google stated 8,000 to 9,000 creatives for a single advertiser in the US alone on 7 September 2026. Scrolling that, for several competitors, across several countries, is not an afternoon’s work — and the site gives you no way to export what you saw.

What you’ll need

Nothing but an Apify account. The actor talks to Google Ads Transparency Center directly.

No Google account, no Ads account, no API key. There is one thing to know, though, and it is the opposite of the usual advice: leave the proxy on. It is on by default here, alone among the actors on this site, and the reason is measured rather than precautionary — see “Where the data comes from” below. Apify’s proxy is included in your plan; you are not paying a third party for anything.

Step 1 — Open the actor

Open Google Ads Transparency Center & Competitor Ad Scraper on the Apify Store and press Try for free. The actor’s own page on this site lists its live health and pricing.

The Google Ads Transparency Center & Competitor Ad Scraper listing on the Apify Store
The listing on the Apify Store. Pricing and the nightly health record are both public.

Step 2 — Fill in the input

The input form for Google Ads Transparency Center & Competitor Ad Scraper in the Apify Console
The input form, as it appears in the Apify Console.
FieldTypeRequiredWhat it does
querystringnoAn advertiser domain (nike.com), a Google advertiser id (AR16735076323512287233), or a company name to search for. Which of the three you gave is recorded on every row in…
queriesarraynoLook up many advertisers in ONE run instead of starting a run per advertiser. Takes the same values as ‘Advertiser’. Combined with it if you fill both, and duplicates are… (default [])
regionstringnoThe country whose ad library to read, as an ISO country code — US, GB, DE, FR, JP, BR and so on. This is not cosmetic: Google keeps a separate library per country and the same… (default US)
maxResultsintegernoCap on creatives returned PER advertiser, not per run. Large advertisers run enormous libraries — Google stated 8,000-9,000 creatives for one of them in the US alone on… (default 200)

query accepts three different things, and which one you give changes how exact the answer is. A domain (nike.com) and an advertiser id (AR16735076323512287233) are both exact. A company name is not: it goes through Google’s own advertiser search and the best match is used. Searching “Nike” on 7 September 2026 returned NIKE SRL in Italy, Nike Klara, Nikel Kola and several unrelated advertisers alongside Nike, Inc. The actor picks the top match and logs how many candidates it considered, and every row records which route was used in matched_by — so if an advertiser looks wrong, you can see immediately whether a name search guessed for you. Where you know the domain, use the domain.

region is not cosmetic — it selects a different library. Google keeps a separate ad library per country, and the same advertiser’s volume varies enormously between them. On 7 September 2026, one advertiser was stated at 8,000–9,000 creatives in the US, 9,000–10,000 in the UK, 20,000–30,000 in Germany and 400–500 in Japan. Running “a competitor’s ads” without deciding the country is answering a question you did not mean to ask.

Give a two-letter country code; 48 are supported and each was verified against the live service. An unrecognised code is refused up front with a free row naming valid ones, and that guard matters: Google answers an unknown region with an empty list rather than an error, which reads exactly like “this advertiser runs no ads there.”

A working input:

{
  "queries": [
    "nike.com",
    "geico.com"
  ],
  "region": "US",
  "maxResults": 200
}

Step 3 — Run it

Press Start. Rows are pushed to the dataset as they’re found, so the run log fills in as it works.

Ads come back in pages of forty. Two advertisers at 200 creatives each returned 400 rows in about 8.7 seconds on Apify, so this is a quick actor unless you deliberately ask for a whole library.

The run log states, per advertiser, how many creatives it returned and what Google says the advertiser’s library holds in that region — the second number being a range rather than a count, because that is how Google publishes it. Reading the two together is how you know whether you took a sample or the lot.

An empty result has three distinct causes here and the actor names which: input_error for a region it will not send (or no advertiser given at all), no_results when Google genuinely has no ads for that advertiser in that country, and blocked when Google throttled the request. That last one is the important distinction — see below — because it is the one that would otherwise look identical to “this competitor isn’t advertising.”

Step 4 — Read the output

Each run returns a labelled table, not raw JSON:

The output fields returned by Google Ads Transparency Center & Competitor Ad Scraper
The output view — every field the actor returns, named and typed.

Every row looks like this — a real row from a real run:

{
  "source_url": "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR10365997061744099329?region=US",
  "advertiser_id": "AR16735076323512287233",
  "advertiser_name": "Nike, Inc.",
  "advertiser_domain": "nike.com",
  "creative_id": "CR10365997061744099329",
  "format_code": 1,
  "creative_type": "image",
  "creative_image_url": "https://tpc.googlesyndication.com/archive/simgad/2588413232821661199",
  "creative_preview_url": null,
  "display_width": 348,
  "display_height": 180,
  "first_shown": "2023-11-16T22:59:37Z",
  "last_shown": "2026-09-07T05:25:23Z",
  "days_running": 1025,
  "region": "US",
  "advertiser_creatives_low": 8000,
  "advertiser_creatives_high": 9000,
  "matched_by": "domain",
  "advertiser_url": "https://adstransparency.google.com/advertiser/AR16735076323512287233?region=US"
}
FieldExample valueMeaning
advertiser_idAR16735076323512287233Google’s own advertiser identifier (AR…) — stable across runs and regions
advertiser_nameNike, Inc.The verified advertiser name as Google publishes it
advertiser_domainnike.comThe advertiser’s domain as stated on the creative
creative_idCR10365997061744099329Google’s identifier for this specific ad (CR…)
format_code1Google’s own numeric format code, passed through unlabelled
creative_typeimageimage or rich_media — derived from what the payload contains, not from the format code
creative_image_urlhttps://tpc.googlesyndication.com/archive/simgad/2588413232821661199Google’s own hosted image for the ad, taken verbatim from the response
creative_preview_urlFor rich-media ads, Google’s renderable preview URL

first_shown and last_shown are what most people came for, and they are more useful together than apart. The gap between them is how long a creative has been running, and an advertiser who keeps a creative live for two years is telling you it works far more reliably than any engagement metric you could infer. The row above has been running since November 2023.

creative_image_url and creative_preview_url are alternatives, not a pair: an image creative has the first, a rich-media creative has the second, and creative_type tells you which. One caveat on the image URL — Google serves those assets to browsers and refuses plain command-line clients, so a curl at one will hang rather than return an error. The URL is real and is taken verbatim from Google’s response; open it in a tab, or send browser-style headers if you are fetching them programmatically. Note also that display_width and display_height are the size the ad is shown at, not the asset’s own resolution — one creative checked at 348×489 in the ad slot is a 696×458 image.

advertiser_creatives_low and advertiser_creatives_high are a range because Google refuses to publish an exact count. Any tool that reports a single total for an advertiser’s library is reporting something Google did not say.

The one thing that is genuinely not here: impressions, spend and reach. Google publishes none of them in this service for a normal commercial ad, so no tool reading this source can give them to you. There is one undocumented integer on each creative row that this actor deliberately does not return — it sits between 5 and 1,737 across the ads sampled, matches neither the image area nor its dimensions, and shipping it labelled “impressions” would be inventing a number.

Step 5 — Export it

Open the Dataset tab and export to CSV, JSON, or Excel — or pull the same rows from the API, which is what you want if this is going to run on a schedule.

What it costs

$0.0015 per creative returned — $1.50 per 1,000. Two advertisers at 200 creatives each is 400 rows and $0.60.

You are charged per creative, not per advertiser: ten advertisers holding 1,000 creatives between them cost the same as one advertiser with 1,000. Marker rows are free — a throttled run, an unusable region, an advertiser with no ads in that country, all cost nothing. Pulling a large advertiser’s full library is the case to think about before you press start: at Google’s stated 8,000–9,000 creatives, that one advertiser is $12–13.50, which is why the cap is per advertiser and defaults to 200. Apify’s platform usage is billed separately and is pennies here.

Where the data comes from

This reads Google Ads Transparency Center directly. The target is An UNDOCUMENTED internal RPC (POST /anji/_/rpc/SearchService/SearchCreatives) that the Transparency Center’s own Angular front end calls. Form-encoded f.req carrying protobuf-as-JSON, so every field is a NUMBER rather than a name and the map had to be read off live traffic. Every night a canary runs this actor against that live source and diffs the result against a frozen fixture — what “verified” means.

The Transparency Center publishes no API. Its front end talks to an internal endpoint that carries protobuf-as-JSON, which means every field arrives numbered rather than named — the advertiser id is field 1, the creative id is field 2, the timestamps are 6 and 7. Nothing announces that, so the map has to be read off the live service and then checked. It also creates an unusually quiet failure mode: if Google ever renumbers those fields, the response stays valid JSON of exactly the right shape and every value lands in the wrong column. This actor asserts that the rows it got still carry a creative id where one belongs, and stops with a source_changed row rather than emitting a dataset that looks fine and is wrong.

The other thing worth knowing is why the proxy default is inverted here. Google throttles this endpoint per IP, and a spent IP is not given a 429 — the request is redirected to google.com/sorry/, so a scraper that only inspects the response body sees a valid page and reports an empty result. That was measured on 7 September 2026 rather than assumed: from an unproxied Apify container the run was blocked, and the identical run through Apify’s datacenter proxy returned real data. This actor checks where each response landed rather than only what it contains, so a throttled run says blocked instead of quietly claiming the advertiser has no ads.

See it used

How long has that ad been running? Creative longevity from the Transparency Center uses first_shown against last_shown to separate the creatives a competitor is committed to from the ones they tested and dropped.

If the advertisers you are tracking run Shopify storefronts, Shopify Store Intel covers the store side — reach for it when you want to know what they sell rather than what they say.

Last updated 2026-09-07