Skip to content
BuzzTake
MenuClose
DocumentationMonitor mentions

How to monitor mentions of your brand and reply

Last updated

Watch an account for posts that mention your brand, and reply to the ones that need an answer. BuzzTake reads the account's notifications and runs the platform's own search; your script runs both on a schedule and keeps what is new.

You need a BuzzTake API key, the id of an active account, and a place to run a scheduled job.

1. Read the account's notifications

POST/v1/accounts/{id}/actions

read_notifications reads the latest notifications as the account sees them. limit is 20 by default and at most 100.

Request
curl https://api.buzztake.com/v1/accounts/$ACCOUNT_ID/actions \
  -H "Authorization: Bearer $BUZZTAKE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "read_notifications", "limit": 50, "wait": 60 }'
200 OK (trimmed)
{
  "type": "read_notifications",
  "status": "succeeded",
  "result": {
    "type": "notifications",
    "items": [
      {
        "kind": "mention",
        "text": "Jamie mentioned you: @acme_demo is dark mode coming to the app?",
        "url": "https://x.com/jamie_codes/status/1851...",
        "actor": "jamie_codes",
        "at": "2026-10-30T08:12:00Z"
      }
    ]
  },
  "credits": 4
}

Each item has kind, text, url, actor and at. A field the page does not show is null. BuzzTake names the kind from the notification's words, so the values differ by platform:

Platformkind values
Xmention, reply, quote, like, repost, follow, post, security
BlueskyThe same as X, plus starter_pack
Threadsmention, reply, quote, like, repost, follow. url is always null.
Instagrammention, comment, reply, tag, like, comment_like, follow, follow_request, share
Facebookmention, comment, reply, tag, reaction, share and others
TikTok, YouTube, PinterestInclude mention, comment and reply
MastodonThe type the server marks, such as favourite or reblog
LinkedIn, RedditAlways null. Match on text instead.

Notifications list the posts that tag the account. To find posts that name the brand without a tag, run search. query is the text you would type into the platform's search box. kind is posts, the default, or people. On X, post results come from the Latest tab.

Request
curl https://api.buzztake.com/v1/accounts/$ACCOUNT_ID/actions \
  -H "Authorization: Bearer $BUZZTAKE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "search", "query": "acme dark mode", "kind": "posts", "limit": 50, "wait": 60 }'

The result is posts, with url, id, author_handle, text and published_at for each post.

3. Keep only what is new

Each read starts again from the top of the list; there is no cursor and no "since" filter. So your script remembers what it has seen. The URL is a good key; when a notification has no URL, combine actor, at and text. The scripts keep the kinds that are mentions; on LinkedIn and Reddit, where kind is null, match on text instead.

JavaScript

JavaScript
// watch.mjs: run from cron. Prints mentions not seen before and remembers them.
import { existsSync, readFileSync, writeFileSync } from "node:fs";

const api = "https://api.buzztake.com/v1";
const accountId = process.env.ACCOUNT_ID;
const headers = {
  Authorization: `Bearer ${process.env.BUZZTAKE_API_KEY}`,
  "Content-Type": "application/json",
};
const SEEN_FILE = "seen.json";
const seen = new Set(existsSync(SEEN_FILE) ? JSON.parse(readFileSync(SEEN_FILE, "utf8")) : []);

async function read(body) {
  const res = await fetch(`${api}/accounts/${accountId}/actions`, {
    method: "POST",
    headers,
    body: JSON.stringify({ ...body, wait: 90 }),
  });
  if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
  const action = await res.json();
  if (action.status !== "succeeded") throw new Error(`${body.type}: ${action.status}`);
  return action.result.items;
}

const notifications = await read({ type: "read_notifications", limit: 50 });
const posts = await read({ type: "search", query: "acme", kind: "posts", limit: 50 });

const mentions = [
  ...notifications
    .filter((n) => ["mention", "reply", "quote", "comment", "tag"].includes(n.kind))
    .map((n) => ({ key: n.url ?? `${n.actor}|${n.at}|${n.text}`, url: n.url, text: n.text })),
  ...posts
    .filter((p) => p.author_handle !== "acme_demo")
    .map((p) => ({ key: p.url ?? p.id, url: p.url, text: p.text })),
];

for (const mention of mentions) {
  if (!mention.key || seen.has(mention.key)) continue;
  seen.add(mention.key);
  console.log(mention.url ?? "(no url)", mention.text);
}
writeFileSync(SEEN_FILE, JSON.stringify([...seen]));

Python

Python
# watch.py: run from cron. Prints mentions not seen before and remembers them.
import json
import os
import requests

api = "https://api.buzztake.com/v1"
account_id = os.environ["ACCOUNT_ID"]
headers = {"Authorization": f"Bearer {os.environ['BUZZTAKE_API_KEY']}"}
SEEN_FILE = "seen.json"
seen = set(json.load(open(SEEN_FILE))) if os.path.exists(SEEN_FILE) else set()


def read(body):
    resp = requests.post(
        f"{api}/accounts/{account_id}/actions",
        headers=headers,
        json={**body, "wait": 90},
        timeout=100,
    )
    if not resp.ok:
        raise SystemExit(f"{resp.status_code}: {resp.text}")
    action = resp.json()
    if action["status"] != "succeeded":
        raise SystemExit(f"{body['type']}: {action['status']}")
    return action["result"]["items"]


notifications = read({"type": "read_notifications", "limit": 50})
posts = read({"type": "search", "query": "acme", "kind": "posts", "limit": 50})

mentions = [
    {"key": n["url"] or f"{n['actor']}|{n['at']}|{n['text']}", "url": n["url"], "text": n["text"]}
    for n in notifications
    if n.get("kind") in ("mention", "reply", "quote", "comment", "tag")
] + [
    {"key": p["url"] or p["id"], "url": p["url"], "text": p["text"]}
    for p in posts
    if p.get("author_handle") != "acme_demo"
]

for mention in mentions:
    if not mention["key"] or mention["key"] in seen:
        continue
    seen.add(mention["key"])
    print(mention["url"] or "(no url)", mention["text"])

with open(SEEN_FILE, "w") as f:
    json.dump(sorted(seen), f)

4. Run it on a schedule

BuzzTake does not push new mentions to you: no webhook fires for them. Run the script from cron, a scheduled CI job or your agent's scheduler.

crontab
# Every 15 minutes, with the key in the environment of the cron job.
*/15 * * * * cd /opt/watch && node watch.mjs >> mentions.log 2>&1

Reads are charged only when they succeed: 4 credits for read_notifications and 4 for search, so one run of the script costs 8 credits. Every 15 minutes that is 768 credits a day; every hour, 192.

Note: Frequent reads can make a platform limit the account. Then the read fails with rate_limited, and nothing is charged. Run less often if you see it.

5. Reply to a mention

Reply with the mention's URL as target. Use an Idempotency-Key made from that URL or id, so a second run cannot post a second reply. How to read and reply to commentscovers which platforms let a reply answer one comment.

Reply to a mention
curl https://api.buzztake.com/v1/accounts/$ACCOUNT_ID/actions \
  -H "Authorization: Bearer $BUZZTAKE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: mention-1851..." \
  -d '{
    "type": "reply",
    "target": "https://x.com/jamie_codes/status/1851...",
    "text": "It is in the app today: Settings, Appearance.",
    "wait": 60
  }'

Common errors

422 invalid_input
"limit must be a whole number from 1 to 100"
422 invalid_input
"kind is posts or people, not tweets"
422 unsupported
Telegram has no notifications. The answer lists what the account can do.
Action error signed_out
The session ended. Seehow to check and fix a signed-out account.

See also

To run this on your own accounts, create a BuzzTake account, then add an account and an API key.