> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mavera.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Competitor Tweet Analysis

> Pull competitor tweets from X, analyze posting strategy and content mix via Mave, and identify exploitable gaps

## 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

```mermaid theme={"dark"}
flowchart LR
    A["X GET /users/{id}/tweets"] --> B["Mavera POST /mave/chat"]
    B --> C["Strategy breakdown"]
```

## Code

<CodeGroup>
  ```python Python theme={"dark"}
  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])
  ```

  ```javascript JavaScript theme={"dark"}
  // --- 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));
  }
  ```
</CodeGroup>

## Example Output

```text theme={"dark"}
@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

<AccordionGroup>
  <Accordion title="Timeline pagination">User tweets endpoint returns up to 3,200 recent tweets. Five pages (500 tweets) is sufficient for monthly analysis.</Accordion>
  <Accordion title="Protected accounts">Protected accounts return empty timelines. Verify public status before building pipelines.</Accordion>
  <Accordion title="Read budget">Each competitor analysis uses \~500 reads. Two competitors weekly = 4,000/month on Basic (10K cap).</Accordion>
</AccordionGroup>

***

<CardGroup cols={2}>
  <Card title="X / Twitter Integration" icon="arrow-left" href="/integrations/x-twitter" />

  <Card title="Mave Agent" icon="brain" href="/features/mave-agent" />
</CardGroup>
