Competitors Bidding on My Brand Name? Track Them
Competitors bidding on my brand name? Schedule brand SERP captures per country, parse advertiser domains, save screenshot evidence and alert on new bidders.
If you suspect competitors bidding on my brand name is happening in some markets, you can't settle it from your own browser. Search ads are auctioned per query, per location and per moment, so the rival you saw once in Frankfurt may never show up when you search from your office in Leeds. What you need is a small, scheduled job: search your brand terms from each market, read the advertiser domains out of the ad block, keep screenshot evidence when a new one appears, and get pinged about it.
This guide builds that job with SparkProxy's Scraping API and proxy gateway, including the part most tutorials skip: why the screenshot for a complaint should come from a Sticky Session, not from the same rotating setup that found the ad.
What Google acts on, and what that means for capture
Read Google's own trademark policy before writing any code, because it decides which captures are worth keeping. The Google Ads trademarks page (support.google.com/adspolicy/answer/6118, read 30 October 2026) lists "Using trademarks as keywords" under what Google will not restrict. What it reviews is the ad itself: "The trademark must be used in the ad, not only on the ad's landing page."
Two more lines from that page shape the data model. Complaints are accepted only "within the countries and industries in which trademark owners have demonstrated trademark rights," so every sighting needs a market attached. And when Google does restrict, the restriction generally covers "any ads that use the same second-level domain in their final URL." The domain the ad points to is the unit of enforcement, so it's the unit you store.
The policy also carves out resellers, sellers of compatible parts, informational sites and descriptive use. A retailer that stocks your product and says so is a different case from a rival quoting your name in its headline.
| What you observe | Google complaint route? | What to capture |
|---|---|---|
| Rival's ad shows on your brand query, ad text doesn't mention your mark | No, keyword use isn't restricted | Sighting log only; useful for bidding strategy and negotiation |
| Rival's ad uses your mark in the headline or description | Yes, in markets where you hold rights | Screenshot, ad text, final URL domain, market, timestamp |
| Affiliate or partner bidding against your program terms | Usually not Google's business; it's a contract issue | Same evidence package, sent to the partner or network |
| Authorised reseller using the mark as policy allows | Usually no | Allowlist entry, so it stops alerting |
Microsoft Advertising runs its own intellectual property policy and complaint form, with rules that have differed by market over the years. Check its current policy page for the markets you care about rather than assuming Google's rules carry over.
The pipeline at a glance
The job has two speeds. A cheap, frequent discovery sweep asks "who is in the ad block right now?" across every term and market. A rare, careful evidence capture runs only when the sweep finds a domain you haven't approved.
- A watchlist of brand terms, markets, optional cities and devices.
- A scheduler fires sweeps several times a day, at local times per market.
- Each sweep fetches the results page through the Scraping API from the right country.
- A parser pulls advertiser domains and ad text from the ad block, and tells "no ads" apart from "parser broke" and "blocked".
- New domains are checked against an allowlist and debounced.
- Confirmed new bidders trigger an evidence capture in a real browser on a Sticky Session: HTML, full-page screenshot, hashes, exit IP before and after.
- An alert goes out with the evidence path and a flag saying whether your mark appears in the ad text.
The broader ad-intelligence picture, such as reading rival creatives in public ad libraries and snapshotting their landing pages by market, is covered in our guide to proxies for competitor ad research. This post stays on one question: who is buying your name, where.
Scraping at scale? Skip the blocks.
Fast, unblockable datacentre proxies with unlimited bandwidth.
Building the watchlist: terms, markets, cities
Bidders rarely stop at the bare brand. Track the variants people actually type when they're ready to buy or log in:
- the brand alone, plus common misspellings and spacing variants
- brand + "login", "pricing", "coupon", "discount code", "reviews"
- brand + "alternative" and brand + "vs" (where rivals bid hardest)
- product names that are trademarks in their own right
Keep the list in a file, one row per term and market, so a marketer can edit it without touching code:
{
"brand_regex": "spark\\s?proxy",
"markets": [
{"cc": "US", "gl": "us", "hl": "en", "cities": []},
{"cc": "DE", "gl": "de", "hl": "de", "cities": []},
{"cc": "GB", "gl": "uk", "hl": "en", "cities": []}
],
"terms": ["sparkproxy", "sparkproxy pricing", "sparkproxy coupon", "sparkproxy alternative"],
"devices": ["desktop", "mobile"]
}
City-level checks matter when you suspect a local bidder, such as a reseller targeting one metro. Google's location signal for that is the undocumented uule parameter, which rank trackers reverse-engineered and which can stop working without notice. If you add cities, add them as extra rows and verify the location Google applied on a sample; for most brand monitoring, country level is the right grain.
Keep gl and hl aligned with the exit country. A German exit with gl=us gives Google mixed signals, and you can't tell which one it believed.
The discovery sweep through the Scraping API
The Scraping API is the simplest sweep engine because the country is a parameter. The base is https://scrape.sparkproxy.io/api/v1, the key goes in the X-API-Key header, render_js defaults to true at 5 credits and country_code adds 5. That's 10 credits per rendered, country-pinned check.
Turn on json_response=true so you get the envelope with job_id, status_code, duration_ms, credits_used and body. The body is Base64. Decode it before you look for anything in it, or every marker test will quietly return False. Don't use format=json for this: format only converts page content (html, md, mdx, json, screenshot, pdf) and never produces the envelope.
import base64
import os
import urllib.parse
import requests
API = "https://scrape.sparkproxy.io/api/v1"
def serp_url(term, gl, hl):
q = {"q": term, "gl": gl, "hl": hl, "pws": "0"}
return "https://www.google.com/search?" + urllib.parse.urlencode(q)
def sweep_one(term, market, device="desktop"):
params = {
"url": serp_url(term, market["gl"], market["hl"]),
"country_code": market["cc"],
"device": device,
"json_response": "true",
"tag": f"brandbid:{market['cc']}:{device}"[:128],
}
r = requests.get(API, params=params, timeout=200,
headers={"X-API-Key": os.environ["SPARKPROXY_API_KEY"]})
r.raise_for_status()
env = r.json()
html = base64.b64decode(env["body"]).decode("utf-8", errors="replace")
return env["status_code"], env["credits_used"], html
Two settings to leave alone. block_ads defaults to false and should stay that way, since it blocks ad and tracking scripts. And never point this job at the Ad-Free pools (proxy_type=ad_free on the API, or the Ad-Free gateway pools): SparkProxy describes them as having built-in ad filtering, which is the opposite of what an ad monitor needs. If Google starts challenging the standard pool, proxy_type=premium moves the request to the premium pool; the docs list it at 10 credits without rendering or 25 with it.
Parsing advertiser domains out of the ad block
Google doesn't document its results-page markup, so any parser here is a maintained heuristic. The visible label is the one anchor point Google does describe. Its October 2025 announcement says text ads are grouped under a "Sponsored results" label (Shopping ads get "Sponsored products"), with no more than four text ads in a grouping. Its older help page on how results differ from ads still says ads "appear under an 'Ads' label." Match all three.
For the advertiser itself, the pattern rank-tracking tools have relied on for years is that ad links route through an aclk click redirect, often with the destination in an adurl query parameter. That's an observation, not a Google contract, so check it against your own captures during the first week.
import re
from html.parser import HTMLParser
from urllib.parse import parse_qs, urlparse
AD_LABELS = ("Sponsored results", "Sponsored", ">Ads<")
BLOCK_MARKERS = ("unusual traffic from your computer network", "/sorry/index")
TWO_PART = {"co.uk", "org.uk", "com.au", "co.nz", "co.jp", "com.br", "com.mx", "co.in"}
def registrable(host):
parts = host.lower().split(":")[0].split(".")
keep = 3 if ".".join(parts[-2:]) in TWO_PART else 2
return ".".join(parts[-keep:]) if len(parts) >= keep else host
class AdLinks(HTMLParser):
def __init__(self):
super().__init__()
self.ads, self._cur = [], None
def handle_starttag(self, tag, attrs):
if tag != "a":
return
a = dict(attrs)
values = " ".join(v for v in a.values() if v)
if "aclk" not in values:
return
dest = None
for v in a.values():
if v and "adurl=" in v:
dest = parse_qs(urlparse(v).query).get("adurl", [None])[0]
href = a.get("href") or ""
if not dest and href.startswith("http") and "google." not in href:
dest = href
self._cur = {"dest": dest, "text": []}
def handle_data(self, data):
if self._cur is not None:
self._cur["text"].append(data.strip())
def handle_endtag(self, tag):
if tag == "a" and self._cur is not None:
if self._cur["dest"]:
self.ads.append(self._cur)
self._cur = None
def parse_serp(html, brand_regex):
if any(m in html for m in BLOCK_MARKERS):
return "blocked", []
p = AdLinks()
p.feed(html)
labelled = any(lbl in html for lbl in AD_LABELS)
if labelled and not p.ads:
return "parser_miss", []
rows, seen = [], set()
for pos, ad in enumerate(p.ads, start=1):
domain = registrable(urlparse(ad["dest"]).netloc)
if domain in seen:
continue
seen.add(domain)
text = " ".join(t for t in ad["text"] if t)
rows.append({"position": pos, "domain": domain, "ad_text": text,
"mark_in_text": bool(re.search(brand_regex, text, re.I))})
return ("ads" if rows else "no_ads"), rows
The four statuses are the point of this function. blocked means you measured nothing. parser_miss means the page has an ad label but the parser found no ad links, which almost always means Google changed its markup; treat it as an operator alert, never as "no bidders". Only no_ads is a real negative.
The registrable() helper reduces a host to the second-level domain Google's restriction is scoped to. Its hard-coded suffix set covers the UK market in the watchlist (co.uk) and a few others; for more markets, swap in a public-suffix-aware library before trusting the domain column.
The anchor text gives you a rough ad text for the mark_in_text flag. For anything you'll send to Google, the screenshot is the authority, not the parse.
One hard rule, stated once: never request the aclk URL. That's an ad click, it costs the advertiser money, and doing it from a script is click fraud regardless of motive. Get the landing page from the destination domain directly.
Random Rotation for sweeps, Sticky Sessions for evidence
The two speeds of the pipeline want opposite proxy behaviour, and mixing them up is the commonest way to end up with evidence that doesn't match your logs.
Sweeps want Random Rotation. Every search is a fresh auction. A new exit per request gives you independent samples of that auction across the day, and it means one challenged IP costs you one data point instead of a whole run. The Scraping API already rotates by default; on the gateway, Random Rotation hands out a fresh IP per request.
Evidence wants a Sticky Session. A complaint package should tell one consistent story: this exit IP, in this country, at this time, loaded this page, and the ad was there. With rotation, the HTML you parsed and the screenshot you saved can come from two different IPs and two different auctions. The ad may simply not be in the second one. A Sticky Session holds one exit for 5 minutes, long enough to record the exit IP, load the results page, save HTML and screenshot from the same load, reload once to confirm, and check the exit IP again.
This is also why format=screenshot on the Scraping API is the wrong tool for the evidence step. The docs say of session_id: "Each request gets a fresh browser profile regardless of the session_id value." A screenshot request is therefore a separate fetch and a separate auction from the sweep that found the ad. It's fine for a quick visual check, but it can't prove the parsed page and the picture are the same page.
| Step | Proxy mode | Why |
|---|---|---|
| Discovery sweep | Scraping API default rotation, or gateway Random Rotation | Independent auction samples, failures stay isolated |
| Evidence capture | Gateway Sticky Sessions, browser you control | HTML, screenshot and reload share one exit IP |
| Landing page snapshot | Same Sticky Session, direct to destination domain | Shows what a visitor from that market was sent to, without a click |
| Country | Same as the sweep's `country_code` | Evidence must come from the market you're complaining about |
| Concurrency | 2 to 4 per market for sweeps, 1 for evidence | Google challenges bursts; evidence is rare anyway |
Our explainer on what a sticky session proxy is covers the mechanics if you haven't used one.
Capturing screenshot evidence that holds up
The gateway is gateway.sparkproxy.io. Port 11000 is Random Rotation; Sticky Sessions run on their own port, 11002 on current accounts, but copy the exact port and username from your dashboard. The homepage example shows the country carried in the username (user-country-us). Auth is user:pass or IP whitelist.
This Playwright capture does the whole package inside one Sticky Session and refuses to keep it if the exit IP changed partway:
import hashlib
import json
import os
from datetime import datetime, timezone
from pathlib import Path
from playwright.sync_api import sync_playwright
PROXY = {
"server": "http://gateway.sparkproxy.io:11002", # Sticky Sessions port from your dashboard
"username": os.environ["SPARK_STICKY_USER"], # country-tagged username, e.g. for DE
"password": os.environ["SPARK_PASS"],
}
def sha256(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def exit_ip(page):
page.goto("https://api.ipify.org?format=json", timeout=30000)
return json.loads(page.inner_text("body"))["ip"]
def capture_evidence(serp, market, term, out_dir, locale="de-DE", tz="Europe/Berlin"):
out = Path(out_dir)
out.mkdir(parents=True, exist_ok=True)
with sync_playwright() as p:
browser = p.chromium.launch(proxy=PROXY)
ctx = browser.new_context(locale=locale, timezone_id=tz,
viewport={"width": 1366, "height": 900})
page = ctx.new_page()
ip_before = exit_ip(page)
started = datetime.now(timezone.utc).isoformat(timespec="seconds")
page.goto(serp, wait_until="domcontentloaded", timeout=60000)
page.wait_for_timeout(2500)
html = page.content().encode("utf-8")
png = page.screenshot(full_page=True)
page.reload(wait_until="domcontentloaded")
page.wait_for_timeout(2500)
png_confirm = page.screenshot(full_page=True)
ip_after = exit_ip(page)
browser.close()
if ip_before != ip_after:
raise RuntimeError(f"exit IP changed {ip_before} -> {ip_after}; discard and rerun")
(out / "serp.html").write_bytes(html)
(out / "serp.png").write_bytes(png)
(out / "serp-confirm.png").write_bytes(png_confirm)
manifest = {
"term": term, "market": market, "serp_url": serp,
"captured_at_utc": started, "exit_ip": ip_before,
"sha256": {"serp.html": sha256(html), "serp.png": sha256(png),
"serp-confirm.png": sha256(png_confirm)},
}
(out / "manifest.json").write_text(json.dumps(manifest, indent=2))
return manifest
A few choices in there are deliberate:
- No overlay on the screenshot. It's tempting to stamp the time and query onto the image. Don't. An altered page is weaker evidence than a clean one with a hash and a manifest beside it.
- A fresh context per capture. No cookies, no history, so the page reflects a new visitor from that market.
- Locale and timezone match the market. A Berlin exit with a US English browser locale is a mixed signal, same as mismatched
gl. - The confirm reload. Ads rotate between loads. If the bidder appears in both screenshots, the package is stronger; if it appears in one, keep both and say so.
If Google challenges the gateway exit, the capture fails loudly at the parse step on serp.html. Rerun later in the day rather than hammering it. Archiving pages as dated, hashed records is its own discipline, and our post on proxies for web archiving and compliance capture goes further on retention and chain of custody.
Storing sightings and the allowlist
SQLite carries this comfortably; a three-market monitor writes a few thousand rows a month. Three tables do the job:
import sqlite3
DDL = """
CREATE TABLE IF NOT EXISTS sightings (
run_id TEXT, captured_at TEXT, market TEXT, device TEXT, term TEXT,
status TEXT, position INTEGER, domain TEXT, ad_text TEXT, mark_in_text INTEGER
);
CREATE TABLE IF NOT EXISTS advertisers (
domain TEXT PRIMARY KEY, first_seen TEXT,
status TEXT DEFAULT 'unknown', -- unknown | allowed | affiliate | rival
note TEXT
);
CREATE TABLE IF NOT EXISTS evidence (
domain TEXT, market TEXT, term TEXT, path TEXT,
exit_ip TEXT, png_sha256 TEXT, captured_at TEXT
);
"""
def open_db(path="brandbid.db"):
con = sqlite3.connect(path)
con.executescript(DDL)
return con
def record(con, run_id, ts, market, device, term, status, rows):
if not rows:
con.execute("INSERT INTO sightings (run_id, captured_at, market, device, term, status)"
" VALUES (?,?,?,?,?,?)", (run_id, ts, market, device, term, status))
for r in rows:
con.execute("INSERT INTO sightings VALUES (?,?,?,?,?,?,?,?,?,?)",
(run_id, ts, market, device, term, status, r["position"],
r["domain"], r["ad_text"], int(r["mark_in_text"])))
con.execute("INSERT OR IGNORE INTO advertisers (domain, first_seen) VALUES (?, ?)",
(r["domain"], ts))
con.commit()
Seed advertisers with your own domains and approved resellers as allowed before the first run, or day one is a wall of false alerts. Keep the rows with blocked and parser_miss status: a gap in coverage is a fact you'll want when someone asks why a bidder went unnoticed for a week. Store evidence folders in object storage keyed by date, market and domain, and keep the database pointing at them.
Alerting on new bidders without the noise
A single sighting is a weak signal. Ads rotate, and a domain might show once on a long-tail variant and never again. Alert when a non-allowed domain appears in at least two separate sweeps in the same market within 24 hours, and escalate immediately when mark_in_text is true, since that's the complaint-eligible case.
import json
import os
import urllib.request
def new_bidders(con, market, hours=24, min_sweeps=2):
sql = """
SELECT s.domain, COUNT(DISTINCT s.run_id) AS sweeps, MAX(s.mark_in_text) AS mark
FROM sightings s JOIN advertisers a ON a.domain = s.domain
WHERE s.market = ? AND s.status = 'ads' AND a.status IN ('unknown', 'affiliate', 'rival')
AND s.captured_at >= datetime('now', ?)
GROUP BY s.domain
HAVING sweeps >= ? OR mark = 1
"""
return con.execute(sql, (market, f"-{hours} hours", min_sweeps)).fetchall()
def alert(market, domain, sweeps, mark, evidence_path):
level = "COMPLAINT-ELIGIBLE" if mark else "keyword-only"
text = (f"[{level}] {domain} bidding on brand terms in {market} "
f"({sweeps} sweeps). Evidence: {evidence_path}")
req = urllib.request.Request(
os.environ["ALERT_WEBHOOK_URL"],
data=json.dumps({"text": text}).encode("utf-8"),
headers={"Content-Type": "application/json"})
urllib.request.urlopen(req, timeout=15)
Most chat tools accept a JSON body with a text field on an incoming webhook; adjust the payload to yours. Run the evidence capture before sending the alert, so the message arrives with a folder path rather than a to-do item. Also send a separate daily alert if any market's parser_miss count is above zero, because a silent parser is worse than a noisy one.
For affiliates, route affiliate-status domains to whoever manages the program. Checking that partners follow their terms is a wider job than search ads, covered in our guide to proxies for affiliate and coupon verification.
Scheduling and failure handling
Bidders with small budgets often run out of daily spend and drop out of the auction, so a single morning sweep misses them. Spread sweeps across each market's local business day, four times is a reasonable start, and stagger markets so they don't all fire at once:
# UTC times; DE = UTC+1 in winter, US Eastern = UTC-5 in winter
0 7,10,13,16 * * * cd /opt/brandbid && python3 sweep.py --market DE >> logs/de.log 2>&1
0 13,16,19,22 * * * cd /opt/brandbid && python3 sweep.py --market US >> logs/us.log 2>&1
30 8,11,14,17 * * * cd /opt/brandbid && python3 sweep.py --market GB >> logs/gb.log 2>&1
Daylight-saving shifts move those local times by an hour twice a year. If that matters, schedule in each market's timezone with a scheduler that supports it.
Failures fall into causes a proxy can fix and causes it can't:
| Symptom | Likely cause | Proxy fixes it? | Action |
|---|---|---|---|
| Status `blocked`, page mentions unusual traffic | Google challenged the exit | Yes | Retry on a new exit after a pause; escalate to `proxy_type=premium` if it persists |
| HTTP 429 from the API | Your rate or concurrency limit | No | Back off; lower workers |
| HTTP 530 "Scrape failed" | Target didn't load | Sometimes | Retry once; the docs say failed requests are refunded |
| Status `parser_miss` | Google changed markup | No | Fix the parser; read a saved page by eye |
| Bidder in sweep, missing in evidence | Auction variance or budget exhausted | No | Rerun evidence at the next sweep time |
| Results look like the wrong country | `gl`/`hl` or locale mismatched | Partly | Align all signals with the exit country |
| Exit IP changed during evidence | Sticky window expired | Yes | Shorten the sequence; rerun |
A small retry wrapper for the sweep, which retries only what retrying can help:
import time
import requests
def sweep_with_retry(term, market, device, brand_regex, attempts=3):
status, rows = "failed", []
for n in range(attempts):
try:
_code, _credits, html = sweep_one(term, market, device)
status, rows = parse_serp(html, brand_regex)
except requests.HTTPError as e:
if e.response is not None and e.response.status_code in (401, 402, 422):
raise # key, credits or params: retrying won't help
status = "http_error"
except requests.RequestException:
status = "network_error"
if status in ("ads", "no_ads", "parser_miss"):
return status, rows
time.sleep(30 * (n + 1))
return status, rows
When we drafted this on 30 October 2026, our API endpoint returned an HTTP 521 from its edge, so we couldn't run a live sweep and aren't quoting any detection or block rate. Pilot it yourself: three terms, two markets, three days, and read every parser_miss page by hand.
Monthly cost for a three-market monitor
Scraping API prices below are in US dollars, excluding VAT/GST, read from sparkproxy.io on 30 October 2026: Starter $49/mo for 250K credits and 50 concurrent requests, Growth $99/mo for 1M and 100, Pro $249/mo for 3M and 200, Scale $599/mo for 8M and 400. New accounts get 1,000 free credits.
Illustrative workload, with assumed inputs: 3 markets, 8 terms, 2 devices, 4 sweeps a day.
| Item | Calculation | Monthly |
|---|---|---|
| Sweeps | 3 x 8 x 2 x 4 x 30 | 5,760 checks |
| Credits per check | render 5 + `country_code` 5 | 10 |
| Sweep credits | 5,760 x 10 | 57,600 |
| Allowance for retries | +20% | about 69,000 credits |
| Plan | fits in 250K | Starter, $49/mo |
Even doubling the terms and adding three more markets stays under 250K. If you later escalate a stubborn market to proxy_type=premium, rendered checks there cost 25 credits plus country_code, so budget that market separately.
Evidence captures run through the gateway, which bills differently. Proxy plans start from $75/month with unlimited bandwidth, so the unit is the plan and its thread count, not traffic consumed. A full-page screenshot weighs the same on the bill as a tiny page. Evidence runs happen only on new bidders, so the smallest plan's 100 threads is far more than this job uses; it's usually bought for other work and borrowed here. Term discounts are 5%, 10% and 15% for 3, 6 and 12 months, and there's a 24-hour free trial without a card if you want to test the Sticky Session capture first.
From alert to complaint
An alert marked COMPLAINT-ELIGIBLE should come with a folder you can hand to legal without rework:
serp.pngandserp-confirm.png, untouched, showing the ad label and your mark in the ad textserp.htmlfrom the same loadmanifest.jsonwith UTC time, market, query URL, exit IP and SHA-256 hashes- the destination second-level domain, since that's what a Google restriction is scoped to
- a list of every sighting of that domain from the database, by market and date
- the advertiser's verified name from the Google Ads Transparency Center, looked up by hand
Google routes trademark complaints through its legal troubleshooter, linked from the policy page. File only for markets where you hold registered rights, because those are the only ones Google says it will consider. For keyword-only bidders, there's no Google route; the sighting history is still useful for a direct conversation with the rival or a bidding decision of your own. Affiliate cases go to the program, with the same folder.
The wider brand-protection workload, counterfeit listings, MAP checks and lookalike domains, is in using proxies for brand protection online. If you also need to confirm your own brand ads render correctly per market, that's ad verification, covered in how brands use proxies for ad verification.
Frequently asked questions
FAQ
Search your brand terms from an exit IP in each country, with gl and hl set to match, and read the advertiser domains out of the sponsored block. Run it several times a day, because small-budget bidders drop out once their daily spend runs out.
Not by complaining about the keyword alone. Google's trademark policy lists using trademarks as keywords as something it won't restrict, but it reviews complaints about the mark appearing in ad text, in countries where you've shown trademark rights.
Use rotation for discovery and Sticky Sessions for evidence. Rotation gives independent samples of each auction, while a Sticky Session keeps one exit IP for 5 minutes so the HTML, screenshot and confirm reload all come from the same place.
No. An automated click on a search ad charges the advertiser and counts as click fraud whatever the reason. Read the destination from the ad's link parameters and visit that domain directly.
Four sweeps spread across each market's business day is a sensible start. Raise the frequency for markets where you've already seen bidders, and alert only after a domain appears in two sweeps within 24 hours.
Three markets, eight terms, two devices and four daily sweeps come to about 5,760 rendered, country-pinned checks a month. At 10 credits each plus a 20% retry allowance that's roughly 69,000 credits, inside the $49/mo Starter Scraping API plan (prices as of 30 October 2026).
Get 20% off your first month
Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.
Save up to 15% more on quarterly, half-yearly and yearly plans
Related articles
ScrapeBox Proxies: Load, Test, and Harvest Google
Set up ScrapeBox proxies that keep harvesting: the IP:PORT:USER:PASS format, loading a rotating gateway, what the Google test checks, and safe connections.
Octoparse Proxy Setup: Custom Lists and Switch Interval
Octoparse proxy setup for local runs: the IP:port list format, whitelist auth, how the switch interval meets gateway rotation, and why pagination breaks.
How to See Google Ads in Other Countries Without Clicking
How to see Google ads in other countries: Ads Preview first, then a clean browser behind a country proxy, no clicks, and a daily script that logs ad copy.
