# Google Ads Brand Bidding Monitor (`herazur/google-brand-bidding-affiliate-compliance-radar`) Actor

Monitor branded Google searches to detect competitor ads, affiliate brand bidding, unauthorized resellers, new paid-search bidders, and ad-copy changes across countries and devices.

- **URL**: https://apify.com/herazur/google-brand-bidding-affiliate-compliance-radar.md
- **Developed by:** [Furkan Toluç](https://apify.com/herazur) (community)
- **Categories:** SEO tools, Lead generation, Developer tools
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $390.00 / 1,000 brand-market reports

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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 Brand Bidding & Affiliate Compliance Radar

Monitor branded Google searches across countries and devices. Detect known competitors, unrecognized advertisers, authorized partners, repeated bidders, new landing domains, and ad-copy changes over time.

This Actor is a branded paid-search monitoring and compliance-signal product. It uses Apify's maintained `apify/google-search-scraper` as its collection primitive and adds deterministic normalization, evidence, classification, compact history, and conservative change detection. It is not a general Google scraper, an ad-spend estimator, or a legal trademark detector.

### What it does

For one brand per run, the Actor:

1. Checks up to ten branded terms in as many as five countries, on desktop and/or mobile.
2. Performs one to three independent observations per country-device market.
3. Extracts paid Google results and normalizes their registrable landing domains.
4. Classifies advertisers as `OFFICIAL`, `AUTHORIZED`, `KNOWN_COMPETITOR`, or `UNRECOGNIZED` from your lists.
5. Measures check coverage, landing-domain coverage, observation depth, and evidence strength.
6. Compares every successful market with its previous successful baseline under the same `monitorKey`.
7. Writes one Dataset item per market, a run-level `REPORT.md`, an `OUTPUT` record, measured `COSTS.json`, and compact named-store state.

It is designed for paid search teams, affiliate managers, performance marketers, brand protection teams, agencies, and SaaS or ecommerce brands.

### Why repeated observations matter

Google Ads are probabilistic: auctions, targeting, budgets, and personalization cause results to vary. A duplicate ad row within one SERP is not repeated evidence. `REPEATED_BIDDER` requires the same landing domain in at least two independent observations of the current country-device market.

Most monitors should use the default of two observations. A single observation remains useful, but cannot produce strong repeated evidence.

Google paid-search observations depend on an external upstream Actor and can occasionally be slow or unavailable. Each observation is protected by a bounded runtime and charge ceiling. The parent makes one child call per logical observation and does not add a retry loop on top of the upstream Actor's own behavior. Failed observations are treated as missing evidence rather than as evidence that no ads exist.

### Input

```json
{
  "monitorKey": "acme-brand-monitor",
  "brandName": "Acme",
  "brandTerms": ["acme", "acme pricing", "acme coupon", "acme login"],
  "officialDomains": ["acme.com"],
  "authorizedDomains": ["partner-a.com"],
  "competitorDomains": ["competitor-one.com", "competitor-two.com"],
  "countries": ["US", "GB", "DE"],
  "devices": ["desktop", "mobile"],
  "observationsPerMarket": 2,
  "languageCode": "en",
  "debug": false,
  "resetState": false
}
```

`monitorKey` is the stable identity for a monitoring configuration. It is normalized and hashed before being used in a named Key-Value Store; arbitrary input is never used directly as a storage name. `resetState` removes only this monitor's `STATE` record.

Domains may be entered as domains, subdomains, or URLs. They are reduced with Public Suffix List-aware logic, so `store.partner.co.uk` becomes `partner.co.uk` rather than `co.uk`.

### Output

The default Dataset contains one item per brand + country + device report—not one item per ad. A report includes a flat Store-friendly summary plus complete nested evidence:

```json
{
  "brandName": "Acme",
  "country": "US",
  "device": "mobile",
  "reportStatus": "SUCCESS",
  "baselineStatus": "EXISTING_BASELINE",
  "evidenceStrength": 100,
  "coverage": {
    "expectedChecks": 8,
    "successfulChecks": 8,
    "successfulCheckRate": 1,
    "landingDomainCoverage": 1
  },
  "summary": {
    "sponsoredAdsObserved": 6,
    "uniqueAdvertiserDomains": 3,
    "knownCompetitorsObserved": 1,
    "unrecognizedDomainsObserved": 1,
    "repeatedBidders": 2,
    "newLandingDomains": 1,
    "newAdCopies": 1
  },
  "advertisers": [],
  "findings": [],
  "changes": {
    "newDomains": ["competitor-two.com"],
    "newAdFingerprints": ["..."],
    "previouslyObservedDomainsNotSeenThisRun": ["coupon-example.com"]
  }
}
```

Evidence strength measures data quality, not advertiser severity. Its documented formula is:

```text
round(60 × successful-check coverage
    + 20 × effective landing-domain coverage
    + 20 × observation depth)
```

When successfully collected SERPs contain no ads, paid-ad parsing and landing-domain coverage are reported as `null` (not applicable) and do not penalize evidence strength. When no checks succeed, evidence strength is zero. Observation depth is 0.5 for one usable observation and 1 for two or more.

Report status thresholds are `SUCCESS` at 90%+ successful checks, `PARTIAL` from 50% through below 90%, and `INSUFFICIENT_EVIDENCE` below 50%. Absence-based signals require at least 80% coverage.

### Signals

- `COMPETITOR_ON_BRAND_TERM`: a landing domain matches your known competitor list.
- `AUTHORIZED_PARTNER_OBSERVED`: a landing domain matches your authorized list.
- `NEW_UNRECOGNIZED_ADVERTISER`: an unrecognized domain is new relative to a prior successful baseline.
- `REPEATED_BIDDER`: a domain appeared in at least two independent current-run observations.
- `TRADEMARK_IN_AD_COPY_SIGNAL`: monitored brand wording appeared in title or description; this is not a legal determination.
- `POTENTIAL_AFFILIATE_POLICY_RISK`: an unrecognized advertiser appeared on a coupon, promo, discount, or deal-oriented branded query; no affiliate relationship is asserted.
- `NEW_LANDING_DOMAIN` and `NEW_AD_COPY`: changes from the previous successful baseline.
- `OWN_AD_NOT_OBSERVED`, `PREVIOUS_BIDDER_NOT_OBSERVED`, and `NO_SPONSORED_ADS_OBSERVED`: conservative absence observations, emitted only with adequate check coverage.
- `INSUFFICIENT_EVIDENCE`: missing SERPs make absence-based conclusions unsafe.

### Monitoring over time

The first successful run establishes a baseline and never calls all current advertisers “new.” Later successful runs compare domains and normalized ad-copy fingerprints with the preceding successful market state. Incomplete markets do not overwrite a healthy baseline.

Save the Actor input as an Apify Task and schedule it daily or weekly to turn one-time branded SERP observations into a monitoring history. Reuse exactly the same `monitorKey` for that monitor. This repeated use is the product's recurring-value mechanism.

### Pricing concept

The Actor is prepared for Apify Pay Per Event with one `brand-market-report` event for one `SUCCESS` brand-country-device report. It does not charge per ad, advertiser, SERP row, or finding. Before another market starts, the Actor checks whether the run's remaining PPE limit can support a report. Partial and insufficient-evidence reports are written for diagnosis but are not charged as successful reports.

The dollar event price is configured in Apify Console, not hardcoded. Choose it only after measuring the current upstream Google Search Results Scraper cost and the Store creator share. `COSTS.json` records child run IDs, charged event counts, and actual `usageTotalUsd` when Apify exposes it; otherwise it explicitly reports cost as unavailable.

### Integrations

Consume Dataset and Key-Value Store output through Apify's normal API and integration ecosystem, including schedules, webhooks, Make, Zapier, Google Sheets, and other standard platform connections. This Actor intentionally does not build custom Slack, email, CRM, or dashboard integrations.

### Limitations and interpretation

- Google Ads are probabilistic. **Not observed does not mean stopped bidding.**
- Missing upstream results are missing evidence, never evidence that no ads existed.
- Advertiser classifications depend on user-supplied official, authorized, and competitor domain lists.
- `UNRECOGNIZED` does not mean fraudulent, unauthorized, illegal, or infringing.
- Brand wording in ad copy is an observation, not a legal trademark conclusion.
- The Actor does not crawl landing pages, follow redirects, estimate ad spend, or estimate lost revenue.
- Country targeting follows the upstream Actor's country-level search controls; it is not proof of every user's experience within that country.

### Development and deployment

The child-observation safety policy is centralized in `src/config/child-observation-policy.ts`. Its initial calibration ceilings are 240 seconds and $0.50 per child observation. Before starting a child run, the Actor reads the upstream Actor's current pricing metadata. If the configured ceiling is below the upstream `minimalMaxTotalChargeUsd`, preflight fails explicitly and does not start a run or silently raise the ceiling. These are maximum safety bounds, not expected costs or a per-keyword pricing formula; revisit them after additional controlled live probes.

```bash
npm ci
npm run validate
apify push
```

A paid live probe runs only when both `APIFY_TOKEN` is available and `LIVE_TEST=1` is explicitly set. Keep it to one country, one device, one or two terms, one observation, and a maximum total charge of $0.50 where supported. Zero observed ads is a valid probe outcome.

### Post-launch product metrics

Targets: at least 10 external testers, 100 successful market reports, 3 users creating schedules, 30% or higher seven-day repeat-run rate, at least 95% deterministic classification precision, at least 90% landing-domain coverage, and zero false stopped-bidding claims. The leading behavioral metric is reuse of the same `monitorKey`.

Reconsider further investment if known-positive branded queries rarely expose ads, landing-domain coverage stays below 80%, current upstream economics prevent a healthy margin, repeat behavior remains below 15% after 100+ reports, classification errors exceed 5%, support becomes excessive, or users consistently require a standalone dashboard.

# Actor input Schema

## `monitorKey` (type: `string`):

Stable identity for this configuration across runs. Reuse it to build monitoring history.

## `brandName` (type: `string`):

Brand wording used for copy-presence signals.

## `brandTerms` (type: `array`):

One to ten exact branded queries to observe. The Actor does not invent more terms.

## `officialDomains` (type: `array`):

Domains owned by the monitored brand. URLs and subdomains are normalized to registrable domains.

## `authorizedDomains` (type: `array`):

Known affiliates, resellers, or other authorized advertisers.

## `competitorDomains` (type: `array`):

User-supplied competitors. The Actor only labels a domain as a known competitor when it is listed here.

## `countries` (type: `array`):

One to five two-letter country codes supported by the maintained Google Search Results Scraper.

## `devices` (type: `array`):

Generate an independent market report for each selected device.

## `observationsPerMarket` (type: `integer`):

Independent child runs per country-device market. Repeated evidence requires at least two.

## `languageCode` (type: `string`):

Language code passed to the upstream search-language and interface-language controls.

## `debug` (type: `boolean`):

Store bounded diagnostics, normalized records, child run IDs, and cost metadata in DEBUG.json.

## `resetState` (type: `boolean`):

Delete only the state record derived from this monitorKey before collecting new observations.

## Actor input object example

```json
{
  "monitorKey": "acme-brand-monitor",
  "brandName": "Acme",
  "brandTerms": [
    "acme",
    "acme pricing",
    "acme coupon"
  ],
  "officialDomains": [
    "acme.com"
  ],
  "authorizedDomains": [],
  "competitorDomains": [],
  "countries": [
    "US"
  ],
  "devices": [
    "desktop",
    "mobile"
  ],
  "observationsPerMarket": 2,
  "languageCode": "en",
  "debug": false,
  "resetState": false
}
```

# Actor output Schema

## `marketReports` (type: `string`):

One dataset item per brand-country-device market.

## `report` (type: `string`):

Human-readable report covering every processed market.

## `runSummary` (type: `string`):

Machine-readable OUTPUT record.

## `costTelemetry` (type: `string`):

Actual child Actor usage metadata when available; never an invented estimate.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("herazur/google-brand-bidding-affiliate-compliance-radar").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("herazur/google-brand-bidding-affiliate-compliance-radar").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 '{}' |
apify call herazur/google-brand-bidding-affiliate-compliance-radar --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,herazur/google-brand-bidding-affiliate-compliance-radar"
        }
    }
}

```

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/H9L8fuhvNttWpizA3/builds/VY7yqWymgbgydQ3aB/openapi.json
