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

# Comment Analysis for Messaging

> Pull comments from high-engagement LinkedIn posts, analyze prospect language and objections via Mave, and build a messaging intelligence map

## Scenario

Your high-engagement posts generate 30–100 comments each — raw prospect language sitting in plain sight. This job identifies your top-engagement posts, pulls their comment threads, then feeds the full comment corpus into Mavera Chat asking: "What questions are prospects asking? What objections surface? What language do they use to describe their problems?" The output is a messaging map built from real audience language — not internal jargon.

## Architecture

```mermaid theme={"dark"}
flowchart LR
    A["LinkedIn GET /posts (by author org)"] --> B["Identify high-engagement posts"]
    B --> C["GET /socialActions/{postUrn}/comments"]
    C --> D["Aggregate comment text"]
    D --> E["Mavera POST /mave/chat (messaging analysis)"]
    E --> F["Questions, objections & language patterns"]
```

## Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests, time

  LI = os.environ["LINKEDIN_ACCESS_TOKEN"]
  MV = os.environ["MAVERA_API_KEY"]
  LI_BASE = "https://api.linkedin.com/rest"
  MV_BASE = "https://app.mavera.io/api/v1"
  LI_H = {"Authorization": f"Bearer {LI}", "LinkedIn-Version": "202401", "X-Restli-Protocol-Version": "2.0.0"}
  MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

  ORG_URN = "urn:li:organization:12345678"

  # 1. Pull recent posts and find high-engagement ones
  r = requests.get(f"{LI_BASE}/posts",
      headers=LI_H,
      params={"q": "author", "author": ORG_URN, "count": 50, "sortBy": "LAST_MODIFIED"})
  if r.status_code == 429:
      time.sleep(int(r.headers.get("Retry-After", 60)))
      r = requests.get(f"{LI_BASE}/posts", headers=LI_H,
          params={"q": "author", "author": ORG_URN, "count": 50, "sortBy": "LAST_MODIFIED"})
  r.raise_for_status()
  posts = r.json().get("elements", [])

  # 2. Get comment counts to find top posts
  post_comments = []
  for post in posts:
      post_urn = post.get("id", "")
      commentary = post.get("commentary", "")
      if not commentary:
          continue

      sr = requests.get(f"{LI_BASE}/socialActions/{post_urn}", headers=LI_H)
      if sr.status_code == 429:
          time.sleep(int(sr.headers.get("Retry-After", 30)))
          sr = requests.get(f"{LI_BASE}/socialActions/{post_urn}", headers=LI_H)

      comment_count = 0
      if sr.ok:
          comment_count = sr.json().get("commentsSummary", {}).get("totalFirstLevelComments", 0)

      post_comments.append({
          "urn": post_urn,
          "commentary": commentary[:300],
          "comment_count": comment_count,
      })
      time.sleep(0.3)

  # 3. Pull actual comments from top posts (by comment count)
  top_posts = sorted(post_comments, key=lambda x: -x["comment_count"])[:8]
  all_comments = []

  for post in top_posts:
      if post["comment_count"] == 0:
          continue

      cr = requests.get(f"{LI_BASE}/socialActions/{post['urn']}/comments",
          headers=LI_H,
          params={"count": 100})
      if cr.status_code == 429:
          time.sleep(int(cr.headers.get("Retry-After", 30)))
          cr = requests.get(f"{LI_BASE}/socialActions/{post['urn']}/comments",
              headers=LI_H, params={"count": 100})

      if cr.ok:
          comments = cr.json().get("elements", [])
          for c in comments:
              text = c.get("message", {}).get("text", "").strip()
              if text and len(text) > 15:
                  all_comments.append({
                      "post_context": post["commentary"][:150],
                      "comment": text[:400],
                  })
      time.sleep(0.5)

  if not all_comments:
      raise SystemExit("No comments found. Check page permissions and post engagement.")

  # 4. Build comment corpus for Mave
  corpus = "\n\n---\n\n".join(
      f"[Post context: {c['post_context'][:100]}...]\nComment: {c['comment']}"
      for c in all_comments[:60]
  )

  # 5. Messaging analysis via Mave
  analysis = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
      "message": f"""Analyze these {len(all_comments)} LinkedIn comments from our Company Page posts.
  These are real prospects and followers reacting to our content.

  {corpus}

  Produce a messaging intelligence report:

  1. QUESTIONS PROSPECTS ASK: What are the top 5-7 recurring questions? Group by theme (pricing, implementation, comparison, capability). Include exact phrasing from comments.

  2. OBJECTIONS THAT SURFACE: What resistance or skepticism appears? List the top 5 objections with representative quotes and suggested counter-messaging.

  3. LANGUAGE MAP: What words and phrases do commenters use to describe their problems? List the top 10 prospect-native phrases (not our internal jargon). These should be used verbatim in ad copy and landing pages.

  4. SENTIMENT CLUSTERS: Group comments by sentiment (enthusiastic, curious, skeptical, critical). What percentage falls in each?

  5. CONTENT GAPS: What topics do commenters ask about that we haven't addressed? List 5 content pieces we should create based on comment demand.

  6. COMPETITIVE MENTIONS: Any competitors or alternatives mentioned? Context for each."""
  }).json()

  print(f"Analyzed {len(all_comments)} comments from {len(top_posts)} high-engagement posts")
  print("=" * 60)
  print(analysis.get("content", "")[:2500])
  ```

  ```javascript JavaScript theme={"dark"}
  const LI = process.env.LINKEDIN_ACCESS_TOKEN;
  const MV = process.env.MAVERA_API_KEY;
  const LI_BASE = "https://api.linkedin.com/rest";
  const MV_BASE = "https://app.mavera.io/api/v1";
  const LI_H = { Authorization: `Bearer ${LI}`, "LinkedIn-Version": "202401", "X-Restli-Protocol-Version": "2.0.0" };
  const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };

  const ORG_URN = "urn:li:organization:12345678";

  // 1. Pull recent posts
  let res = await fetch(
    `${LI_BASE}/posts?q=author&author=${encodeURIComponent(ORG_URN)}&count=50&sortBy=LAST_MODIFIED`,
    { headers: LI_H }
  );
  if (res.status === 429) {
    await new Promise(r => setTimeout(r, parseInt(res.headers.get("Retry-After") || "60", 10) * 1000));
    res = await fetch(
      `${LI_BASE}/posts?q=author&author=${encodeURIComponent(ORG_URN)}&count=50&sortBy=LAST_MODIFIED`,
      { headers: LI_H }
    );
  }
  if (!res.ok) throw new Error(`LinkedIn ${res.status}`);
  const posts = (await res.json()).elements || [];

  // 2. Find high-engagement posts by comment count
  const postComments = [];
  for (const post of posts) {
    const commentary = post.commentary || "";
    if (!commentary) continue;
    const sr = await fetch(`${LI_BASE}/socialActions/${post.id}`, { headers: LI_H });
    const commentCount = sr.ok
      ? (await sr.json()).commentsSummary?.totalFirstLevelComments || 0
      : 0;
    postComments.push({ urn: post.id, commentary: commentary.slice(0, 300), commentCount });
    await new Promise(r => setTimeout(r, 300));
  }

  // 3. Pull comments from top posts
  const topPosts = postComments.sort((a, b) => b.commentCount - a.commentCount).slice(0, 8);
  const allComments = [];

  for (const post of topPosts) {
    if (post.commentCount === 0) continue;
    const cr = await fetch(
      `${LI_BASE}/socialActions/${post.urn}/comments?count=100`,
      { headers: LI_H }
    );
    if (cr.ok) {
      const comments = (await cr.json()).elements || [];
      for (const c of comments) {
        const text = (c.message?.text || "").trim();
        if (text.length > 15) {
          allComments.push({ postContext: post.commentary.slice(0, 150), comment: text.slice(0, 400) });
        }
      }
    }
    await new Promise(r => setTimeout(r, 500));
  }

  if (!allComments.length) throw new Error("No comments found.");

  // 4. Build corpus
  const corpus = allComments.slice(0, 60).map(c =>
    `[Post context: ${c.postContext.slice(0, 100)}...]\nComment: ${c.comment}`
  ).join("\n\n---\n\n");

  // 5. Messaging analysis
  const analysis = await fetch(`${MV_BASE}/mave/chat`, {
    method: "POST", headers: MV_H,
    body: JSON.stringify({
      message: `Analyze these ${allComments.length} LinkedIn comments from our Company Page.\n\n${corpus}\n\nProduce:\n1. QUESTIONS PROSPECTS ASK (top 5-7, grouped by theme, exact phrasing)\n2. OBJECTIONS (top 5 with quotes and counter-messaging)\n3. LANGUAGE MAP (top 10 prospect-native phrases for ad copy)\n4. SENTIMENT CLUSTERS (enthusiastic/curious/skeptical/critical percentages)\n5. CONTENT GAPS (5 pieces to create based on demand)\n6. COMPETITIVE MENTIONS`,
    }),
  }).then(r => r.json());

  console.log(`Analyzed ${allComments.length} comments from ${topPosts.filter(p => p.commentCount > 0).length} posts`);
  console.log("=".repeat(60));
  console.log((analysis.content || "").slice(0, 2500));
  ```
</CodeGroup>

## Example Output

```text theme={"dark"}
Analyzed 187 comments from 8 high-engagement posts
============================================================

## 1. Questions Prospects Ask

**Pricing & ROI (34 mentions)**
- "What does this actually cost for a team of 20?"
- "Is there a free tier to test before committing?"
- "How long before we see measurable ROI?"

**Implementation (28 mentions)**
- "How long does onboarding take?"
- "Does this integrate with Salesforce out of the box?"
- "Do we need a dedicated admin?"

**Comparison (19 mentions)**
- "How is this different from [Competitor X]?"
- "We tried [Competitor Y] and it was clunky — is this better?"

## 2. Objections
1. "Sounds great in theory but we're locked into annual contracts" (12x)
   → Counter: Lead with migration support and contract overlap pricing
2. "Our team is too small for this" (9x)
   → Counter: Highlight self-serve tier and 1-person success stories
3. "AI-generated content feels inauthentic" (7x)
   → Counter: Position as augmentation, show human-in-the-loop workflow

## 3. Language Map (use verbatim in copy)
- "scattered across tools" (not "fragmented tech stack")
- "hours on reports nobody reads"
- "flying blind on what works"
- "just guessing at this point"
- "can't prove it to leadership"

## 4. Sentiment: Enthusiastic 38% | Curious 31% | Skeptical 22% | Critical 9%

## 5. Content Gaps
1. Migration/switching guide (28 comments ask about transition)
2. Small team playbook (underserved segment based on comments)
3. ROI calculator or benchmark data
```

## Error Handling

<AccordionGroup>
  <Accordion title="Comments endpoint pagination">The comments endpoint returns max 100 per call. For posts with 100+ comments, paginate with `start` parameter. High-comment posts are rare on Company Pages.</Accordion>
  <Accordion title="Filtering short comments">Comments under 15 characters (e.g., emoji reactions, "Great!") add noise. The code filters these out before analysis.</Accordion>
  <Accordion title="Context window with large comment volumes">At 60 comments × 400 chars, the prompt stays under typical limits. For 200+ comments, batch into multiple Mave calls and merge results.</Accordion>
  <Accordion title="Comment author privacy">LinkedIn's API does not return commenter profile data unless they're connected to the token holder. The analysis focuses on comment text, not identity.</Accordion>
</AccordionGroup>

***

<CardGroup cols={2}>
  <Card title="LinkedIn Content Integration" icon="arrow-left" href="/integrations/linkedin-content" />

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