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

Customer.io stores rich customer attributes — plan type, industry, usage frequency, signup source, feature flags. These attributes define natural audience clusters, but they live as flat key-value pairs with no synthesis. This job pulls customer attributes via the App API, clusters them by plan tier and industry, then creates Mavera Custom Personas for each segment. Every focus group and content generation can now target the exact mix of attributes that defines each customer type. Flow: Customer.io App API → Customer attributes → Cluster by plan/industry/usage → Mavera POST /api/v1/personas → Attribute-driven persona library

Architecture

Code

import os, requests, time
from collections import defaultdict

CIO_APP = os.environ["CIO_APP_KEY"]
MV = os.environ["MAVERA_API_KEY"]
APP_BASE = "https://api.customer.io/v1"
MB = "https://app.mavera.io/api/v1"
APP_H = {"Authorization": f"Bearer {CIO_APP}"}
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

SEGMENT_IDS = [1, 2, 3, 4, 5]

# 1. Pull customers from key segments
all_customers = []
for seg_id in SEGMENT_IDS:
    r = requests.get(f"{APP_BASE}/segments/{seg_id}/membership",
        headers=APP_H, params={"limit": 200})
    if r.status_code == 429:
        time.sleep(1)
        r = requests.get(f"{APP_BASE}/segments/{seg_id}/membership",
            headers=APP_H, params={"limit": 200})
    r.raise_for_status()
    ids = r.json().get("ids", [])

    for cid in ids[:50]:
        cr = requests.get(f"{APP_BASE}/customers/{cid}/attributes",
            headers=APP_H)
        if cr.status_code == 429:
            time.sleep(1)
            cr = requests.get(f"{APP_BASE}/customers/{cid}/attributes",
                headers=APP_H)
        if cr.ok:
            attrs = cr.json().get("customer", {})
            all_customers.append(attrs)
        time.sleep(0.12)

# 2. Cluster by plan × industry × usage
clusters = defaultdict(list)
for cust in all_customers:
    plan = cust.get("plan_type", "free").lower()
    industry = cust.get("industry", "unknown").lower()
    usage = cust.get("usage_tier", "low").lower()
    key = f"{plan}|{industry}|{usage}"
    clusters[key].append(cust)

# 3. Create personas for significant clusters (5+ members)
personas = []
for key, members in sorted(clusters.items(), key=lambda x: -len(x[1])):
    if len(members) < 5:
        continue

    plan, industry, usage = key.split("|")
    roles = list({m.get("role", "") for m in members if m.get("role")})[:5]
    sources = list({m.get("signup_source", "") for m in members if m.get("signup_source")})[:3]
    features = list({f for m in members for f in (m.get("active_features") or "").split(",") if f.strip()})[:5]

    r = requests.post(f"{MB}/personas", headers=MV_H, json={
        "name": f"CIO: {plan.title()} / {industry.title()} / {usage.title()} Usage",
        "description": (
            f"Customer.io segment. Plan: {plan}. Industry: {industry}. "
            f"Usage tier: {usage}. N={len(members)}. "
            f"Common roles: {', '.join(roles[:3])}. "
            f"Signup sources: {', '.join(sources)}. "
            f"Active features: {', '.join(features[:3])}."
        ),
        "demographic": {
            "job_titles": roles,
            "industries": [industry],
        },
        "psychographic": {
            "plan_tier": plan,
            "usage_level": usage,
            "feature_adoption": features[:3],
        },
    })
    r.raise_for_status()
    personas.append({
        "cluster": key,
        "id": r.json()["id"],
        "n": len(members),
    })
    print(f"{plan.title()} / {industry.title()} / {usage.title()}: "
          f"{r.json()['id']} ({len(members)} customers)")
    time.sleep(0.3)

print(f"\nCreated {len(personas)} personas from {len(all_customers)} customers")

Example Output

[
  { "cluster": "pro|saas|high",    "id": "per_cio_a1b2", "n": 87 },
  { "cluster": "pro|ecommerce|medium", "id": "per_cio_c3d4", "n": 42 },
  { "cluster": "enterprise|fintech|high", "id": "per_cio_e5f6", "n": 31 },
  { "cluster": "free|saas|low",    "id": "per_cio_g7h8", "n": 28 },
  { "cluster": "starter|healthcare|medium", "id": "per_cio_i9j0", "n": 15 }
]
Pro / Saas / High: per_cio_a1b2 (87 customers)
Pro / Ecommerce / Medium: per_cio_c3d4 (42 customers)
Enterprise / Fintech / High: per_cio_e5f6 (31 customers)
Free / Saas / Low: per_cio_g7h8 (28 customers)
Starter / Healthcare / Medium: per_cio_i9j0 (15 customers)

Created 5 personas from 203 customers

Error Handling

Individual customer attribute lookups burn rate fast. The code throttles to 120ms between requests. For 1,000+ customers, use Customer.io’s data export or segment-based approaches instead of per-customer calls.
Not all customers have plan_type, industry, or usage_tier. Defaults to free/unknown/low. Ensure these attributes are set via Track API identify calls before running this job.
The /segments/{id}/membership endpoint returns up to 200 IDs per page. For larger segments, paginate with the next cursor.
Re-running creates duplicates. Check with GET /api/v1/personas?search=CIO: before creating, or DELETE old versions first.

What’s Next

Customer.io Integration

Back to Customer.io integration overview

Campaign Messaging Strategy

Analyze campaign metrics for winning patterns

Webhook → Mave Trigger

Automated retention research on churn signals

Personas API

Full reference for POST /api/v1/personas