Build a brand monitoring agent

Superhighway guides

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:

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

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.