# Google Ads Monitor — Competitor Ads, New Ads Only, Full Ad Copy (`sumitr_mardy/google-ads-monitor`) Actor

Track competitors' Google ads from the Ads Transparency Center. Monitor mode returns only ads Google first showed since your last run. Text-ad copy read by OCR, video ad copy, landing URLs, regions. Flat schema, never null. Built for pipelines and AI agents.

- **URL**: https://apify.com/sumitr_mardy/google-ads-monitor.md
- **Developed by:** [Sumitr Mardy](https://apify.com/sumitr_mardy) (community)
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 ad returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Monitor — Competitor Ads, New Ads Only, Full Ad Copy

Track your competitors' Google ads from the [Google Ads Transparency Center](https://adstransparency.google.com/). Give it domains or advertiser IDs, put it on a schedule, and each run returns **only the ads Google first showed since the previous run** — with the headline and description of text ads read for you.

One flat JSON record per ad. Every key is present on every record, and nothing is ever `null`.

***

### Why this one

Most Google Ads scrapers are built to download an advertiser's archive once. Monitoring is a different job, and three things break when you try to do it with a one-shot scraper:

1. **"New" is decided by Google's own first-shown date, not by "an ID I haven't seen".** The Transparency Center lists ads by when they were *last* shown, so old ads drift back into the first pages every time they run again. Measured overnight on 20 large advertisers: **2,401 ad IDs were unseen the day before, and 2 of them were actually new.** A monitor that bills every unseen ID charges you for reruns of last year's ads. This one returns an ad only when its `firstShown` falls after your previous check.
2. **Text-ad copy is included.** About 75% of Google ads are text ads, and **96% of those exist in the archive only as a screenshot** — there is no headline or description field in the data. This actor reads the screenshot (OCR) and returns `headline`, `description` and `displayUrl`, in the base price. Measured accuracy is below. Video ads come with their headline, description, call to action, landing URL and YouTube link too (92% of video ads).
3. **You pay for the monitoring, not for re-reading the archive.** A daily check of 50 competitors is 50 × $0.01 plus the genuinely new ads. No seed run, no per-run fee.

***

### Status — what works and what does not

Stated up front, because you would find it anyway.

| Feature | Status |
|---|---|
| Domains, advertiser IDs, Transparency Center URLs as input | works |
| Region filter (any ISO country code, or `anywhere`) | works |
| Monitor mode (`firstShown` after the previous check) | works |
| All mode (newest-shown ads, up to a limit) | works |
| Format, first/last shown, days served, variations, served regions | works, every ad |
| Text-ad copy (headline, description, display URL) | works: **33/40 ads exact** on both fields, headline 36/40, on two untouched test sets (details below) |
| Korean text-ad copy | **weak**: readable, but word spacing is wrong |
| Copy of video ads (headline, description, call to action, landing URL, YouTube link) | works: **92% of video ads** with copy, 93% with a YouTube link, read from the ad's own data across all its variations. App-install videos give the app name and store link |
| Copy of image ads | partial: **~15%**, only when the ad carries its own data; image-only banners have no text to read |
| Impressions per region, audience targeting | not extracted |
| Stopped / paused ads | not reported yet |
| Age-restricted ads | not visible (no login is used) |

***

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `targets` | string\[] | — | One per line: a domain (`hubspot.com`), an advertiser ID (`AR10072600183532683265`), or a Transparency Center URL (advertiser, creative or domain search). Required. |
| `mode` | `monitor` | `all` | `monitor` | `monitor`: only ads first shown since the last run for that target. `all`: every ad, newest-shown first, up to `maxAdsPerTarget`. |
| `region` | string | `anywhere` | Two-letter country code to see only ads served there. A region inside a pasted URL wins over this. |
| `lookbackDays` | integer 1–365 | `7` | How far back the **first** monitor run of a target reaches. Later runs start where the previous one ended. |
| `maxAdsPerTarget` | integer | `100` | All mode only. Monitor mode returns every new ad. |
| `adCopy` | boolean | `true` | Read ad copy (text ads by OCR, video and image ads from their own data). Turn off for faster runs when you only need IDs and dates. |
| `monitorName` | string | `default` | Keeps separate schedules apart. A daily and a weekly schedule on the same targets need different names, or each sees the other's runs as its own. |
| `maxPagesPerCheck` | integer | `100` | Safety cap on listing depth (40 ads per page). |
| `concurrency` | integer 1–10 | `4` | Targets checked side by side. |
| `residentialFallback` | boolean | `true` | A target that keeps getting blocked on your proxy switches to Apify residential for the rest of the run, at no extra charge to you. |
| `proxyConfiguration` | object | Apify Proxy | Datacenter by default. |

```json
{
  "targets": ["zoom.us", "monday.com", "AR10072600183532683265"],
  "mode": "monitor",
  "region": "US",
  "lookbackDays": 7
}
```

Then schedule it (Apify Console → Schedules) daily or hourly with the same input.

#### A domain is a landing-domain search

Searching `zoom.us` finds every advertiser whose ads land on `zoom.us`, not only Zoom itself. Across 20 large brands the main advertiser held 83% of the creatives; the rest are resellers, agencies, affiliates and the brand's other legal entities (`zoom.us`: 37 advertisers, `squarespace.com`: 25). That is useful, because it catches every entity of a brand, but it is not filtered for you. Every record carries `advertiserId` and `advertiserName`, so you can keep only the brand, or pass the advertiser ID (`AR…`) instead of the domain.

***

### Output

One record per ad. A real one, from a run on 2026-10-20:

```json
{
  "target": "https://adstransparency.google.com/advertiser/AR10072600183532683265?region=anywhere",
  "targetType": "advertiser",
  "region": "anywhere",
  "advertiserId": "AR10072600183532683265",
  "advertiserName": "Hubspot, Inc.",
  "creativeId": "CR02685094814793334785",
  "adUrl": "https://adstransparency.google.com/advertiser/AR10072600183532683265/creative/CR02685094814793334785",
  "format": "TEXT",
  "landingDomain": "hubspot.com",
  "firstShown": "2026-01-29T08:00:00.000Z",
  "lastShown": "2026-10-20T05:32:10.000Z",
  "daysServed": 264,
  "regions": ["US", "CA"],
  "variations": 1,
  "headline": "HubSpot for Startups - Bootstrap to Series A",
  "description": "Partnered with top VCs and accelerators. Get exclusive startup pricing and resources. From...",
  "displayUrl": "www.hubspot.com/",
  "landingUrl": "",
  "callToAction": "",
  "videoUrl": "",
  "copySource": "ocr",
  "copyLanguage": "eng",
  "previewImageUrl": "https://tpc.googlesyndication.com/archive/simgad/12284280382862371734",
  "previewScriptUrl": "",
  "checkedAt": "2026-10-20T06:48:02.980Z"
}
```

The trailing "From..." is Google's own truncation in the archived ad, not an OCR cut. A
text ad read from its screenshot has no landing URL (the screenshot shows only the display
URL), so `landingDomain` comes from the display URL here. A video ad (hubspot.fr, 2026-10-20),
copy fields only:

```json
{
  "format": "VIDEO",
  "landingDomain": "hubspot.fr",
  "headline": "CRM de HubSpot",
  "description": "Configurez votre CRM en quelques minutes pour accélérer votre activité dès aujourd'hui.",
  "displayUrl": "hubspot.fr",
  "landingUrl": "https://www.hubspot.fr/crm/e010a-7-0",
  "callToAction": "En savoir plus",
  "videoUrl": "https://www.youtube.com/watch?v=wTEjgF1qocM",
  "copySource": "contentjs"
}
```

#### Field guarantees

Every key is present on every record. Strings are `''` when empty, arrays are `[]`, numbers are `0`. **Never `null`**, so a pipeline can read any field without a guard.

| key | type | notes |
|---|---|---|
| `target`, `targetType` | string | the input as given; `domain` or `advertiser` |
| `region` | string | ISO alpha-2 filter used, or `anywhere` |
| `advertiserId`, `advertiserName`, `creativeId`, `adUrl` | string | |
| `format` | string | `TEXT`, `IMAGE`, `VIDEO`, or `UNKNOWN` |
| `landingDomain` | string | the searched domain for domain targets; for advertiser targets, taken from the ad's landing URL, else its display URL; `''` when the ad shows neither |
| `firstShown`, `lastShown` | string | ISO 8601 |
| `daysServed`, `variations` | number | |
| `regions` | string\[] | ISO alpha-2 codes where it was served |
| `headline`, `description`, `displayUrl` | string | `''` when there is no copy |
| `landingUrl`, `callToAction` | string | from the ad's own data (video and some image ads); `''` otherwise |
| `videoUrl` | string | YouTube link for video ads; `''` otherwise |
| `copySource` | string | `ocr`, `contentjs` or `none` |
| `copyLanguage` | string | OCR language pack that read it, or `''` |
| `previewImageUrl`, `previewScriptUrl` | string | one is usually `''` |
| `checkedAt` | string | ISO 8601 |

Each run also writes a `SUMMARY` record to its key-value store: per target, its status (`ok`, `failed`, `partial_cost_limit`, …), pages read, ads scanned and returned, and whether the listing cap was hit (`truncated`).

***

### How monitor mode works

For each target, state is kept per `monitorName` + target + region:

1. The check starts at the previous check's time. On a target's first run it starts `lookbackDays` ago.
2. The listing is read newest-shown first, and stops at the first page that reaches back past that time — nothing shown before it can be new.
3. An ad is new when Google's `firstShown` is after the start (with a 2-day margin, because the archive publishes ads up to about a day late) and it has not been returned before. The margin never bills an ad twice.
4. Only new ads are looked up, read and returned.
5. **The state moves forward only after the ads are delivered and charged.** A target that failed or hit your cost limit keeps its old state, so the next run returns what this one could not.

If every target fails — Google blocking, or the archive changing — the run ends **FAILED** with the cause. It never hands back an empty dataset that would look like "no new ads".

How many new ads to expect: it varies a lot by advertiser. Measured on large brands, from 0 per day (booking.com, expedia.com) to more than 17 per day (monday.com: 157 in 9 days).

***

### Text-ad copy: how it is read, and how accurate it is

Google renders each part of a search ad in a fixed colour — the headline blue, the URL and description grey. The actor reads the archived screenshot with Tesseract, assigns each word a field by its colour, and picks the OCR language from the countries the ad was served in (an ad served only in Japan is read with Japanese and English). Ads delivered as script rather than image (about 4% of text ads) are parsed directly, with `copySource: "contentjs"`.

Measured on two sets of 20 hand-transcribed ads, from 20 large advertisers, that the rules were never tuned on. The second set was drawn later from live ads, with more photo layouts and non-English copy, and it scored lower. Both numbers are shown:

| | set 1 | set 2 | together |
|---|---|---|---|
| headline **and** description exact | 19 / 20 | 14 / 20 | **33 / 40 (83%)** |
| headline exact | 20 / 20 | 16 / 20 | **36 / 40 (90%)** |
| character error rate, headline / description | 0.0% / 0.2% | 0.6% / 1.2% | |

A miss is usually one glyph or a clipped word, not a wrong ad: the character error rate stays around 1%. Known misses, all measured:

- **Korean**: the Korean model splits words by syllable, so spacing comes out wrong. The words are readable; exact-match fails.
- **Accents on ads served in many countries**: an ad served in more than two language markets is read with English, so a Romanian ad served across Europe comes back as "Automata" instead of "Automată".
- **Text cut by a photo**: when an image overlaps the copy in Google's own archive render, the hidden words are not in the source at all.
- **Single glyphs**: an occasional misread or dropped character ("–" read as "—", "AEO" as "AEOQ", a dropped " — ").

For comparison, a general OCR pass over the same 20 ads in set 2 reproduced the headline exactly on 12 and the description on 3, because it reads the whole card (advertiser name, sitelinks, ratings) as one block of text. Splitting the fields by colour is what makes the copy usable as data.

`copyLanguage` tells you which language pack produced the copy, so you can treat non-English reads with appropriate care.

***

### Pricing

**Pay per event. No setup run, and no per-run fee of ours.** Apify's standard start event
applies ($0.00005 per run per GB of memory, so $0.00005 at the default 1 GB); in return
Apify covers the first 5 seconds of compute.

| Event | Price | Charged when |
|---|---|---|
| `ad` | **$1.20 / 1,000** (Free) → $0.90 Bronze → $0.80 Silver → **$0.70 Gold** | each ad returned, copy included. In monitor mode only new ads are returned |
| `target_checked` | **$0.01** | each domain or advertiser checked in a **monitor-mode** run, whether or not anything changed |

Example: 50 competitors checked daily for 30 days is 1,500 checks ($15) plus the new ads they launch. All mode charges `ad` only.

The residential fallback is included: when it kicks in, the proxy traffic is paid by the actor, not added to your bill. Set a maximum cost per run in Apify Console and the actor stops cleanly at it — it never pushes ads it cannot charge for, and the monitor state is kept so the next run picks up the rest.

***

### Known limits

- **The Transparency Center is not an official API.** The actor reads the same endpoints the website uses. If Google changes them, the run fails loudly with "the RPC may have changed" rather than returning partial data.
- **Google blocks busy IPs.** Checks run on your Apify datacenter proxy first. A target blocked twice in a row switches to residential for the rest of the run. Apify's Free plan has only 5 shared datacenter IPs, so expect the fallback to be used there.
- **Big advertisers are expensive to read, not to bill.** The listing has no "first shown" sort, so a check reads every ad shown since the last check: 20 pages for zapier.com, about 150 for salesforce.com over one day. `maxPagesPerCheck` (100) caps it; a capped target is marked `truncated` in `SUMMARY`. Checking big brands more often keeps each check short.
- **The archive is up to about a day behind** the live ads. An ad shown this morning may appear in tomorrow's run.
- **Copy accuracy** is 83% exact on both fields over 40 ads (above), with about 1% of characters wrong. Korean and accented copy in multi-country ads are the weak spots.
- **Image-only banners have no copy.** Most image ads are a bare picture with no text data behind them (copy found for about 15%). Their `previewImageUrl` is given.
- **Speed**: about a second per ad when reading copy at the default 1 GB. Give the run more memory to go faster, or turn `adCopy` off when you only need IDs and dates.
- **Not included**: impressions, audience targeting, stopped-ad reports, age-restricted ads.

***

### Use it from code

**One call, results back** (runs up to 300 seconds; keep `maxAdsPerTarget` small, or use the async pattern below):

```bash
curl -X POST "https://api.apify.com/v2/acts/sumitr_mardy~google-ads-monitor/run-sync-get-dataset-items" \
  -H "Authorization: Bearer YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"targets": ["hubspot.com"], "mode": "all", "maxAdsPerTarget": 20}'
```

**Node, any size:**

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

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });

const run = await client.actor('sumitr_mardy/google-ads-monitor').call({
    targets: ['zoom.us', 'hubspot.com'],
    mode: 'monitor',
    region: 'US',
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
// Keep only the brand itself, not resellers landing on its domain:
const ownAds = items.filter((ad) => ad.advertiserName.startsWith('Zoom'));
for (const ad of ownAds) console.log(ad.firstShown, ad.format, ad.headline, ad.adUrl);
```

**Daily competitor alerts:** create a Schedule in Apify Console (daily, or hourly for fast movers) with `mode: "monitor"`, and connect the run to Slack, Google Sheets, n8n, Make or a webhook. Each run delivers only the ads that are new since the last one, so the alert is the dataset itself.

***

### Use it from AI agents (MCP)

This Actor is a tool on the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp), so Claude, Cursor, VS Code or any MCP client can call it. Load only this tool: a short tool list keeps the agent from picking the wrong one.

**Recommended: hosted server, OAuth sign-in (nothing secret on disk).** Add to your MCP client config (`claude_desktop_config.json`, `.cursor/mcp.json`, …):

```json
{
  "mcpServers": {
    "google-ads-monitor": {
      "url": "https://mcp.apify.com?tools=sumitr_mardy/google-ads-monitor"
    }
  }
}
```

**With an API token instead** (clients without OAuth, or unattended setups):

```json
{
  "mcpServers": {
    "google-ads-monitor": {
      "url": "https://mcp.apify.com?tools=sumitr_mardy/google-ads-monitor",
      "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
    }
  }
}
```

**Local server (stdio):**

```json
{
  "mcpServers": {
    "google-ads-monitor": {
      "command": "npx",
      "args": ["-y", "@apify/actors-mcp-server@latest", "--tools", "sumitr_mardy/google-ads-monitor"],
      "env": { "APIFY_TOKEN": "YOUR_APIFY_TOKEN" }
    }
  }
}
```

The hosted server also gives the agent this Actor's output schema, so it knows every field before the first call. `apify mcp install <client>` writes the config for you if you use the Apify CLI. Get a token at [Apify Console → API & Integrations](https://console.apify.com/settings/integrations), and never commit it.

**Tips for agents:**

- *"What ads is X running right now?"* Use `mode: "all"` with `maxAdsPerTarget` of 10–25. Reading ad copy takes about a second per ad.
- *"What did X launch this week?"* Use `mode: "monitor"` with `lookbackDays: 7`. Calling it again later returns only what is new since then.
- To filter to the brand, pass its advertiser ID (`AR…`) instead of the domain, or filter by `advertiserName`.
- The MCP `call-actor` tool waits up to 45 seconds. A longer run keeps going: fetch the results with `get-dataset-items` and the run's `datasetId`.
- Every record has the same keys and never a `null`, so an agent can read `headline`, `firstShown` or `landingUrl` without a guard. `copySource: "none"` means the ad really has no text, such as an image-only banner. It does not mean the read failed.

# Actor input Schema

## `targets` (type: `array`):

Competitors to check, one per item: a domain ("hubspot.com"), a Google advertiser ID ("AR10072600183532683265"), or a Google Ads Transparency Center URL (advertiser, creative or domain search). A domain finds every advertiser whose ads land on it, resellers and agencies included; every output record names its advertiser. Example: \["hubspot.com", "AR10072600183532683265"].

## `mode` (type: `string`):

"monitor" (default): only ads Google first showed since the previous run for each target, judged by Google's own first-shown date; built for daily or hourly schedules, and a target's first run looks back lookbackDays. "all": the target's ads, most recently shown first, up to maxAdsPerTarget; use it for a one-off look at what a competitor is running.

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

Two-letter ISO country code (US, GB, DE, JP, VN...) to see only ads served in that country, or "anywhere" for all countries. A region inside a pasted Transparency Center URL takes precedence.

## `lookbackDays` (type: `integer`):

Monitor mode only: how many days back the FIRST run for a target reaches. Later runs start where the previous run ended, so they return only what is new.

## `maxAdsPerTarget` (type: `integer`):

All mode only: how many ads to return per target, most recently shown first. Monitor mode always returns every new ad. For a quick answer to an AI agent, keep it small (10-25): reading ad copy takes about a second per ad.

## `adCopy` (type: `boolean`):

Read the ad copy: text ads from Google's archived screenshot (OCR); video and some image ads from their own data (headline, description, call to action, landing URL, YouTube link). Turn off for much faster runs when only IDs, dates and countries are needed.

## `monitorName` (type: `string`):

Monitor mode only. Keeps separate schedules apart: a daily and a weekly schedule on the same targets need different names, or each will treat the other's runs as its own. Any short name.

## `maxPagesPerCheck` (type: `integer`):

Safety cap on how deep one target's listing is read (40 ads per page). Very large advertisers show thousands of ads a day; the run log and SUMMARY say when a target hit the cap.

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

How many targets are checked side by side. Pages of a single target are always read in order.

## `residentialFallback` (type: `boolean`):

When Google keeps blocking the proxy for one target, switch that target to Apify residential proxy for the rest of the run. Included in the price: no extra charge to you.

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

Apify Proxy (datacenter) by default; the Actor changes IP by itself when Google pushes back. You normally do not need to change this.

## Actor input object example

```json
{
  "targets": [
    "hubspot.com",
    "zoom.us"
  ],
  "mode": "monitor",
  "region": "anywhere",
  "lookbackDays": 7,
  "maxAdsPerTarget": 100,
  "adCopy": true,
  "monitorName": "default",
  "maxPagesPerCheck": 100,
  "concurrency": 4,
  "residentialFallback": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All ad records of this run as JSON. In monitor mode: only ads Google first showed since the previous run for each target. Field definitions are in the dataset schema.

## `summary` (type: `string`):

One JSON record per run: per target its status (ok, failed, invalid, partial_cost_limit, skipped_cost_limit), pages read, ads scanned and returned, and whether the listing cap was hit (truncated).

# 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 = {
    "targets": [
        "hubspot.com",
        "zoom.us"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("sumitr_mardy/google-ads-monitor").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 = { "targets": [
        "hubspot.com",
        "zoom.us",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("sumitr_mardy/google-ads-monitor").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 '{
  "targets": [
    "hubspot.com",
    "zoom.us"
  ]
}' |
apify call sumitr_mardy/google-ads-monitor --silent --output-dataset

```

## MCP server setup

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

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/mgzcrqEm3myx3P5ei/builds/3uYDQwpFq6Ca6quc2/openapi.json
