Skip to main content

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.

Scenario

Your product/marketing team runs sprint retrospectives in Asana — each retro is a task or project with discussion items captured as subtasks and comments. Over time, retros reveal messaging gold: what customers love, where positioning falls flat, which features generate excitement. This job aggregates retro content and feeds it to Mave for messaging pattern extraction. Flow: Asana GET /tasks (retro tasks) → GET /tasks/{id}/subtasks + GET /tasks/{id}/stories → Aggregate → Mavera POST /api/v1/mave/chat → Messaging insights

Code

import os, requests, time

ASANA = os.environ["ASANA_PAT"]
MV = os.environ["MAVERA_API_KEY"]
AB = "https://app.asana.com/api/1.0"
MB = "https://app.mavera.io/api/v1"
AH = {"Authorization": f"Bearer {ASANA}"}
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

RETRO_PROJECT_GID = "1234567890123456"

# 1. Fetch retro tasks (each retro is a task in the project)
retro_tasks = requests.get(f"{AB}/projects/{RETRO_PROJECT_GID}/tasks", headers=AH,
    params={
        "opt_fields": "name,notes,completed_at,created_at",
        "limit": 20,
    }).json().get("data", [])

print(f"Found {len(retro_tasks)} retro entries")

# 2. For each retro, get subtasks (discussion items) and comments
retro_corpus = []
for retro in retro_tasks[:12]:
    entry = f"## {retro['name']}\n{retro.get('notes', '')[:1000]}\n"

    # Subtasks (retro items)
    subtasks = requests.get(f"{AB}/tasks/{retro['gid']}/subtasks", headers=AH,
        params={"opt_fields": "name,notes,completed", "limit": 50}).json().get("data", [])
    if subtasks:
        entry += "\nDiscussion items:\n"
        for st in subtasks:
            entry += f"- {'[done]' if st.get('completed') else '[open]'} {st['name']}"
            if st.get("notes"):
                entry += f": {st['notes'][:200]}"
            entry += "\n"

    # Comments (stories of type "comment")
    stories = requests.get(f"{AB}/tasks/{retro['gid']}/stories", headers=AH,
        params={"opt_fields": "text,type,created_by.name", "limit": 30}).json().get("data", [])
    comments = [s for s in stories if s.get("type") == "comment" and s.get("text")]
    if comments:
        entry += "\nComments:\n"
        for c in comments[:10]:
            author = c.get("created_by", {}).get("name", "Unknown")
            entry += f"- [{author}] {c['text'][:300]}\n"

    retro_corpus.append(entry)
    time.sleep(0.5)

corpus = "\n\n---\n\n".join(retro_corpus)
print(f"Aggregated {len(retro_corpus)} retros ({len(corpus)} chars)")

# 3. Mave messaging insights
insights = requests.post(f"{MB}/mave/chat", headers=MH, json={
    "message": (
        f"Messaging strategist. Analyze {len(retro_corpus)} sprint retrospectives "
        f"from a product/marketing team.\n\n"
        f"RETRO NOTES:\n{corpus[:10000]}\n\n"
        "Extract MESSAGING INSIGHTS:\n\n"
        "1. **Customer Language Patterns** — Phrases and framing the team uses when "
        "discussing customer feedback. These are messaging gold.\n"
        "2. **Positioning Gaps** — Where did messaging fall flat? What confused customers?\n"
        "3. **Feature Excitement Signals** — Which features/updates generated the most "
        "positive discussion? These should be amplified in marketing.\n"
        "4. **Competitive Positioning Clues** — Any competitor mentions and how the team "
        "frames the comparison.\n"
        "5. **Objection Patterns** — Recurring customer pushback themes.\n"
        "6. **Messaging Recommendations** — 5 specific messaging changes based on the data.\n"
        "7. **Tagline Candidates** — 3-5 tagline options derived from the language patterns.\n\n"
        "Quote directly from the retro notes. Attribution matters."
    ),
}).json()

print(f"\n{'='*60}\nMESSAGING INSIGHTS FROM RETROS\n{'='*60}")
print(insights.get("content", "")[:3000])

Example Output

Found 12 retro entries
Aggregated 12 retros (14832 chars)

MESSAGING INSIGHTS FROM RETROS
============================================================

## 1. Customer Language Patterns
- Customers consistently say "I just want it to work" (appeared in
  5/12 retros) → Simplicity messaging should lead
- "We don't have time to learn another tool" (4 retros) → Zero
  learning curve positioning
- "Show me the ROI before I commit" (3 retros) → ROI calculator
  should be above-the-fold

## 2. Positioning Gaps
- Sprint 8 retro: "Customers confused by 'AI-powered' — they think
  it means less control, not more" → Reframe AI as "AI-assisted"
- Sprint 10: "Enterprise prospects think we're too small for them"
  → Need social proof from similar-sized companies

## 3. Feature Excitement
- Real-time dashboards: "Biggest wow moment in every demo" (7 retros)
- API webhooks: "Developers light up when they see this"
- Custom reports: "This is where deals accelerate"

## 6. Messaging Recommendations
1. Replace "AI-powered" with "AI-assisted" in all materials
2. Lead demos with real-time dashboards (proven wow factor)
3. Add ROI calculator to pricing page
4. Create "Zero Learning Curve" campaign pillar
5. Build enterprise case study program (3 logos by Q3)

## 7. Tagline Candidates
- "It just works. You just grow."
- "Less learning. More doing."
- "From data to decisions in minutes, not meetings."

Error Handling

Some retros may be placeholder tasks with no notes or subtasks. The code processes them but generates thin analysis. Filter by notes length or subtask count for higher-quality input.
Asana stories include system events (assignment, status changes) and comments. The code filters to type: "comment" only. System stories add noise to the corpus.
The stories endpoint counts against the global rate limit. With 12 retros × 2 calls each (subtasks + stories) = 24 calls. The 500ms delay keeps this well within limits.
Old retros may reference obsolete features or resolved issues. Consider limiting to the last 3-6 months of retros for relevant messaging insights.