Scenario
You see competitor tweets in your feed but never systematically analyze strategy. This job pulls recent tweets, feeds them into Mavera Chat, and produces a breakdown: cadence, content mix, top formats, and exploitable gaps.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"}
COMPETITORS = ["competitor_a", "competitor_b"]
# 1. Resolve usernames
lookup = requests.get(f"{X_BASE}/users/by", headers=X_H,
params={"usernames": ",".join(COMPETITORS), "user.fields": "public_metrics,description"})
if lookup.status_code == 429:
time.sleep(int(lookup.headers.get("x-rate-limit-reset", time.time()+60)) - int(time.time()))
lookup = requests.get(f"{X_BASE}/users/by", headers=X_H,
params={"usernames": ",".join(COMPETITORS), "user.fields": "public_metrics,description"})
lookup.raise_for_status()
users = lookup.json().get("data", [])
# 2. Fetch tweets per competitor
for user in users:
tweets, nt = [], None
for _ in range(5):
params = {"max_results": 100, "tweet.fields": "created_at,public_metrics,referenced_tweets", "exclude": "retweets"}
if nt: params["pagination_token"] = nt
r = requests.get(f"{X_BASE}/users/{user['id']}/tweets", 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}/users/{user['id']}/tweets", headers=X_H, params=params)
r.raise_for_status(); data = r.json()
for t in data.get("data", []):
m = t.get("public_metrics", {})
tweets.append({"text": t["text"], "created_at": t.get("created_at",""),
"likes": m.get("like_count",0), "retweets": m.get("retweet_count",0), "replies": m.get("reply_count",0)})
nt = data.get("meta",{}).get("next_token")
if not nt: break
time.sleep(1)
# 3. Analyze
block = "\n\n".join(f"[{t['created_at'][:10]}] {t['likes']}♥ {t['retweets']}🔁 {t['replies']}💬\n{t['text'][:280]}"
for t in sorted(tweets, key=lambda x: -(x["likes"]+x["retweets"]))[:40])
fol = user.get("public_metrics",{}).get("followers_count",0)
analysis = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
"message": f"Analyze @{user['username']}'s X strategy.\n\nACCOUNT: {fol:,} followers | Bio: {user.get('description','')}\nTWEETS ({len(tweets)} analyzed):\n{block}\n\n"
"Produce:\n## Posting Strategy (cadence, times, content mix %)\n## Top-Performing Content (format, hooks)\n"
"## Audience Interaction (reply rate, engagement rate)\n## Weaknesses & Gaps\n## Key Takeaways (3 strengths, 3 to exploit)"
}).json()
print(f"\n{'='*60}\n@{user['username']} ({fol:,} followers) — {len(tweets)} tweets\n{'='*60}")
print(analysis.get("content","")[:1500])
// --- Same X_BASE, MV_BASE, X_H, MV_H setup as Job 1 ---
const COMPETITORS = ["competitor_a", "competitor_b"];
const lookup = await fetch(`${X_BASE}/users/by?usernames=${COMPETITORS.join(",")}&user.fields=public_metrics,description`, { headers: X_H });
if (!lookup.ok) throw new Error(`X API ${lookup.status}`);
for (const user of (await lookup.json()).data || []) {
const tweets = []; let nt = null;
for (let i = 0; i < 5; i++) {
const params = new URLSearchParams({ max_results: "100",
"tweet.fields": "created_at,public_metrics", exclude: "retweets" });
if (nt) params.set("pagination_token", nt);
let r = await fetch(`${X_BASE}/users/${user.id}/tweets?${params}`, { headers: X_H });
if (r.status === 429) { await new Promise(res => setTimeout(res, 60000));
r = await fetch(`${X_BASE}/users/${user.id}/tweets?${params}`, { headers: X_H }); }
if (!r.ok) break;
const data = await r.json();
for (const t of data.data||[]) { const m = t.public_metrics||{};
tweets.push({ text: t.text, created_at: t.created_at||"",
likes: m.like_count||0, retweets: m.retweet_count||0, replies: m.reply_count||0 }); }
nt = data.meta?.next_token; if (!nt) break;
await new Promise(r => setTimeout(r, 1000));
}
const block = tweets.sort((a,b) => (b.likes+b.retweets)-(a.likes+a.retweets)).slice(0,40)
.map(t => `[${(t.created_at||"").slice(0,10)}] ${t.likes}♥ ${t.retweets}🔁\n${t.text.slice(0,280)}`).join("\n\n");
const fol = user.public_metrics?.followers_count||0;
const analysis = await fetch(`${MV_BASE}/mave/chat`, { method: "POST", headers: MV_H,
body: JSON.stringify({ message: `Analyze @${user.username} (${fol} fol).\n${tweets.length} tweets:\n${block}\n\nProduce: Strategy, Top Content, Interaction, Weaknesses, Takeaways.` }),
}).then(r => r.json());
console.log(`\n@${user.username} (${fol.toLocaleString()}) — ${tweets.length} tweets`);
console.log((analysis.content||"").slice(0,1500));
}
Example Output
@competitor_a (45,200 followers) — 342 tweets
Strategy: 2.3/day weekdays, silent weekends. 9-10am + 1-2pm EST.
Mix: 40% educational, 30% promo, 20% engagement, 10% personal.
Top Content: Threads outperform singles 4.2× (340 vs 81 likes).
Best hook: "Most [role]s get [topic] wrong. Here's why:"
Gaps: No video. Never discuss pricing/ROI. Broadcast-only, no community.
Exploit: Video gap, no customer amplification, weekend silence.
Error Handling
Timeline pagination
Timeline pagination
User tweets endpoint returns up to 3,200 recent tweets. Five pages (500 tweets) is sufficient for monthly analysis.
Protected accounts
Protected accounts
Protected accounts return empty timelines. Verify public status before building pipelines.
Read budget
Read budget
Each competitor analysis uses ~500 reads. Two competitors weekly = 4,000/month on Basic (10K cap).