Build a brand monitoring agent
Brand reputation is a search-and-read problem that never stops. Every day, people write about your brand in news articles, reviews, forum threads, and blog posts — and the mentions that matter most (a viral complaint, a press hit, a competitor takedown) are buried among the noise. This guide builds a Python agent that does the watching for you. Give it a brand name and a few keywords, and it chains /news (press coverage), /search (web mentions, reviews, forum discussions), and /scrape (full article content), then hands everything to an LLM that scores sentiment, classifies urgency, categorizes each mention, and recommends an action. Run it on a schedule and it becomes a daily brand digest — social listening and PR monitoring without a six-figure SaaS contract.
1. What you'll build
A Python agent that monitors mentions of a brand and produces a prioritized daily digest. It:
- Takes a brand name plus keywords to monitor (pricing, support, a product name)
- Searches news coverage (via
/news) and web mentions — reviews, forums, blogs (via/search) - Scrapes the full content of key mentions for context (via
/scrape) - Uses an LLM to analyze sentiment, classify urgency, and categorize each mention
- Outputs a daily digest of significant brand mentions, sorted by urgency
2. Setup
pip install openai requests python-dotenv
Create a .env file with your two keys:
SUPERHIGHWAY_API_KEY=your_key_here
OPENAI_API_KEY=your_key_here
3. Search for news coverage
Start with the press. /news returns recent articles mentioning the brand, each with a title, source, description, and publish date — exactly what you need to catch a story while it's still breaking.
import requests, os, json
from datetime import datetime
SUPERHIGHWAY_KEY = os.getenv("SUPERHIGHWAY_API_KEY")
BASE = "https://superhighway.walls.sh"
def get_news_mentions(brand: str, count: int = 10) -> list[dict]:
"""Get recent news articles mentioning the brand."""
r = requests.get(
f"{BASE}/news",
params={"q": brand, "count": count},
headers={"Authorization": f"Bearer {SUPERHIGHWAY_KEY}"}
)
return r.json().get("articles", [])
4. Search for web mentions (forums, blogs, reviews)
News is only part of the picture. The opinions that shape reputation live in reviews, forum threads, and blog posts. Run a few targeted /search queries — wrapping the brand in quotes for an exact match — and deduplicate by URL so the same page never gets analyzed twice.
def get_web_mentions(brand: str, keywords: list[str] | None = None) -> list[dict]:
"""Search for blog posts, forum discussions, and reviews mentioning the brand."""
extra = " ".join(keywords) if keywords else ""
queries = [
f'"{brand}" review',
f'"{brand}" {extra}'.strip(),
f'"{brand}" forum discussion',
]
seen = set()
results = []
for q in queries:
r = requests.get(
f"{BASE}/search",
params={"q": q, "limit": 5},
headers={"Authorization": f"Bearer {SUPERHIGHWAY_KEY}"}
)
for item in r.json().get("results", []):
if item["url"] not in seen:
seen.add(item["url"])
results.append(item)
return results
5. Scrape full content from key mentions
Search and news snippets are too thin to judge sentiment or urgency — a one-line description can't tell you whether a thread is a glowing review or a pile-on. /scrape returns the full page as clean, LLM-ready Markdown with nav, ads, and cookie banners stripped. Truncate it so the prompt stays small and cheap.
def scrape_mention(url: str) -> str:
"""Get full content from a mention URL."""
r = requests.get(
f"{BASE}/scrape",
params={"url": url},
headers={"Authorization": f"Bearer {SUPERHIGHWAY_KEY}"}
)
return r.json().get("markdown", "")[:2500]
6. Analyze each mention with an LLM
This is where raw mentions become triage. Pass the title, source, and scraped content to the LLM and ask for structured JSON: sentiment, urgency, category, a one-line summary, a recommended action, and the single most important quote. The response_format JSON-object mode guarantees parseable output.
from openai import OpenAI
llm = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def analyze_mention(brand: str, mention: dict, full_content: str) -> dict | None:
"""Classify a mention by sentiment, urgency, and category."""
response = llm.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": f"You analyze brand mentions for {brand}. Classify each mention and identify what action (if any) is needed."
},
{
"role": "user",
"content": f"""Analyze this mention of {brand}:
Title: {mention.get("title", "")}
URL: {mention.get("url", "")}
Source: {mention.get("source", mention.get("description", "")[:100])}
Content:
{full_content[:1500]}
Return JSON with:
- sentiment: "positive" | "neutral" | "negative"
- urgency: "high" | "medium" | "low"
- category: "press" | "review" | "social" | "forum" | "complaint" | "praise" | "other"
- summary: one-sentence summary of the mention
- recommended_action: string (e.g. "respond publicly", "flag for PR team", "no action needed")
- key_quote: the most important sentence from the content (or null)"""
}
],
response_format={"type": "json_object"}
)
try:
result = json.loads(response.choices[0].message.content)
result["url"] = mention.get("url", "")
result["title"] = mention.get("title", "")
result["source"] = mention.get("source", "")
return result
except (json.JSONDecodeError, KeyError):
return None
7. The full monitoring pipeline
Now wire the steps together. Pull news and web mentions, deduplicate them into one list, cap the count to control API spend, then scrape and analyze each. Filter by a minimum urgency, sort high-to-low, and roll everything up into a digest with a sentiment breakdown.
def monitor_brand(
brand: str,
keywords: list[str] | None = None,
min_urgency: str = "low"
) -> dict:
"""Run the full brand monitoring pipeline."""
urgency_order = {"high": 3, "medium": 2, "low": 1}
min_level = urgency_order.get(min_urgency, 1)
print(f"Monitoring mentions of: {brand}")
# Step 1: News
news = get_news_mentions(brand)
print(f" Found {len(news)} news articles")
# Step 2: Web mentions
web = get_web_mentions(brand, keywords)
print(f" Found {len(web)} web mentions")
# Combine and deduplicate by URL
seen_urls = set()
all_mentions = []
for item in news + web:
url = item.get("url", "")
if url and url not in seen_urls:
seen_urls.add(url)
all_mentions.append(item)
print(f" {len(all_mentions)} unique mentions to analyze")
# Step 3 + 4: Scrape and analyze
analyzed = []
for mention in all_mentions[:12]: # Cap to control API usage
content = scrape_mention(mention["url"])
result = analyze_mention(brand, mention, content)
if result:
level = urgency_order.get(result.get("urgency", "low"), 1)
if level >= min_level:
analyzed.append(result)
# Sort by urgency descending
analyzed.sort(key=lambda x: urgency_order.get(x.get("urgency", "low"), 0), reverse=True)
return {
"brand": brand,
"date": datetime.now().strftime("%Y-%m-%d"),
"total_mentions": len(all_mentions),
"analyzed": len(analyzed),
"high_urgency": [m for m in analyzed if m.get("urgency") == "high"],
"medium_urgency": [m for m in analyzed if m.get("urgency") == "medium"],
"low_urgency": [m for m in analyzed if m.get("urgency") == "low"],
"sentiment_breakdown": {
"positive": sum(1 for m in analyzed if m.get("sentiment") == "positive"),
"neutral": sum(1 for m in analyzed if m.get("sentiment") == "neutral"),
"negative": sum(1 for m in analyzed if m.get("sentiment") == "negative"),
}
}
8. Print a daily digest
Finally, format the report into something a human can scan in ten seconds: a sentiment line up top, then high-urgency items with their recommended actions and a key quote, followed by medium-urgency items with summaries.
def print_digest(report: dict):
"""Print a formatted daily digest."""
print(f"\n{'='*60}")
print(f"Brand Monitor Digest: {report['brand']} — {report['date']}")
print(f"{'='*60}")
print(f"Total mentions found: {report['total_mentions']} | Analyzed: {report['analyzed']}")
s = report["sentiment_breakdown"]
print(f"Sentiment: {s['positive']} positive / {s['neutral']} neutral / {s['negative']} negative")
if report["high_urgency"]:
print(f"\n🔴 HIGH URGENCY ({len(report['high_urgency'])} items):")
for m in report["high_urgency"]:
print(f" [{m.get('category', '?').upper()}] {m.get('title', m.get('url', ''))[:70]}")
print(f" Sentiment: {m.get('sentiment')} | Action: {m.get('recommended_action', '')}")
if m.get("key_quote"):
print(f" \"{m['key_quote'][:100]}\"")
if report["medium_urgency"]:
print(f"\n🟡 MEDIUM URGENCY ({len(report['medium_urgency'])} items):")
for m in report["medium_urgency"]:
print(f" [{m.get('category', '?').upper()}] {m.get('title', '')[:70]}")
print(f" {m.get('summary', '')[:100]}")
if __name__ == "__main__":
import sys
brand = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else "your brand name"
report = monitor_brand(
brand,
keywords=["pricing", "support", "feature"],
min_urgency="low"
)
print_digest(report)
9. Schedule daily brand digests
A brand monitor earns its keep on autopilot. Drop it into a GitHub Actions workflow and it sweeps the news and web every morning, so a fresh digest is waiting before your team logs on.
# .github/workflows/brand-monitor.yml
name: Daily Brand Monitor
on:
schedule:
- cron: '0 7 * * *' # Daily at 7 AM UTC
workflow_dispatch:
jobs:
monitor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- run: pip install openai requests python-dotenv
- run: python brand_monitor.py "YourBrand"
env:
SUPERHIGHWAY_API_KEY: ${{ secrets.SUPERHIGHWAY_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
10. Extending the agent
- Multi-brand monitoring — loop over a list of brands (your brand, competitors, key executives) and aggregate into a single digest for a full competitive picture.
- Slack/email alerts — add instant notification for any high-urgency mention using a webhook, so you hear about a crisis the moment it surfaces, not the next morning.
- Historical tracking — store results in a SQLite database to track sentiment trends over time and catch a reputation slide before it becomes a story.
- Keyword escalation — add industry-specific terms (
"data breach","outage","lawsuit") that auto-escalate any matching mention to high urgency. - Response drafts — for negative mentions, feed the full content back to the LLM to generate a suggested public response draft your team can edit and ship.
11. Getting your API key
Grab a free Superhighway key at /pricing (1,000 calls/month, no credit card). For an agent that provisions its own access, skip the key entirely with x402: it pays $0.002 per call in USDC on Base — no signup, no key management. See the x402 pay-per-call guide for the wallet setup.
For related builds, the news briefing agent applies the same news-and-analysis pattern to daily intelligence, and the competitor analysis agent turns the same search-scrape-synthesize chain on your rivals.