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

# Lifecycle Stage Persona Builder

> Create Mavera personas from HubSpot lifecycle stages — subscriber, lead, MQL, SQL, customer, evangelist

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

```mermaid theme={"dark"}
flowchart LR
    A[HubSpot CRM Search] --> B[Contacts by lifecycle stage] --> C[Aggregate traits] --> D["POST /api/v1/personas"] --> E[Persona per stage]
```

## Code

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

  ```javascript JavaScript theme={"dark"}
  const HS = process.env.HUBSPOT_ACCESS_TOKEN;
  const MV = process.env.MAVERA_API_KEY;

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

  async function searchStage(stage, limit = 50) {
    const res = await fetch("https://api.hubapi.com/crm/v3/objects/contacts/search", {
      method: "POST",
      headers: { Authorization: `Bearer ${HS}`, "Content-Type": "application/json" },
      body: JSON.stringify({
        filterGroups: [{ filters: [
          { propertyName: "lifecyclestage", operator: "EQ", value: stage },
        ]}],
        properties: ["jobtitle", "company", "industry"],
        limit,
      }),
    });
    if (res.status === 429) { await new Promise(r => setTimeout(r, 1000)); return searchStage(stage, limit); }
    if (!res.ok) throw new Error(`HubSpot ${res.status}`);
    return (await res.json()).results || [];
  }

  const personas = [];
  for (const [stage, label] of Object.entries(STAGES)) {
    const contacts = await searchStage(stage);
    if (!contacts.length) continue;

    const titles = [...new Set(contacts.map(c => c.properties.jobtitle).filter(Boolean))].slice(0, 5);
    const industries = [...new Set(contacts.map(c => c.properties.industry).filter(Boolean))].slice(0, 3);

    const res = await fetch("https://app.mavera.io/api/v1/personas", {
      method: "POST",
      headers: { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" },
      body: JSON.stringify({
        name: `HubSpot ${label}`,
        description: `${label} stage. Roles: ${titles.slice(0,3).join(", ")}. Industries: ${industries.join(", ")}. N=${contacts.length}.`,
        demographic: { job_titles: titles, industries },
        psychographic: { mindset: `Typical ${label.toLowerCase()} intent level` },
      }),
    });
    if (!res.ok) throw new Error(`Mavera ${res.status}`);
    const persona = await res.json();
    personas.push({ stage: label, id: persona.id, n: contacts.length });
    console.log(`${label}: ${persona.id} (${contacts.length} contacts)`);
    await new Promise(r => setTimeout(r, 300));
  }
  ```
</CodeGroup>

## Example Output

```json theme={"dark"}
[ { "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

<AccordionGroup>
  <Accordion title="CRM Search rate limit (4 req/sec)">The code retries on 429 with 1s backoff. For large portals, add exponential backoff or paginate with `after` cursor.</Accordion>
  <Accordion title="Empty lifecycle stages">Not all portals use all 6 stages. Custom stages need the internal name. Check with `GET /crm/v3/properties/contacts/lifecyclestage`.</Accordion>
</AccordionGroup>

***

<CardGroup cols={2}>
  <Card title="HubSpot Integration" icon="arrow-left" href="/integrations/hubspot" />

  <Card title="Personas API" icon="user" href="/features/personas" />
</CardGroup>
