Scenario
Google Ads shows you who you compete against in every auction — impression share, overlap rate, outranking share, position above rate. But this data sits in a table nobody reads. You pull auction insights for your top campaigns, identify the biggest competitive threats, then send each competitor to Mave for deep research and battle card generation. The result is AI-generated battle cards backed by both auction data and web research.Architecture
Code
import os, requests, time
from google.ads.googleads.client import GoogleAdsClient
from collections import defaultdict
MV = os.environ["MAVERA_API_KEY"]
CUSTOMER_ID = os.environ["GOOGLE_ADS_CUSTOMER_ID"]
client = GoogleAdsClient.load_from_env()
ga_service = client.get_service("GoogleAdsService")
query = """
SELECT
auction_insights.display_domain,
metrics.auction_insight_search_impression_share,
metrics.auction_insight_search_overlap_rate,
metrics.auction_insight_search_outranking_share,
metrics.auction_insight_search_position_above_rate,
metrics.auction_insight_search_top_impression_percentage
FROM auction_insights
WHERE segments.date DURING LAST_30_DAYS
"""
response = ga_service.search(customer_id=CUSTOMER_ID, query=query)
competitors = defaultdict(lambda: {
"impression_share": [], "overlap_rate": [], "outranking_share": [],
"position_above_rate": [], "top_impression_pct": [],
})
for row in response:
domain = row.auction_insights.display_domain
m = row.metrics
competitors[domain]["impression_share"].append(m.auction_insight_search_impression_share)
competitors[domain]["overlap_rate"].append(m.auction_insight_search_overlap_rate)
competitors[domain]["outranking_share"].append(m.auction_insight_search_outranking_share)
competitors[domain]["position_above_rate"].append(m.auction_insight_search_position_above_rate)
competitors[domain]["top_impression_pct"].append(m.auction_insight_search_top_impression_percentage)
def avg(lst):
return sum(lst) / len(lst) if lst else 0
comp_summary = []
for domain, metrics in competitors.items():
if domain == "Your domain":
continue
comp_summary.append({
"domain": domain,
"impression_share": avg(metrics["impression_share"]),
"overlap_rate": avg(metrics["overlap_rate"]),
"outranking_share": avg(metrics["outranking_share"]),
"position_above_rate": avg(metrics["position_above_rate"]),
"top_impression_pct": avg(metrics["top_impression_pct"]),
})
comp_summary.sort(key=lambda c: c["overlap_rate"], reverse=True)
top_competitors = comp_summary[:5]
print(f"Top {len(top_competitors)} competitors by auction overlap:\n")
for c in top_competitors:
print(f" {c['domain']}: overlap {c['overlap_rate']:.1%}, "
f"outranking {c['outranking_share']:.1%}, "
f"pos above {c['position_above_rate']:.1%}")
for comp in top_competitors:
prompt = f"""Generate a competitive battle card for {comp['domain']}.
GOOGLE ADS AUCTION DATA (last 30 days):
- Impression share: {comp['impression_share']:.1%}
- Overlap rate with us: {comp['overlap_rate']:.1%} (how often we compete)
- They outrank us: {comp['outranking_share']:.1%} of the time
- They appear above us: {comp['position_above_rate']:.1%} of the time
- Their top impression %: {comp['top_impression_pct']:.1%}
Research this competitor and produce:
1. Company overview (funding, size, positioning)
2. Their strengths (what they do well)
3. Their weaknesses (where we win)
4. Pricing intelligence (if available)
5. Common objections when competing against them
6. Killer discovery questions for reps
7. Landmine questions to watch for
8. Why customers switch from them to us
9. Recommended ad copy adjustments when competing in the same auction"""
card = requests.post(
"https://app.mavera.io/api/v1/mave/chat",
headers={"Authorization": f"Bearer {MV}", "Content-Type": "application/json"},
json={"message": prompt},
).json()
print(f"\n{'='*60}")
print(f"BATTLE CARD: {comp['domain']}")
print(f"Overlap: {comp['overlap_rate']:.1%} | They outrank us: {comp['outranking_share']:.1%}")
print(f"{'='*60}")
print(card.get("content", "")[:1200])
print(f"Sources: {len(card.get('sources', []))}")
time.sleep(1)
const DEV_TOKEN = process.env.GOOGLE_ADS_DEVELOPER_TOKEN;
const ACCESS_TOKEN = process.env.GOOGLE_ADS_ACCESS_TOKEN;
const CUSTOMER_ID = process.env.GOOGLE_ADS_CUSTOMER_ID;
const MV = process.env.MAVERA_API_KEY;
const gaql = `
SELECT auction_insights.display_domain,
metrics.auction_insight_search_impression_share,
metrics.auction_insight_search_overlap_rate,
metrics.auction_insight_search_outranking_share,
metrics.auction_insight_search_position_above_rate,
metrics.auction_insight_search_top_impression_percentage
FROM auction_insights
WHERE segments.date DURING LAST_30_DAYS`;
const gaRes = await fetch(
`https://googleads.googleapis.com/v23/customers/${CUSTOMER_ID}/googleAds:searchStream`,
{
method: "POST",
headers: { Authorization: `Bearer ${ACCESS_TOKEN}`, "developer-token": DEV_TOKEN, "Content-Type": "application/json" },
body: JSON.stringify({ query: gaql }),
}
).then((r) => r.json());
const competitors = {};
for (const row of gaRes[0]?.results || []) {
const domain = row.auctionInsights.displayDomain;
if (!domain || domain === "Your domain") continue;
competitors[domain] ??= { impressionShare: [], overlapRate: [], outrankingShare: [], posAboveRate: [], topImprPct: [] };
const m = row.metrics;
competitors[domain].impressionShare.push(parseFloat(m.auctionInsightSearchImpressionShare || "0"));
competitors[domain].overlapRate.push(parseFloat(m.auctionInsightSearchOverlapRate || "0"));
competitors[domain].outrankingShare.push(parseFloat(m.auctionInsightSearchOutrankingShare || "0"));
competitors[domain].posAboveRate.push(parseFloat(m.auctionInsightSearchPositionAboveRate || "0"));
competitors[domain].topImprPct.push(parseFloat(m.auctionInsightSearchTopImpressionPercentage || "0"));
}
const avg = (arr) => arr.length ? arr.reduce((s, v) => s + v, 0) / arr.length : 0;
const compSummary = Object.entries(competitors)
.map(([domain, m]) => ({
domain, impressionShare: avg(m.impressionShare), overlapRate: avg(m.overlapRate),
outrankingShare: avg(m.outrankingShare), posAboveRate: avg(m.posAboveRate), topImprPct: avg(m.topImprPct),
}))
.sort((a, b) => b.overlapRate - a.overlapRate)
.slice(0, 5);
for (const comp of compSummary) {
const card = await fetch("https://app.mavera.io/api/v1/mave/chat", {
method: "POST",
headers: { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" },
body: JSON.stringify({
message: `Generate a battle card for ${comp.domain}.\n\nAUCTION DATA:\n- Overlap: ${(comp.overlapRate * 100).toFixed(1)}%\n- They outrank us: ${(comp.outrankingShare * 100).toFixed(1)}%\n- Position above: ${(comp.posAboveRate * 100).toFixed(1)}%\n\nProduce: 1) Overview 2) Strengths 3) Weaknesses 4) Pricing 5) Objections 6) Killer questions 7) Landmines 8) Switch reasons 9) Ad copy adjustments`,
}),
}).then((r) => r.json());
console.log(`\n${"=".repeat(60)}`);
console.log(`BATTLE CARD: ${comp.domain} | Overlap: ${(comp.overlapRate * 100).toFixed(1)}%`);
console.log(`${"=".repeat(60)}`);
console.log((card.content || "").slice(0, 1200));
console.log(`Sources: ${(card.sources || []).length}`);
await new Promise((r) => setTimeout(r, 1000));
}
Example Output
Top 5 competitors by auction overlap:
competitorx.com: overlap 78.2%, outranking 34.1%, pos above 28.5%
rivaltool.io: overlap 65.4%, outranking 42.3%, pos above 38.1%
alternativeapp.com: overlap 51.8%, outranking 22.7%, pos above 18.3%
============================================================
BATTLE CARD: competitorx.com
Overlap: 78.2% | They outrank us: 34.1%
============================================================
### Overview
CompetitorX — Series C ($65M raised), 300 employees. Positioning as an
"all-in-one revenue platform." Strong SMB traction, expanding into mid-market.
### Strengths
- Lower entry price ($79/seat vs our $149/seat)
- Faster self-serve onboarding (claim: 5 minutes to first value)
- Strong G2 presence (4.6 stars, 1,200+ reviews)
### Weaknesses
- No SSO/SCIM below Enterprise tier
- Reporting limited to pre-built dashboards (no custom SQL/API export)
- No SOC 2 Type II — only Type I
- Customer support: community forum only below $25K ACV
### Pricing
Starter: $79/seat/mo | Pro: $149/seat/mo | Enterprise: custom
No monthly option — annual only.
### Killer Questions
- "How does your team handle SSO requirements for compliance?"
- "Do you need to export data to your existing BI tools?"
- "What does your security review process look like?"
### Ad Copy Adjustments
When competing in the same auction:
- Lead with "SOC 2 Type II Certified" in headline
- Add "Free Trial — No Annual Lock-In" to differentiate
- Use sitelink: "See Our Security Certifications"
Sources: 5
Error Handling
Auction insights require Search campaigns
Auction insights require Search campaigns
Display and Video campaigns don’t populate
auction_insights. The report only covers Search and Shopping campaigns.Domain-level aggregation
Domain-level aggregation
Auction insights report at the domain level, not per-ad. A competitor with multiple products under one domain shows as a single entry.
Mave rate limiting on batch requests
Mave rate limiting on batch requests
Generating 5 battle cards sequentially takes 30–60s. The code includes 1s delays. For 10+ competitors, parallelize with
asyncio.gather (Python) or Promise.all (JS) with a concurrency limit.All Google Ads jobs
View all 7 Google Ads integration jobs
Mave Agent
Full reference for POST /api/v1/mave/chat