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 team maintains competitive intelligence in a Notion wiki — feature comparisons, pricing snapshots, win/loss notes, and market positioning. This data is goldmine for sales but trapped in static pages. This job pulls competitive pages, enriches them through Mave Agent’s research capabilities, then generates structured battle cards with the Generate endpoint. The result is always-fresh, AI-enriched battle cards that combine your internal intel with public information. Flow: Notion POST /databases/{id}/query (competitive wiki) → Extract pages → Mavera POST /api/v1/mave/chat (enrich each competitor) → POST /api/v1/generations → Battle card documents

Code

import os, requests, time

NOTION = os.environ["NOTION_API_KEY"]
MV = os.environ["MAVERA_API_KEY"]
NB = "https://api.notion.com/v1"
MB = "https://app.mavera.io/api/v1"
NH = {
    "Authorization": f"Bearer {NOTION}",
    "Notion-Version": "2022-06-28",
    "Content-Type": "application/json",
}
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

COMPETITIVE_DB_ID = "your-competitive-wiki-database-id"

# 1. Query competitive wiki pages
competitors = requests.post(f"{NB}/databases/{COMPETITIVE_DB_ID}/query", headers=NH, json={
    "sorts": [{"property": "Last edited time", "direction": "descending"}],
    "page_size": 10,
}).json().get("results", [])

print(f"Found {len(competitors)} competitor pages")

# 2. Extract text from each page
def get_blocks_text(block_id):
    texts = []
    cursor = None
    while True:
        params = {"page_size": 100}
        if cursor:
            params["start_cursor"] = cursor
        r = requests.get(f"{NB}/blocks/{block_id}/children", headers=NH, params=params)
        if r.status_code == 429:
            time.sleep(1); continue
        data = r.json()
        for block in data.get("results", []):
            btype = block.get("type", "")
            rt = block.get(btype, {}).get("rich_text", [])
            text = "".join(t.get("plain_text", "") for t in rt)
            if text.strip():
                texts.append(text)
        cursor = data.get("next_cursor")
        if not cursor:
            break
        time.sleep(0.4)
    return "\n".join(texts)

battle_cards = []
for page in competitors:
    props = page.get("properties", {})
    title_parts = props.get("Name", props.get("Title", props.get("Competitor", {}))).get("title", [])
    comp_name = "".join(t.get("plain_text", "") for t in title_parts) or "Unknown Competitor"

    category = (props.get("Category", {}).get("select", {}) or {}).get("name", "")
    website = (props.get("Website", {}).get("url", None)) or ""

    internal_intel = get_blocks_text(page["id"])
    print(f"  Extracting: {comp_name} ({len(internal_intel)} chars)")

    # 3. Enrich with Mave Agent (public research)
    enrichment = requests.post(f"{MB}/mave/chat", headers=MH, json={
        "message": (
            f"Research the company '{comp_name}'"
            f"{' (' + website + ')' if website else ''}. "
            f"Focus on: recent product launches, pricing changes, funding rounds, "
            f"key hires, market positioning, and notable customer wins or losses. "
            f"Provide cited facts with dates where possible."
        ),
    }).json()
    mave_research = enrichment.get("content", "")
    sources = enrichment.get("sources", [])

    # 4. Generate battle card combining internal intel + Mave research
    gen = requests.post(f"{MB}/generations", headers=MH, json={
        "prompt": (
            f"Create a sales battle card for competing against '{comp_name}'.\n\n"
            f"INTERNAL INTELLIGENCE (from our wiki):\n{internal_intel[:4000]}\n\n"
            f"MARKET RESEARCH:\n{mave_research[:4000]}\n\n"
            "Structure the battle card with these sections:\n"
            "1. **Overview** — Company summary, funding, employee count\n"
            "2. **Their Positioning** — How they describe themselves\n"
            "3. **Where They Win** — Their genuine strengths\n"
            "4. **Where We Win** — Our advantages backed by evidence\n"
            "5. **Common Objections** — What prospects say, with rebuttals\n"
            "6. **Killer Questions** — Questions reps should ask that expose weaknesses\n"
            "7. **Landmine Questions** — Questions to watch for that favor them\n"
            "8. **Quick Pricing Comparison** — If pricing data is available\n"
            "9. **Recent Customer Movements** — Wins/losses from either side\n\n"
            "Be specific and actionable. This goes directly to sales reps."
        ),
    }).json()

    card_content = gen.get("output", gen.get("content", ""))
    battle_cards.append({
        "competitor": comp_name,
        "card": card_content,
        "sources": len(sources),
    })

    # 5. Optionally write back to Notion as a child page or block
    blocks = []
    for paragraph in card_content.split("\n\n")[:50]:
        p = paragraph.strip()
        if not p:
            continue
        if p.startswith("## ") or p.startswith("**") and p.endswith("**"):
            blocks.append({"object": "block", "type": "heading_2",
                "heading_2": {"rich_text": [{"type": "text", "text": {"content": p.strip("#* ")}}]}})
        else:
            blocks.append({"object": "block", "type": "paragraph",
                "paragraph": {"rich_text": [{"type": "text", "text": {"content": p[:2000]}}]}})

    if blocks:
        bc_page = requests.post(f"{NB}/pages", headers=NH, json={
            "parent": {"database_id": COMPETITIVE_DB_ID},
            "properties": {
                "Name": {"title": [{"text": {"content": f"Battle Card: {comp_name}"}}]},
            },
            "children": blocks[:100],
        })
        if bc_page.ok:
            print(f"    → Battle card page created in Notion")

    print(f"  ✓ {comp_name}{len(card_content)} chars, {len(sources)} sources")
    time.sleep(1)

print(f"\nGenerated {len(battle_cards)} battle cards")

Example Output

Found 4 competitor pages
  Extracting: CompetitorX (2847 chars)
    → Battle card page created in Notion
  ✓ CompetitorX — 3421 chars, 6 sources

BATTLE CARD: CompetitorX
============================================================

## Overview
Series C startup ($45M raised, Jan 2026). ~180 employees.
Positioning: "AI-powered revenue intelligence."

## Where They Win
- Lower price point ($79/seat vs our $149)
- Strong Slack community (12K members)
- Faster self-serve onboarding (< 10 minutes)

## Where We Win
- No SSO/SCIM below Enterprise tier (dealbreaker for security teams)
- Our API supports webhooks; theirs is polling-only
- Dedicated CSM at $25K+ ACV (theirs: $75K+)
- SOC 2 Type II + HIPAA (they only have SOC 2 Type I)

## Killer Questions
1. "How important is SSO to your InfoSec team?"
2. "Do you need real-time data or is polling every 15 min acceptable?"
3. "What's your ACV threshold for getting a dedicated CSM?"

## Landmine Questions (Watch For)
1. "What's your per-seat price?" (they're cheaper — reframe to total cost of ownership)
2. "How quickly can we get started?" (their self-serve is faster)

## Recent Customer Movements
- We won 3 accounts from CompetitorX in Q1 (all cited SSO gaps)
- Lost 1 SMB deal to CompetitorX on price (sub-$15K ACV)

Error Handling

Competitive wikis have inconsistent schemas. The code tries Name, Title, and Competitor properties. If your title property has a different name, adjust the extraction logic.
Mave Agent’s research reflects its latest available data. For time-sensitive competitive intelligence (e.g., a pricing change announced today), supplement with the Notion wiki’s internal observations.
Writing battle card pages back to Notion requires the database schema to support the properties you’re setting. If your competitive wiki has required properties beyond Name, the create call will fail with a 400 error.
This job makes 3 API calls per competitor (Notion read + Mave + Generate + optional Notion write). For 10 competitors, that’s ~30+ calls. The 1-second delay between competitors keeps you within Notion’s 3 req/sec limit.