Scenario
Social listening tools show a sentiment score but not why people feel positive or negative. This job searches recent brand mentions, feeds tweets into Mavera Chat with an analyst persona, and produces structured sentiment classification by topic. Run daily to catch emerging issues.Architecture
Code
import os, requests, time
X = os.environ["X_BEARER_TOKEN"]; MV = os.environ["MAVERA_API_KEY"]
X_BASE = "https://api.x.com/2"; MV_BASE = "https://app.mavera.io/api/v1"
X_H = {"Authorization": f"Bearer {X}"}
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
QUERY = '"Your Brand" OR @yourbrand -is:retweet'
tweets, next_token = [], None
# 1. Paginated search
for _ in range(3):
params = {"query": QUERY, "max_results": 100,
"tweet.fields": "created_at,public_metrics,author_id",
"expansions": "author_id", "user.fields": "name,username,public_metrics"}
if next_token: params["next_token"] = next_token
r = requests.get(f"{X_BASE}/tweets/search/recent", headers=X_H, params=params)
if r.status_code == 429:
time.sleep(int(r.headers.get("x-rate-limit-reset", time.time()+60)) - int(time.time()))
r = requests.get(f"{X_BASE}/tweets/search/recent", headers=X_H, params=params)
r.raise_for_status(); data = r.json()
users = {u["id"]: u for u in data.get("includes",{}).get("users",[])}
for t in data.get("data",[]):
a = users.get(t.get("author_id"),{})
m = t.get("public_metrics",{})
tweets.append({"text": t["text"], "username": a.get("username",""),
"followers": a.get("public_metrics",{}).get("followers_count",0),
"likes": m.get("like_count",0), "retweets": m.get("retweet_count",0)})
next_token = data.get("meta",{}).get("next_token")
if not next_token: break
time.sleep(1)
print(f"Collected {len(tweets)} brand mentions")
# 2. Mavera Chat analysis
block = "\n\n".join(f"@{t['username']} ({t['followers']:,} fol) | {t['likes']}♥ {t['retweets']}🔁\n{t['text']}"
for t in sorted(tweets, key=lambda x: -(x["likes"]+x["retweets"]))[:50])
analysis = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
"message": f"Brand sentiment analyst. Classify these {len(tweets)} tweets.\n\nTWEETS:\n{block}\n\n"
"Produce:\n## Sentiment Distribution (count, %, themes)\n## Topic Clusters (ranked, with representative tweets)\n"
"## High-Impact Mentions (followers >10K or engagement >50)\n## Emerging Issues (3+ negative mentions)\n## Recommended Actions"
}).json()
print(analysis.get("content","")[:2000])
const X = process.env.X_BEARER_TOKEN, MV = process.env.MAVERA_API_KEY;
const X_BASE = "https://api.x.com/2", MV_BASE = "https://app.mavera.io/api/v1";
const X_H = { Authorization: `Bearer ${X}` };
const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const QUERY = '"Your Brand" OR @yourbrand -is:retweet';
const tweets = []; let nextToken = null;
for (let i = 0; i < 3; i++) {
const params = new URLSearchParams({ query: QUERY, max_results: "100",
"tweet.fields": "created_at,public_metrics,author_id", expansions: "author_id",
"user.fields": "name,username,public_metrics" });
if (nextToken) params.set("next_token", nextToken);
let r = await fetch(`${X_BASE}/tweets/search/recent?${params}`, { headers: X_H });
if (r.status === 429) { await new Promise(res => setTimeout(res, 60000));
r = await fetch(`${X_BASE}/tweets/search/recent?${params}`, { headers: X_H }); }
if (!r.ok) throw new Error(`X API ${r.status}`);
const data = await r.json();
const users = Object.fromEntries((data.includes?.users||[]).map(u => [u.id, u]));
for (const t of data.data || []) {
const a = users[t.author_id]||{}, m = t.public_metrics||{};
tweets.push({ text: t.text, username: a.username||"", followers: a.public_metrics?.followers_count||0,
likes: m.like_count||0, retweets: m.retweet_count||0 });
}
nextToken = data.meta?.next_token; if (!nextToken) break;
await new Promise(r => setTimeout(r, 1000));
}
const block = tweets.sort((a,b) => (b.likes+b.retweets)-(a.likes+a.retweets)).slice(0,50)
.map(t => `@${t.username} (${t.followers.toLocaleString()}) | ${t.likes}♥ ${t.retweets}🔁\n${t.text}`).join("\n\n");
const analysis = await fetch(`${MV_BASE}/mave/chat`, { method: "POST", headers: MV_H,
body: JSON.stringify({ message: `Classify ${tweets.length} tweets.\n\n${block}\n\nProduce: Sentiment, Clusters, High-Impact, Issues, Actions.` }),
}).then(r => r.json());
console.log((analysis.content||"").slice(0,2000));
Example Output
Collected 247 brand mentions
## Sentiment: Positive 112 (45%), Negative 68 (28%), Neutral 67 (27%)
## Top Clusters
1. Feature Launch — 54 tweets, 78% positive: "shipped the dashboard I begged for"
2. Pricing — 38 tweets, 71% negative: "$49/seat for 30 people is insane"
## High-Impact: @techreviewer (142K) positive → request testimonial
@startupfounder (89K) negative → DM for feedback, escalate
## Emerging: Mobile crash (7 mentions, HIGH) — iOS dashboard since v4.2.1
Error Handling
Basic tier read limits
Basic tier read limits
10,000 reads/month. Three pages (300 tweets) × 30 daily runs = 9,000 reads. Monitor usage in Developer Portal.
Time window
Time window
Recent search covers last 7 days. For historical analysis, use full-archive search (Pro, $5K/mo).
Rate limit headers
Rate limit headers
X returns
x-rate-limit-remaining and x-rate-limit-reset (Unix timestamp). Code handles 429s by sleeping.