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 HubSpot portal has contacts across lifecycle stages — subscriber, lead, MQL, SQL, customer, evangelist. Each stage represents a distinct mindset. You create a Mavera persona per stage so every generated or tested piece of content speaks to the right funnel position.

Architecture

Code

import os, requests, time

HS = os.environ["HUBSPOT_ACCESS_TOKEN"]
MV = os.environ["MAVERA_API_KEY"]

STAGES = {
    "subscriber": "Subscriber", "lead": "Lead",
    "marketingqualifiedlead": "MQL", "salesqualifiedlead": "SQL",
    "customer": "Customer", "evangelist": "Evangelist",
}

def search_stage(stage, limit=50):
    r = requests.post(
        "https://api.hubapi.com/crm/v3/objects/contacts/search",
        headers={"Authorization": f"Bearer {HS}"},
        json={
            "filterGroups": [{"filters": [
                {"propertyName": "lifecyclestage", "operator": "EQ", "value": stage}
            ]}],
            "properties": ["jobtitle", "company", "industry"],
            "limit": limit,
        },
    )
    if r.status_code == 429:
        time.sleep(1); return search_stage(stage, limit)
    r.raise_for_status()
    return r.json().get("results", [])

personas = []
for stage, label in STAGES.items():
    contacts = search_stage(stage)
    if not contacts: continue

    titles = list({c["properties"].get("jobtitle","") for c in contacts if c["properties"].get("jobtitle")})[:5]
    industries = list({c["properties"].get("industry","") for c in contacts if c["properties"].get("industry")})[:3]

    r = requests.post(
        "https://app.mavera.io/api/v1/personas",
        headers={"Authorization": f"Bearer {MV}", "Content-Type": "application/json"},
        json={
            "name": f"HubSpot {label}",
            "description": f"{label} stage. Roles: {', '.join(titles[:3])}. Industries: {', '.join(industries)}. N={len(contacts)}.",
            "demographic": {"job_titles": titles, "industries": industries},
            "psychographic": {"mindset": f"Typical {label.lower()} intent level"},
        },
    )
    r.raise_for_status()
    personas.append({"stage": label, "id": r.json()["id"], "n": len(contacts)})
    print(f"{label}: {r.json()['id']} ({len(contacts)} contacts)")
    time.sleep(0.3)

Example Output

[ { "stage": "Subscriber", "id": "per_abc123", "n": 1240 },
  { "stage": "Lead", "id": "per_def456", "n": 890 },
  { "stage": "MQL", "id": "per_ghi789", "n": 340 },
  { "stage": "SQL", "id": "per_jkl012", "n": 120 },
  { "stage": "Customer", "id": "per_mno345", "n": 560 },
  { "stage": "Evangelist", "id": "per_pqr678", "n": 45 } ]

Error Handling

The code retries on 429 with 1s backoff. For large portals, add exponential backoff or paginate with after cursor.
Not all portals use all 6 stages. Custom stages need the internal name. Check with GET /crm/v3/properties/contacts/lifecyclestage.

HubSpot Integration

Personas API