# Google Ads Transparency Center Scraper (Advertisers & Ads) (`subimpact/google-ads-transparency-scraper`) Actor

Scrape Google Ads Transparency Center: find advertisers by name or ID and export their ads with format, first and last shown dates, preview and ad links, filtered by region. No browser. $0.80 per 1,000 ads, $0.50 per 1,000 advertisers.

- **URL**: https://apify.com/subimpact/google-ads-transparency-scraper.md
- **Developed by:** [subimpact](https://apify.com/subimpact) (community)
- **Categories:** SEO tools, Lead generation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 ads

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Google Ads Transparency Center Scraper (Advertisers & Ads)

Scrape Google's public **Ads Transparency Center** (`adstransparency.google.com`) for advertisers
and their ads. Search advertisers by name or domain, or pass `AR...` advertiser ids directly, then
get one row per creative with its format, preview link, public ad permalink, first and last shown
dates, and the region you asked for.

Pure HTTP against the site's own JSON RPC. No browser, no Playwright, no login, no Google API key.
Requests run through the Apify proxy because Google answers direct requests from datacenter IPs
with HTTP 429.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `searchTerms` | array of strings | none (prefill `["adidas"]`) | Advertiser names or domains. Each term is resolved to advertisers first |
| `advertiserIds` | array of strings | none | `AR...` ids, used directly and skipping the search |
| `region` | string | `anywhere` | Two letter ISO code such as `US`, `GB`, `MY`, `SG`, `TH`, or `anywhere` |
| `maxAdsPerAdvertiser` | integer | `40` | 1 to 1000. The form default is 40; an empty API input (`{}`) uses 10 |
| `maxAdvertisersPerTerm` | integer | `3` | 1 to 20, in the order Google returns them |
| `maxItems` | integer | `100` | Global cap on ad rows for the whole run, 1 to 5000 |
| `concurrency` | integer | `5` | Parallel advertisers, 1 to 20. Each one gets its own proxy session |
| `proxyConfiguration` | object | `{"useApifyProxy": true}` | Required, see the proxy note below |

At least one of `searchTerms` or `advertiserIds` must be non-empty. Invalid values (an advertiser
id that is not `AR` followed by letters and digits, a region Google has no geo id for, a cap of 0)
fail the run immediately with a clear message, before any request and before any charge.

### Example inputs

Pre-filled example, one advertiser with 20 ads, region not filtered:

```json
{
  "searchTerms": ["adidas"],
  "maxAdvertisersPerTerm": 1,
  "maxAdsPerAdvertiser": 20
}
```

An empty input works too. `{}` is treated as `adidas`, 10 ads per advertiser, at most 3 advertisers
and 100 ads in total:

```json
{}
```

By advertiser id, filtered to Thailand, up to 40 ads:

```json
{
  "advertiserIds": ["AR17365672681860497409"],
  "region": "TH",
  "maxAdsPerAdvertiser": 40
}
```

A domain instead of a name:

```json
{
  "searchTerms": ["adidas.com"],
  "maxAdvertisersPerTerm": 2,
  "maxAdsPerAdvertiser": 15
}
```

Google's suggestion endpoint answers a domain query with domain entries that carry no advertiser
id, so the Actor retries such a term with the domain's name part (`adidas.com` becomes `adidas`)
and logs that it did so. If you already know the advertiser, `advertiserIds` is exact and skips
this round trip.

### Output

One row per ad plus one row per matched advertiser. `rowType` tells them apart: `ad`, `advertiser`
or `error`. Error rows are informational and are never charged.

| Field | Type | On | Notes |
|---|---|---|---|
| `rowType` | string | all | `ad`, `advertiser` or `error` |
| `advertiserId` | string or null | all | `AR...` id |
| `advertiserName` | string or null | ad, advertiser | Name as Google publishes it |
| `country` | string or null | advertiser | Country code on the matching suggestion |
| `adCountEstimate` | integer or null | advertiser | Upper bound of Google's ad count range |
| `verified` | boolean or null | advertiser | Google's verification flag, null when the payload omits it |
| `creativeId` | string or null | ad | `CR...` id |
| `adFormat` | string or null | ad | `text`, `image`, `video` or `unknown` |
| `previewUrl` | string or null | ad | Google hosted preview, either an image or a `content.js` renderer |
| `adUrl` | string or null | ad | Public Ads Transparency Center permalink |
| `firstShown` | string or null | ad | ISO 8601 timestamp |
| `lastShown` | string or null | ad | ISO 8601 timestamp |
| `regionQueried` | string or null | ad | The region used for the search, or `anywhere` |
| `searchTerm` | string or null | all | Term that produced the advertiser, null for `advertiserIds` |
| `scrapedAt` | string | all | ISO 8601 timestamp of this run |
| `error` | string or null | error | Failure reason |

Real rows, taken from the captured Nike SRL responses in `test/fixtures/`. The ad row is the first
creative in that response:

```json
{
  "rowType": "advertiser",
  "advertiserId": "AR17365672681860497409",
  "advertiserName": "NIKE SRL",
  "country": "IT",
  "adCountEstimate": 8,
  "verified": null,
  "searchTerm": "nike",
  "scrapedAt": "2026-10-24T12:00:00.000Z"
}
```

```json
{
  "rowType": "ad",
  "advertiserId": "AR17365672681860497409",
  "advertiserName": "NIKE SRL",
  "creativeId": "CR00017566880097107969",
  "adFormat": "text",
  "previewUrl": "https://tpc.googlesyndication.com/archive/simgad/5369694199582718379",
  "adUrl": "https://adstransparency.google.com/advertiser/AR17365672681860497409/creative/CR00017566880097107969",
  "firstShown": "2025-12-05T04:56:43.000Z",
  "lastShown": "2025-12-22T22:59:54.000Z",
  "regionQueried": "anywhere",
  "searchTerm": "nike",
  "scrapedAt": "2026-10-24T12:00:00.000Z"
}
```

The other seven creatives in the same captured response are `CR06126911430613532673` (video,
shown 2025-11-26 to 2025-12-22), `CR02919090104867749889` (image, 2025-12-02 to 2025-12-22),
`CR13902772011454693377` (text), `CR08131215574932914177` (image), `CR03470410798679982081`
(video, 2025-10-19 to 2025-11-22), `CR10815781378604400641` (image) and `CR16552501301028585473`
(image). Advertiser rows from the `nike` suggestion response include `Nike, Inc.`
(`AR16735076323512287233`, US, estimate 10000) and `Nike Srl` (`AR03550664066492006401`, IT,
estimate 1).

`adFormat` comes from the creative's format code, which was verified against the preview payloads:

| Code | Label | Evidence in the preview payload |
|---|---|---|
| 1 | `text` | overlay protobuf carries `headline`, `description`, `visurl` |
| 2 | `image` | overlay carries `productAdsRenderingData` with the product and image data |
| 3 | `video` | `assets` protobuf carries media asset ids |
| other | `unknown` | labelled rather than guessed |

A text ad still gets an image preview from Google (`simgad` render), and video ads get a
`content.js` renderer. `previewUrl` points at whichever form Google returned.

`adCountEstimate` is the **upper** bound of the range Google reports. For an advertiser shown as
"9000 to 10000 ads" the row carries `10000`. `verified` is `true` when the suggestion payload
carries Google's verification flag and `null` when the flag is absent, which is the common case.

### How it works

#### Session and proxy

`GET https://adstransparency.google.com/?region=XX` mints the session cookie (`NID` on
`.google.com`). Each advertiser then uses its own proxy session and its own cookie jar, so one
throttled advertiser cannot poison the others.

The proxy is not optional. On 2026-10-24 every direct request from an Apify container got HTTP 429,
while the Apify datacenter proxy (`auto` group) returned 200 for the homepage, suggestions and
creatives, including a 10 way parallel suggestion burst (10/10). If the proxy cannot be created or
cannot issue a URL, the Actor fails instead of silently going direct.

#### Advertiser search

`POST /anji/_/rpc/SearchService/SearchSuggestions?authuser=` with a form encoded body
`f.req={"1":"<term>","2":10,"3":10}`.

The response has one mixed array under `"1"`. Entries shaped `{"1":{...}}` are advertisers:
`1` name, `2` `AR...` id, `3` country, `4.2.1`/`4.2.2` ad count range, `5` verification flag.
Entries shaped `{"2":{"1":"nike.com"}}` are domains and carry no advertiser id.

#### Ads by advertiser

`POST /anji/_/rpc/SearchService/SearchCreatives?authuser=` with
`f.req={"2":40,"3":{"12":{"1":"","2":true},"13":{"1":["AR..."]}},"7":{"1":1}}`.

Field notes, all verified against live responses:

- `"2"` is the page size (40 works and returns 40 distinct creatives).
- `"3"."13"."1"` is the advertiser id list.
- `"3"."8"` is the region filter, a list of numeric Google Ads geo target ids.
- `"4"` at the **top level** is the next page token, echoed back from the previous response's
  top level `"2"`. Two consecutive pages of one advertiser contained 40 and 40 creatives with no
  overlapping ids.
- Each creative: `1` advertiser id, `2` `CR...` id, `3` preview block (`3.1.4` is the `content.js`
  URL, `3.3.2` is an `<img>` snippet), `4` format code, `6.1` first shown in Unix seconds,
  `7.1` last shown in Unix seconds, `12` advertiser name.

An empty result set is `{}`, which is valid and means zero matching creatives. A changed payload
(an items array with no usable `CR...` id, or a non-array where an array belongs) raises a shape
error: the run records an uncharged error row instead of delivering hollow rows.

#### Region filter

The region is translated from the ISO code to Google's Ads geo target id using the country table
published in the Google Ads API geo targets CSV (release 2026-10-06, 219 active countries). The
table is embedded in `src/regions.js` and maps, for example, US to 2840, MY to 2458, SG to 2702 and
TH to 2764.

This was checked against the live service: the same advertiser returned 8 ads with `"8":[2840]`
(US) and a full 40 of 200 to 300 with `"8":[2458]` (MY). `anywhere` sends no `"8"` filter at all.

#### Pagination

The Actor follows the top level `"2"` token as `"4"` until it has `maxAdsPerAdvertiser` rows, the
token stops changing, a page adds no new creative ids, the global `maxItems` cap is reached, or 60
pages have been read. Creative ids are deduped per advertiser, so a token that re-serves an item
cannot double charge.

#### Retries and rate limits

Each request has a 30 second timeout. HTTP 429, 5xx and network errors are retried up to 3 times,
and every retry opens a **fresh proxy session** with a new session id and new cookies. A 429 waits
5 to 15 seconds with jitter first. HTTP 400 and 404 are not retried. If all attempts fail, the
search term or advertiser produces one error row and the run continues with the rest.

### Pay per event

| Event | Price | Charged when |
|---|---|---|
| `ad` | $0.0008 per ad row | Immediately after the row is pushed to the dataset |
| `advertiser` | $0.0005 per advertiser row | Immediately after the row is pushed to the dataset |

Rows are pushed and charged one at a time, so a run that times out still delivers and bills only
what it already pushed. Error rows are pushed for diagnostics and are never charged. A failed
charge logs a warning and never kills the run. Apify platform usage is paid by you, the buyer, on
top of these event prices.

Typical event cost: the pre-filled example (1 advertiser, at most 20 ads) is at most about $0.0165.
An empty input (`{}`, up to 3 advertisers and 30 ads) is at most about $0.026. A run that hits the
100 ad cap is $0.08 in ad rows plus advertiser rows.

### How this compares

At $0.80 per 1,000 ads and $0.50 per 1,000 advertisers, this Actor costs about half of the $1.50
actors and less than half of scrapesage's $2.00. Unlike scrapesage, there is no separate "ad
details" event: every row already includes the format, the preview URL, the public permalink and
both dates.

| Actor | Ads (per 1,000) | Other events | Platform usage |
|---|---|---|---|
| subimpact/google-ads-transparency-scraper | $0.80 | advertiser rows $0.50 per 1,000 | paid by you |
| scrapesage/google-ads-transparency-scraper | $2.00 | ad details $3.00 per 1,000, advertisers $3.00 per 1,000. Listing shows 1,261 users in 30 days | see listing |
| solidcode Ads Transparency actor | about $1.50 | see listing | see listing |
| s-r Ads Transparency actor | about $1.50 | see listing | see listing |

Prices are Apify Store list prices on the Free plan, checked 24 October 2026. Competitor prices
can change; check their listings.

### Limits and cost guardrails

- `maxItems` defaults to 100 ad rows and can be set from 1 to 5000. It is enforced across all
  advertisers and all search terms, including the parallel workers.
- `maxAdsPerAdvertiser` is 1 to 1000. The form default is 40, an empty API input uses 10.
- `maxAdvertisersPerTerm` is 1 to 20. Explicit `advertiserIds` are all processed.
- At most 100 advertisers are processed in one run, whatever the input. This keeps a many-term
  run from turning into hundreds of advertiser rows.
- `concurrency` is 1 to 20 and defaults to 5. Higher values finish faster and hit rate limits more
  often.
- Only 219 country codes are accepted for `region`, plus `anywhere`. The list comes from Google's
  own geo target table.
- Ads are returned 40 per request and at most 60 pages per advertiser are read.
- Preview URLs are Google hosted. A `content.js` preview is a renderer, not an image file, so it
  needs a browser to display; an image preview can be opened or downloaded directly.

### Responsible use

The Ads Transparency Center is a public archive about advertisers and their creatives. This Actor
is meant for competitive research, ad monitoring, brand safety checks and journalism. It does not
collect data about ad viewers, log in anywhere, or touch private accounts.

Google still owns the service. Keep request volume modest, do not use the output to target or
profile individuals, respect Google's terms and any local rules that apply to political
advertising data, and check a sample of rows before you rely on them for a decision.

### When Google blocks

If Google rate limits every request, the run retries each request three times on fresh proxy
sessions. A term or advertiser that still fails leaves one error row in the dataset and is not
charged. A run that delivers no ad and no advertiser row fails with a status message instead of
ending as a silent empty success. `RUN_SUMMARY` in the key-value store records request counts,
retries, rate limited responses and the per-run caps.

### Development

```bash
npm install
npm test
```

The tests run with `node --test` and are fully offline: parser, input validation, billing order,
budget and proxy helpers are pure functions, and the parser runs against the captured fixtures in
`test/fixtures/`. Captures were taken on 2026-10-24; signed preview URLs had their signature
parameters scrubbed. A failing parser test means the code changed or Google changed the payload,
not that the network flaked.

# Actor input Schema

## `searchTerms` (type: `array`):

Advertiser names or domains, for example nike, adidas.com, samsung. Each term is resolved to advertisers through Google's public suggestion endpoint, then their ads are downloaded. An empty input ({}) searches nike.

## `advertiserIds` (type: `array`):

Advertiser ids from the Ads Transparency Center, for example AR06641858037806006273. Skips the suggestion lookup and fetches these advertisers directly.

## `region` (type: `string`):

Two letter ISO country code (US, GB, DE, MY, SG, TH, ...) or anywhere. Region filtering happens on Google's side with the matching geo target id, so you get the ads shown in that country. anywhere skips the filter and returns ads from every region.

## `maxAdsPerAdvertiser` (type: `integer`):

Hard cap on ad rows per advertiser. Cost guard: 40 ads cost about $0.032 in ad rows. An empty API input ({}) defaults to 10; the form default is 40.

## `maxAdvertisersPerTerm` (type: `integer`):

How many matching advertisers to process for each search term, in the order Google returns them. advertiserIds are always processed in full.

## `maxItems` (type: `integer`):

Global cap on ad rows across every advertiser and every search term. Advertiser rows are not counted against it. The run stops cleanly when the cap is reached.

## `concurrency` (type: `integer`):

How many advertisers are fetched at the same time, each on its own proxy session. 5 is a good default; higher values finish faster and are more likely to be rate limited.

## `proxyConfiguration` (type: `object`):

Required. Direct requests from Apify's datacenter IPs get HTTP 429 from the Ads Transparency Center, while the Apify datacenter proxy returns 200. Keep the default, or supply your own proxyUrls.

## Actor input object example

```json
{
  "searchTerms": [
    "adidas"
  ],
  "region": "anywhere",
  "maxAdsPerAdvertiser": 20,
  "maxAdvertisersPerTerm": 1,
  "maxItems": 100,
  "concurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `ads` (type: `string`):

One row per creative: advertiser, creative id, format, preview link, public ad link, first and last shown dates, region and search term.

## `advertisers` (type: `string`):

One row per matched advertiser: id, name, country, ad count estimate and verification flag.

## `errors` (type: `string`):

Uncharged error rows for search terms or advertisers that could not be fetched.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchTerms": [
        "adidas"
    ],
    "region": "anywhere",
    "maxAdsPerAdvertiser": 20,
    "maxAdvertisersPerTerm": 1,
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("subimpact/google-ads-transparency-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchTerms": ["adidas"],
    "region": "anywhere",
    "maxAdsPerAdvertiser": 20,
    "maxAdvertisersPerTerm": 1,
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("subimpact/google-ads-transparency-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchTerms": [
    "adidas"
  ],
  "region": "anywhere",
  "maxAdsPerAdvertiser": 20,
  "maxAdvertisersPerTerm": 1,
  "maxItems": 100
}' |
apify call subimpact/google-ads-transparency-scraper --silent --output-dataset

```

## MCP server setup

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

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/rhcE2YG97h9OgUquw/builds/2jNpSjEENfqH4j7Yg/openapi.json
