# Google Ads Scraper (`vortex_data/google-ads`) Actor

See what competitors advertise on Google. Per ad: title, body, CTA, landing URL, thumbnail, YouTube preview, regions, platforms, days-active. Sort by days-active to find their winners. Search by keyword, domain or advertiser ID — strict word-boundary match, no `nikey` for `nike`.

- **URL**: https://apify.com/vortex\_data/google-ads.md
- **Developed by:** [VortexData](https://apify.com/vortex_data) (community)
- **Categories:** Developer tools, Lead generation, Other
- **Stats:** 5 total users, 3 monthly users, 80.0% runs succeeded, NaN bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## 🎯 Google Ads Scraper — Transparency Center Intelligence

**See what your competitors are advertising on Google.** Visible ad text, landing URL, thumbnail, video URL, lifecycle stats, regions, platforms — for every active or historical ad of any advertiser in Google's Ads Transparency Center.

Search by **keyword** (`nike`), **domain** (`shopify.com`), **advertiser ID** (`AR…`) or paste a **Transparency-Center URL**. Get back a clean per-ad dataset plus a per-advertiser summary report.

> ⚡ **120 ads/sec listing · 25 ads/sec full enrichment · ~$0.06 per 1,000 ads**

---

### ✨ Why this scraper

| | |
|---|---|
| 🔍 **Keyword search that doesn't lie** — `nike` matches `Nike, Inc.` and `Nike SRL`, but **not** `nikey` / `Nikena`. Strict word-boundary rule, no fuzzy ranking |
| 📝 **Decoded ad text** — title, body, CTA, landing URL extracted from Google's preview JS (Pikmin Bloom → `["Pikmin Bloom", "Enjoy your Pikmin's company wherever you go", "Install"]` + `play.google.com/...`) |
| 📺 **All Google surfaces** — YouTube, Search, Display, Maps, Shopping, Discover, Gmail, Play. Filter by surface |
| 📊 **Per-advertiser summary report** — separate dataset with `longestRunningCreativeId`, `topRegions`, `topPlatforms`, format mix per advertiser |
| 🚀 **Speaks Google's internal RPC API directly** — no browser, no DOM parsing, just JSON over HTTPS |
| 🎯 **Sparse output** — fields with no data are omitted entirely. No `null`/`[]` clutter, table view stays clean |
| ⚡ **Skip-details mode** runs **~4× cheaper** when you only need IDs + dates |
| 🛡️ **Stealthy by default** — Chrome TLS fingerprint via `curl_cffi`, fresh proxy session per request |
| 🧠 **Smart input dedup** — `nike` + `shop.nike.com` + `?query=nike` resolve to one advertiser, processed once |

---

### 📦 What you get

The actor produces **two datasets**.

#### 📋 Default dataset — one row per ad

```json
{
    "advertiserName":    "Niantic, Inc.",
    "advertiserId":      "AR08888592736429539329",
    "creativeId":        "CR01129991885394280449",
    "format":            "VIDEO",
    "adTransparencyUrl": "https://adstransparency.google.com/advertiser/AR.../creative/CR...",

    "isActive":     true,
    "firstShown":   "2026-03-31",
    "lastShown":    "2026-05-04",
    "daysActive":   33,

    "regionsCount": 3,
    "regions":      ["Australia", "Canada", "United States"],

    "preview":      "https://i1.ytimg.com/vi/1izIj43UMaQ/hqdefault.jpg",
    "media":        [
        "https://i1.ytimg.com/vi/1izIj43UMaQ/hqdefault.jpg",
        "https://www.youtube.com/watch?v=1izIj43UMaQ"
    ]
}
````

| Section | Fields | Notes |
|---|---|---|
| 🆔 **Identity** | `advertiserName`, `advertiserId`, `creativeId`, `format`, `adTransparencyUrl` | Stable IDs + ad type (`VIDEO` / `IMAGE` / `TEXT`) + click-through to view on Google |
| ⏱️ **Lifecycle** | `isActive`, `firstShown`, `lastShown`, `daysActive` | Dates as `YYYY-MM-DD` (UTC). `isActive` = `lastShown` within last 7 days. **Sort by `daysActive` desc to find longest-running winners** |
| 🌍 **Reach** | `regionsCount`, `regions[]` | Country names sorted alphabetically. `regionsCount` is handy for sort/filter (e.g. "ads that ran in 5+ countries") |
| 🖼️ **Media** | `preview`, `media[]` | `preview` = single best image URL. The `dataset_schema.json` declares it as `format: "image"` so Apify Console renders it **inline as a thumbnail** in the table — open the **🖼️ Visual preview** view for fast visual scan. `media[]` is the unified bucket: every image URL plus the YouTube watch URL when the ad is YouTube-hosted. Signed `googlevideo.com` streams and 1×1 placeholder pixels (`dot.gif`) are deliberately filtered out. |

> **Sparse output.** Fields with no data (`null`, `[]`, `{}`) are omitted entirely. `format`, `isActive`, `regionsCount` are always kept so filters/sorts work uniformly.

***

### 🚀 Quick start

#### Search by keyword or domain

```json
{
    "searchTargets": ["nike", "shopify.com", "tesla"],
    "maxAdvertisersPerQuery": 5,
    "resultsLimit": 100
}
```

`nike` matches advertiser names containing the word `nike` (`Nike, Inc.`, `Nike SRL`, `Nike Lee`) — not `nikey` or `Nikena`. `shopify.com` strips the TLD and searches `shopify`. Per-keyword cap defaults to 5 advertisers.

#### Track a specific advertiser

```json
{
    "searchTargets": [
        "https://adstransparency.google.com/advertiser/AR08888592736429539329"
    ],
    "resultsLimit": 100
}
```

Or just the bare ID: `["AR08888592736429539329"]`.

#### Filter by region + format + platform + date

```json
{
    "searchTargets": ["AR08888592736429539329"],
    "filterRegion": "DE",
    "filterFormat": "VIDEO",
    "filterPlatform": "YOUTUBE",
    "timeRangePreset": "LAST_30_DAYS"
}
```

#### Speed mode — listing only, ~4× cheaper

```json
{
    "searchTargets": ["AR08888592736429539329"],
    "resultsLimit": 1000,
    "skipDetails": true
}
```

Returns IDs + format + dates + advertiser name. Skips per-creative decoding (no `textLines`, `landingUrl`, `preview.video`).

***

### ⚙️ Input reference

#### 🎯 What to scrape

| Field | Type | Default | Description |
|---|---|---|---|
| `searchTargets` ⭐ | string\[] | required | Mix of keywords (`nike`), domains (`nike.com`), advertiser IDs (`AR…`) or Transparency URLs |

#### 📊 Limits

| Field | Type | Default | Description |
|---|---|---|---|
| `resultsLimit` | int | 50 | Max ads per advertiser. `0` = unlimited |
| `maxAdvertisersPerQuery` | int | 5 | When the target is a keyword/domain, fetch ads from at most this many matched advertisers |

#### 🔎 Filters

| Field | Type | Default | Description |
|---|---|---|---|
| `filterRegion` | string | `ALL` | ISO-2 code (`US`, `DE`, `FR`, `JP`, …) or `ALL` |
| `filterFormat` | enum | `ALL` | `ALL` / `TEXT` / `IMAGE` / `VIDEO` |
| `filterPlatform` | enum | `ALL` | `ALL` / `YOUTUBE` / `SEARCH` / `DISPLAY` / `MAPS` / `SHOPPING` / `DISCOVER` / `GMAIL` / `PLAY` |
| `timeRangePreset` | enum | `ALL_TIME` | `LAST_7_DAYS` / `LAST_30_DAYS` / `LAST_90_DAYS` / `THIS_YEAR` |
| `customStartDate` | string | — | YYYY-MM-DD, overrides preset |
| `customEndDate` | string | — | YYYY-MM-DD, overrides preset |

URL-embedded filters (`?region=US&format=VIDEO&platform=YouTube`) override globals per target.

#### ⚡ Speed mode

| Field | Type | Default | Description |
|---|---|---|---|
| `skipDetails` ⚡ | bool | false | Listing only (IDs, format, dates, advertiser name). Skips per-creative decoding. ~4× faster, ~4× cheaper |

#### ⚙️ Network

| Field | Type | Default | Description |
|---|---|---|---|
| `maxConcurrency` | int | 10 | Parallel RPC calls (5–20 sweet spot) |
| `proxyConfiguration` | object | Residential | Default. Google rate-limits Datacenter IP ranges aggressively, especially under heavy parallel load. Switch to Datacenter only for small batches (≤10 ads) where you want lower per-request cost |

***

### How keyword search works

Google's typeahead returns name-prefix matches: a search for `nike` surfaces `Nike Inc.` along with `nikey`, `Nikena`, etc. We apply a **strict word-boundary filter** (`\bnike\b` regex, case-insensitive Unicode):

| Advertiser name | Match? |
|---|---|
| `Nike, Inc.`           | ✓ — `nike` is a complete word |
| `Nike SRL`             | ✓ |
| `Nike Lee`             | ✓ |
| `Just Do It Nike`      | ✓ |
| `nikey`                | ✗ — `nike` runs into `y`, no boundary |
| `Nikena`               | ✗ — `nike` runs into `na`, no boundary |

No fuzzy ranking, no relevance scoring — strictly matches or doesn't. If you actually want `nikey`, search for `nikey` exactly.

**Domains** (`nike.com`, `shop.nike.com`, `amazon.co.uk`) get the brand part extracted (`nike`, `nike`, `amazon`) and run through the same filter.

**Same advertiser from multiple inputs?** `nike` + `shop.nike.com` + `?query=nike` all resolve to the same advertiser. We dedupe before fetching — you pay once.

### Heuristic limits

`textLines[]` is the visible text from the iframe in document order. For most ads it reads like `[brand, body, CTA]` (e.g. `["Pikmin Bloom", "Enjoy your...", "Install"]`), but some HTML5-bundle formats serve text we can't parse — in that case `textLines` is omitted, but `preview.thumbnail`, `preview.video`, `adTransparencyUrl`, `landingUrl` and lifecycle fields still work.

We deliberately don't auto-split into `title` / `body` / `cta` columns — Google's templates put text in unpredictable order and any auto-split is wrong for some fraction of ads. If you need a single-column title for analysis, `textLines[0]` is usually the brand name.

***

### 📊 Performance & cost

Measured on the live API:

| Workload | Time | Throughput | Cost (Datacenter proxy) |
|---|---:|---:|---:|
| 200 ads, listing only | 1.7 s | 120 ads/sec | $0.005 |
| 50 ads, full enrichment | 2.0 s | 25 ads/sec | $0.013 |
| 1,000 ads, full enrichment | 40 s | 25 ads/sec | $0.06 |
| 10,000 ads, listing only (`skipDetails: true`) | 80 s | 125 ads/sec | $0.05 |

That's **~16,000 fully-enriched ads per Apify compute hour**, or **~430,000 ads in listing-only mode**.

***

### 💡 Use cases

- **Competitive intelligence** — track every ad your rivals run, daily
- **Creative benchmarking** — sort `daysActive` desc, copy what's been running for 6+ months (= what works)
- **Market research** — quantify ad activity per country, format, platform, time period
- **Brand monitoring** — detect impersonators or unauthorized resellers using your name
- **Media planning** — see which Google surfaces (YouTube vs Search vs Maps) competitors prioritize
- **AI / data pipelines** — feed structured ad data into LLMs for clustering, summarization, trend analysis

***

### 📤 Output access

Two views available in Apify Console (Storage → Dataset):

| View | Best for |
|---|---|
| 🖼️ **Visual preview** | Thumbnails rendered inline — fast visual scan, advertiser, format, days active, regions count, click-through to Google |
| 📋 **Full data** | Every column. Use for export or when you need every field. Sort by `daysActive` desc to find longest-running winners |

Export to **JSON / CSV / Excel / XML** with one click. Or pull via API:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("YOUR_USERNAME/google-ads-scraper").call(run_input={
    "searchTargets": ["AR08888592736429539329"],
    "resultsLimit": 200,
})

## Per-creative records (default dataset)
for ad in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(f"{ad['advertiserName']}  {ad['daysActive']}d  {ad.get('textLines', [None])[0]}")

## Per-advertiser summaries (named dataset)
summaries = client.dataset_collection().get_or_create(name="advertisers")
for s in client.dataset(summaries["id"]).iterate_items():
    print(f"{s['advertiserName']}: {s['stats']['creativesFetched']} ads, "
          f"longest-running {s['stats']['longestRunningDays']} days")
```

***

### 🏗️ Under the hood

Speaks Google's **internal `anji` RPC API** — the same backend the Transparency Center web app uses:

| Operation | Endpoint |
|---|---|
| Keyword → advertiser | `POST /anji/_/rpc/SearchService/SearchSuggestions` |
| Advertiser → creatives | `POST /anji/_/rpc/SearchService/SearchCreatives` |
| Advertiser metadata | `POST /anji/_/rpc/LookupService/GetAdvertiserById` |
| Creative details | `POST /anji/_/rpc/LookupService/GetCreativeById` |

**Engineering choices:**

- **`curl_cffi.AsyncSession`** with `impersonate="chrome"` — real Chrome TLS / JA3 fingerprint
- **Fresh proxy session per HTTP call** — `uuid4()` session IDs, every request from a different IP
- **Three independent retry budgets** — `cookies` / `search` / `lookup` each retry 3× with exponential backoff
- **15-second hard timeout** on every individual call
- **Per-run preview-URL dedup** — Google often serves the same ad under 4-7 cache-bust URLs; we fetch once
- **Asyncio semaphore-bounded concurrency** — controlled parallelism, no thundering herd
- **Word-boundary keyword filter** in pure regex — predictable, deterministic, no ranking heuristics

***

### ❓ FAQ

**Q: Is this legal?**
Yes. Google's Ads Transparency Center exposes this data publicly under the EU Digital Services Act and similar transparency commitments. No login required, no private data, no ToS violation. Fair game for competitive analysis, research, journalism, brand monitoring.

**Q: Why are some `impressions` `{min: 0, max: 1000}`?**
Google reports impressions in bucketed ranges. When a creative had < 1,000 impressions, that's the lowest bucket Google exposes. Matches what you see on transparency.google.com directly.

**Q: Why is `platforms` empty for some creatives?**
App-install ads and certain dynamic creatives don't expose per-platform breakdown in the API. We omit the field rather than emit `[]`. Both we and competitor scrapers face the same Google limitation.

**Q: Can I get email / phone / address of advertisers?**
No — and no other scraper can either. Google deliberately excludes contact info from the Transparency API.

**Q: Why does keyword `shopify.com` return Shopify instead of all `.com` advertisers?**
We extract the brand part (`shopify`) from the domain and search by word-boundary. So `shopify.com` ≡ `shopify`. Use a different keyword if you want a different advertiser.

**Q: Why does `nike` not return `nikey`?**
Word-boundary rule (`\bnike\b`). `nikey` doesn't have `nike` as a complete word — it's a substring. If you want `nikey`, search for `nikey` exactly. Logs show what was dropped.

**Q: How fast can I scrape one advertiser's full ad library?**
\~25 ads/sec with full enrichment, ~120 ads/sec listing-only. A typical advertiser with 500 active ads completes in 20–40 seconds.

**Q: What about rate limits?**
Each RPC call goes out on a fresh proxy session ID, so each request hits a different IP. Datacenter proxy is fine for most use; switch to Residential if you see persistent 429s.

***

### ⚠️ Limitations

- **Per-region × per-platform impression cross-tab** — we report `regions[]` + `platforms[]` + overall `impressions` separately, not the cross-tab. If you need "Bulgaria × YouTube → 1000-2000 impressions", let us know.
- **Asset URLs** (`googlevideo.com/videoplayback?...`) are signed and **expire after a few hours** — `preview.video` may 404 if you fetch the URL hours later. Re-run if you need fresh links.
- **Some HTML5-bundle ad templates** don't expose decodable text — `textLines` is omitted for those. The `preview` block (thumbnail/video) and `adTransparencyUrl` still work; click through to see the ad on Google.
- The internal `f.req` JSPB schema is reverse-engineered and may shift if Google changes their protocol — we follow upstream.

***

### 📜 Disclaimer

All data extracted by this actor is publicly available through Google's official [Ads Transparency Center](https://adstransparency.google.com/). This actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Google or Alphabet Inc. Use responsibly and respect rate limits.

# Actor input Schema

## `searchTargets` (type: `array`):

Each line is one target:

• **Keyword** — `nike`, `shopify`, `tesla`. Resolved to advertisers whose **display name contains the keyword as a whole word** (`\bnike\b`). So `nike` matches `Nike, Inc.` / `Nike SRL` / `Nike Lee` but NOT `nikey` or `Nikena`. If you need a substring match, use the exact name (e.g. `nikey`).
• **Domain** — `nike.com`, `www.shopify.com`, `shop.nike.com`. The brand part (`nike`, `shopify`) is extracted and searched as a keyword.
• **Advertiser ID** — `AR08888592736429539329` (Niantic) — exact, no ambiguity
• **Advertiser URL** — `https://adstransparency.google.com/advertiser/AR…`
• **Single creative URL** — `https://adstransparency.google.com/advertiser/AR…/creative/CR…`

URL-embedded filters (`?region=US&format=VIDEO&platform=YouTube`) override the global filters below per-target.

## `resultsLimit` (type: `integer`):

Maximum ads to return per advertiser. 0 = unlimited.

## `maxAdvertisersPerQuery` (type: `integer`):

When a target is a keyword or domain, fetch creatives for at most this many matched advertisers (in Google's typeahead order, after the word-boundary filter).

## `filterRegion` (type: `string`):

ISO-2 country code (US, GB, DE, FR, JP, …) or 'ALL' for worldwide.

## `filterFormat` (type: `string`):

Restrict to a specific creative format.

## `timeRangePreset` (type: `string`):

Pre-defined time window. Custom dates below override this.

## `customStartDate` (type: `string`):

YYYY-MM-DD. Overrides the preset above when set.

## `customEndDate` (type: `string`):

YYYY-MM-DD. Overrides the preset above when set.

## `skipDetails` (type: `boolean`):

Returns only listing-level data (IDs, format, dates, advertiser name). Skips the per-creative deep lookup that pulls regions, decoded title/body/CTA, landing URL and video preview. ~4× faster, ~4× cheaper — useful when you only need the list of creatives, not their content.

## `maxConcurrency` (type: `integer`):

Higher = faster. Default 8 works well with Residential proxy (each request from a different IP, no per-IP rate-limit). If you switch to Datacenter, drop to 3 since the IP pool is much smaller.

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

Residential + country=US by default. Google's Transparency Center is hosted in US datacenters — US residential IPs have clean reputation, lowest round-trip latency, and the smallest chance of hitting the 15s timeout that kills slow overseas exit nodes. The US Apify residential pool is still hundreds of thousands of IPs, so per-IP rate-limit isn't a concern. Drop the country filter only if you specifically need a non-US perspective. Switch to Datacenter only for small fast tests where you don't want to pay residential traffic.

## Actor input object example

```json
{
  "searchTargets": [
    "nike",
    "shopify.com",
    "crypto exchange",
    "AR08888592736429539329",
    "https://adstransparency.google.com/advertiser/AR06674226878443683841",
    "https://adstransparency.google.com/advertiser/AR12815412684405080065?region=US&format=IMAGE"
  ],
  "resultsLimit": 5,
  "maxAdvertisersPerQuery": 3,
  "filterRegion": "US",
  "filterFormat": "ALL",
  "timeRangePreset": "LAST_30_DAYS",
  "skipDetails": false,
  "maxConcurrency": 8,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `creativesDataset` (type: `string`):

All scraped ad creatives — one row per ad. Pick the format you need on the dataset page (JSON, CSV, Excel, XML).

## `consoleRun` (type: `string`):

Browse all datasets, logs and storage for this run.

# 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 = {
    "searchTargets": [
        "nike",
        "shopify.com",
        "crypto exchange",
        "AR08888592736429539329",
        "https://adstransparency.google.com/advertiser/AR06674226878443683841",
        "https://adstransparency.google.com/advertiser/AR12815412684405080065?region=US&format=IMAGE"
    ],
    "resultsLimit": 5,
    "maxAdvertisersPerQuery": 3,
    "filterRegion": "US",
    "timeRangePreset": "LAST_30_DAYS"
};

// Run the Actor and wait for it to finish
const run = await client.actor("vortex_data/google-ads").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 = {
    "searchTargets": [
        "nike",
        "shopify.com",
        "crypto exchange",
        "AR08888592736429539329",
        "https://adstransparency.google.com/advertiser/AR06674226878443683841",
        "https://adstransparency.google.com/advertiser/AR12815412684405080065?region=US&format=IMAGE",
    ],
    "resultsLimit": 5,
    "maxAdvertisersPerQuery": 3,
    "filterRegion": "US",
    "timeRangePreset": "LAST_30_DAYS",
}

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

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

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

```

## CLI example

```bash
echo '{
  "searchTargets": [
    "nike",
    "shopify.com",
    "crypto exchange",
    "AR08888592736429539329",
    "https://adstransparency.google.com/advertiser/AR06674226878443683841",
    "https://adstransparency.google.com/advertiser/AR12815412684405080065?region=US&format=IMAGE"
  ],
  "resultsLimit": 5,
  "maxAdvertisersPerQuery": 3,
  "filterRegion": "US",
  "timeRangePreset": "LAST_30_DAYS"
}' |
apify call vortex_data/google-ads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=vortex_data/google-ads",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Google Ads Scraper",
        "description": "See what competitors advertise on Google. Per ad: title, body, CTA, landing URL, thumbnail, YouTube preview, regions, platforms, days-active. Sort by days-active to find their winners. Search by keyword, domain or advertiser ID — strict word-boundary match, no `nikey` for `nike`.",
        "version": "1.0",
        "x-build-id": "HdnT1MO3RogGdb7Lq"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/vortex_data~google-ads/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-vortex_data-google-ads",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/vortex_data~google-ads/runs": {
            "post": {
                "operationId": "runs-sync-vortex_data-google-ads",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/vortex_data~google-ads/run-sync": {
            "post": {
                "operationId": "run-sync-vortex_data-google-ads",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "searchTargets"
                ],
                "properties": {
                    "searchTargets": {
                        "title": "🎯 What to scrape",
                        "type": "array",
                        "description": "Each line is one target:\n\n• **Keyword** — `nike`, `shopify`, `tesla`. Resolved to advertisers whose **display name contains the keyword as a whole word** (`\\bnike\\b`). So `nike` matches `Nike, Inc.` / `Nike SRL` / `Nike Lee` but NOT `nikey` or `Nikena`. If you need a substring match, use the exact name (e.g. `nikey`).\n• **Domain** — `nike.com`, `www.shopify.com`, `shop.nike.com`. The brand part (`nike`, `shopify`) is extracted and searched as a keyword.\n• **Advertiser ID** — `AR08888592736429539329` (Niantic) — exact, no ambiguity\n• **Advertiser URL** — `https://adstransparency.google.com/advertiser/AR…`\n• **Single creative URL** — `https://adstransparency.google.com/advertiser/AR…/creative/CR…`\n\nURL-embedded filters (`?region=US&format=VIDEO&platform=YouTube`) override the global filters below per-target.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "resultsLimit": {
                        "title": "📊 Results limit",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Maximum ads to return per advertiser. 0 = unlimited.",
                        "default": 50
                    },
                    "maxAdvertisersPerQuery": {
                        "title": "👥 Max advertisers per keyword",
                        "minimum": 1,
                        "type": "integer",
                        "description": "When a target is a keyword or domain, fetch creatives for at most this many matched advertisers (in Google's typeahead order, after the word-boundary filter).",
                        "default": 5
                    },
                    "filterRegion": {
                        "title": "🌍 Region filter",
                        "type": "string",
                        "description": "ISO-2 country code (US, GB, DE, FR, JP, …) or 'ALL' for worldwide.",
                        "default": "ALL"
                    },
                    "filterFormat": {
                        "title": "🎬 Ad format filter",
                        "enum": [
                            "ALL",
                            "TEXT",
                            "IMAGE",
                            "VIDEO"
                        ],
                        "type": "string",
                        "description": "Restrict to a specific creative format.",
                        "default": "ALL"
                    },
                    "timeRangePreset": {
                        "title": "📅 Quick date range",
                        "enum": [
                            "ALL_TIME",
                            "LAST_7_DAYS",
                            "LAST_30_DAYS",
                            "LAST_90_DAYS",
                            "THIS_YEAR"
                        ],
                        "type": "string",
                        "description": "Pre-defined time window. Custom dates below override this.",
                        "default": "ALL_TIME"
                    },
                    "customStartDate": {
                        "title": "📅 Custom start date",
                        "type": "string",
                        "description": "YYYY-MM-DD. Overrides the preset above when set."
                    },
                    "customEndDate": {
                        "title": "📅 Custom end date",
                        "type": "string",
                        "description": "YYYY-MM-DD. Overrides the preset above when set."
                    },
                    "skipDetails": {
                        "title": "⚡ Speed mode (skip ad copy decoding)",
                        "type": "boolean",
                        "description": "Returns only listing-level data (IDs, format, dates, advertiser name). Skips the per-creative deep lookup that pulls regions, decoded title/body/CTA, landing URL and video preview. ~4× faster, ~4× cheaper — useful when you only need the list of creatives, not their content.",
                        "default": false
                    },
                    "maxConcurrency": {
                        "title": "⚙️ Max concurrent RPC calls",
                        "minimum": 1,
                        "maximum": 50,
                        "type": "integer",
                        "description": "Higher = faster. Default 8 works well with Residential proxy (each request from a different IP, no per-IP rate-limit). If you switch to Datacenter, drop to 3 since the IP pool is much smaller.",
                        "default": 8
                    },
                    "proxyConfiguration": {
                        "title": "🌐 Proxy configuration",
                        "type": "object",
                        "description": "Residential + country=US by default. Google's Transparency Center is hosted in US datacenters — US residential IPs have clean reputation, lowest round-trip latency, and the smallest chance of hitting the 15s timeout that kills slow overseas exit nodes. The US Apify residential pool is still hundreds of thousands of IPs, so per-IP rate-limit isn't a concern. Drop the country filter only if you specifically need a non-US perspective. Switch to Datacenter only for small fast tests where you don't want to pay residential traffic.",
                        "default": {
                            "useApifyProxy": true,
                            "apifyProxyGroups": [
                                "RESIDENTIAL"
                            ],
                            "apifyProxyCountry": "US"
                        }
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
