Back to use cases

    Use case / Marketers / Python 3 / Published

    Monitor Twitter Complaints About Your Brand with Python

    Card for Monitor Twitter Complaints About Your Brand with Python

    Complaint Radar is a small Python script for support, product and marketing teams. Give it a brand name and it returns a short morning report of public Twitter complaints about it, sorted by theme and by how much attention each post got.

    The problem

    Customers often complain in public before they open a ticket. Reading every mention by hand takes time, and most mentions are not complaints at all. Teams need a quick daily view: what are people unhappy about, is it growing, and which posts are getting the most attention.

    What the app does

    • Takes a brand name, other names for it, and the brand's own support account to leave out.
    • Searches recent public X posts that pair the brand with complaint words such as "not working", "refund" or "can't log in".
    • Removes reposts, duplicate posts and the brand's own posts.
    • Sorts each post into a theme: outage, account, billing, bug, support, feature request or other.
    • Writes a Markdown report: posts per theme, volume per day, the top posts by engagement, and the loudest posts per theme.
    • Saves a JSON snapshot with every post and every API call, plus the total cost of the run.

    How it works

    1. Build the search. The script turns your input into one X search query, for example (PlayStation OR PSN) ("not working" OR refund OR outage ...) -from:AskPlayStation. The query supports X search operators such as OR, quotes, -word and from:.
    2. Search one day at a time. For each day it calls GET /twitter with sort=Latest and a start and end date. If a page is full, it asks for the next page.
    3. Optional replies. For the top complaint posts it calls GET /twitter/replies/post to show what other people answered.
    4. Sort locally. Simple keyword rules assign a theme. Engagement score is likes + 2 x replies + reposts + quotes + bookmarks.
    5. Write the report and add up the cost from the X-Desearch-Cost-Usd response header.

    The code

    The core call is one HTTP request:

        def get(self, path, params, label):
            if self.spent() >= self.max_spend:
                print(f"spend cap ${self.max_spend} reached, skipping {label}", file=sys.stderr)
                return None
            for attempt in (1, 2):
                when = time.strftime("%Y-%m-%d %H:%M:%S %z")
                t0 = time.perf_counter()
                try:
                    r = self.s.get(BASE + path, params=params, timeout=120)
                    status, hdr, text = r.status_code, r.headers, r.text
                except Exception as e:  # network error
                    status, hdr, text = None, {}, str(e)
                lat = round(time.perf_counter() - t0, 2)
                try:
                    body = json.loads(text)
                except ValueError:
                    body = {"_raw_text": text[:2000]}
                items = body if isinstance(body, list) else []
                c = {"when": when, "label": label, "path": path, "params": params, "status": status, "latency_s": lat,
                     "items": len(items), "units": int(hdr.get("X-Desearch-Usage-Count") or 0) or None,
                     "cost_usd": float(hdr.get("X-Desearch-Cost-Usd") or 0), "attempt": attempt}
                self.calls.append(c)
                print(f"[{when}] {status} {path} {json.dumps(params)} items={c['items']} units={c['units']} "
                      f"cost=${c['cost_usd']:.5f} {lat}s", file=sys.stderr)
                if self.raw_dir:
                    s = json.dumps({"call": c, "request": {"url": BASE + path, "params": params, "headers": {"Authorization": "<redacted>"}},
                                    "response": body}, ensure_ascii=False, indent=1)
                    assert self.key not in s
                    open(os.path.join(self.raw_dir, f"{len(self.calls):03d}-{label}.json"), "w").write(s)
                if status == 200:
                    return body
                if status in (429, 500, 502, 503, 504, None) and attempt == 1:
                    time.sleep(2)
                    continue
                return None
    

    The repo folder has one script, complaintradar.py (about 380 lines: query builder, day-by-day paging, themes, report writer), and offline tests in test_complaintradar.py.

    Endpoints

    • GET /twitter
    • GET /twitter/replies/post

    Example output

    From a run for PlayStation on 2026-10-08, covering 3 days (2026-10-06 to 2026-10-08 UTC). 123 posts after removing duplicates.

    ThemePostsShare
    bug5948%
    billing2722%
    outage1815%
    support43%
    account11%
    other1411%

    The full report also lists posts per day, the 10 complaint posts with the most engagement, and the loudest posts in each theme, each with a link to the post.

    Run it yourself

    The public examples folder linked above includes the script and a README. Install its requirements, set DESEARCH_API_KEY from console.desearch.ai, and use the run command in that README.

    Useful flags: --days (how many days back), --keywords (your own complaint words), --exclude (words to filter out), --max-spend (stop at a dollar limit, default $0.10).

    What it costs

    The example run above made 10 API calls (7 searches returning 127 posts and 3 reply lookups) and cost $0.028, read from the X-Desearch-Cost-Usd header on 2026-10-08. A full page of 20 posts cost $0.003. See pricing.

    Ideas to extend it

    • Run it every morning with cron and send the report to a team chat.
    • Track two or three brands side by side to compare complaint themes.
    • Replace the keyword themes with an LLM classifier for more nuance.
    • Alert when a theme jumps above its usual daily count.

    Use with AI agents

    Point an MCP client at the hosted Desearch server. Replace the placeholder with a key from the console. Do not commit the key.

    {
      "mcpServers": {
        "desearch": {
          "url": "https://mcp.desearch.ai/mcp",
          "headers": {
            "Authorization": "Bearer <DESEARCH_API_KEY>"
          }
        }
      }
    }

    Prompt you can paste for this example:

    Use Desearch to find public X posts from the last day that complain about the brand I name. Group them by theme and quote the posts you retrieve. Skip posts that are not complaints. Do not invent posts.

    Build your own example.

    Give your agents access to web and social search. Get an API key to start building, or talk to us about your integration.

    Read docs