DocumentationMonitor mentions
How to monitor mentions of your brand and reply
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.
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 }'{
"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:
| Platform | kind values |
|---|---|
| X | mention, reply, quote, like, repost, follow, post, security |
| Bluesky | The same as X, plus starter_pack |
| Threads | mention, reply, quote, like, repost, follow. url is always null. |
mention, comment, reply, tag, like, comment_like, follow, follow_request, share | |
mention, comment, reply, tag, reaction, share and others | |
| TikTok, YouTube, Pinterest | Include mention, comment and reply |
| Mastodon | The type the server marks, such as favourite or reblog |
| LinkedIn, Reddit | Always null. Match on text instead. |
2. Search for mentions without a tag
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.
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
// 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
# 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.
# Every 15 minutes, with the key in the environment of the cron job.
*/15 * * * * cd /opt/watch && node watch.mjs >> mentions.log 2>&1Reads 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.
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.
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
- "
limitmust be a whole number from 1 to 100" - 422 invalid_input
- "
kindis posts or people, nottweets" - 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
- The read_notifications action
- The search action
- How to read and reply to comments on your posts
- How to read and send direct messages with an API
- How to retry API calls without posting twice
To run this on your own accounts, create a BuzzTake account, then add an account and an API key.