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

# A/B Copy Production

> Use Generate twice with the same brief but different brand voices or personas to produce ready-to-test A/B creative variants — no manual rewriting needed

## Scenario

You have a product launch landing page, an email campaign, or a set of ads — and you need A/B variants. Typically you'd write variant A, then manually tweak tone and angle for variant B. That's slow, and the "tweaking" often produces variants that are too similar to produce meaningful test results.

This playbook produces genuinely different variants by changing the **brand voice** or **persona** — not just individual words. Variant A might use your bold, direct brand voice while variant B uses a warmer, storytelling voice. Or variant A is written for a startup founder persona while variant B targets an enterprise buyer. Same facts, different framing. Ready for your A/B testing platform.

<Info>
  **Mavera-only.** No A/B testing platform integration. This playbook produces the creative variants — you deploy them wherever you run tests.
</Info>

***

## Architecture

```mermaid theme={"dark"}
flowchart LR
    A["Shared Brief"] --> B1["Voice A / Persona A"]
    A --> B2["Voice B / Persona B"]
    A --> B3["Voice C / Persona C"]
    B1 --> C1["Variant A"]
    B2 --> C2["Variant B"]
    B3 --> C3["Variant C"]
    C1 --> D["Structured Comparison"]
    C2 --> D
    C3 --> D
    D --> E["A/B Test Ready Package"]
```

***

## What You Need

| Requirement                        | Details                                                                                                     |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Mavera API key**                 | Starts with `mvra_live_`. Get one at [Developer Settings](https://app.mavera.io/settings/developer).        |
| **Workspace ID**                   | From your dashboard URL (`ws_...`).                                                                         |
| **2–3 brand voice IDs**            | Different voice profiles to drive variant differentiation. Create via [Brand Voice](/features/brand-voice). |
| **OR 2–3 persona IDs**             | Use personas instead of (or alongside) voice differences.                                                   |
| **Content brief**                  | Shared topic, audience, and key points — consistent across all variants.                                    |
| **Credits**                        | \~100–300 total. See [Credits Estimate](#credits-estimate).                                                 |
| **Python 3.8+** or **Node.js 18+** | `requests` + `openai` SDK for Python; native `fetch` for Node.                                              |

```
MAVERA_API_KEY=mvra_live_your_key_here
MAVERA_WORKSPACE_ID=ws_your_workspace_id
```

***

## The Flow

<Steps>
  <Step title="Create or select brand voices / personas">
    You need at least 2 differentiated voices or personas. Create brand voices from different source material (e.g. your blog vs. your support docs), or use personas with different psychographic profiles.
  </Step>

  <Step title="Define the shared brief">
    Write one brief with topic, audience, key points, and format. This brief is identical across all variants — the only variable is the voice or persona.
  </Step>

  <Step title="Generate variants">
    Call `POST /generations` once per voice/persona. Each call produces a variant shaped by its specific voice or persona context.
  </Step>

  <Step title="Compare variants">
    Use Chat with `response_format` to produce a structured comparison: tone analysis, readability, emotional appeal, and a recommendation for which variant to test first.
  </Step>
</Steps>

***

## Strategy A — Different Brand Voices

Create 2–3 brand voices from different source material. Each voice encodes different tone, vocabulary, and style rules.

<CodeGroup>
  ```python Python theme={"dark"}
  import os
  import time
  import json
  import requests

  API_KEY = os.environ["MAVERA_API_KEY"]
  WORKSPACE_ID = os.environ["MAVERA_WORKSPACE_ID"]
  BASE = "https://app.mavera.io/api/v1"
  HEADERS = {
      "Authorization": f"Bearer {API_KEY}",
      "Content-Type": "application/json",
  }


  def create_voice(label, urls, usage_context):
      """Create a brand voice and wait for READY."""
      resp = requests.post(f"{BASE}/brand-voices", headers=HEADERS, json={
          "label": label,
          "usage_context": usage_context,
          "urls": urls,
          "workspace_id": WORKSPACE_ID,
      })
      resp.raise_for_status()
      bv = resp.json()

      for _ in range(30):
          check = requests.get(f"{BASE}/brand-voices/{bv['id']}", headers=HEADERS).json()
          if check.get("status") == "READY":
              return check
          time.sleep(10)
      raise TimeoutError(f"Brand voice {bv['id']} not ready")


  VOICES = {
      "bold_direct": create_voice(
          label="Bold & Direct",
          urls=["https://yourbrand.com/blog", "https://yourbrand.com/manifesto"],
          usage_context="High-energy marketing copy that challenges the status quo",
      ),
      "warm_storytelling": create_voice(
          label="Warm & Storytelling",
          urls=["https://yourbrand.com/customer-stories", "https://yourbrand.com/about"],
          usage_context="Empathetic content that connects through customer narratives",
      ),
      "data_driven": create_voice(
          label="Data-Driven & Precise",
          urls=["https://yourbrand.com/research", "https://yourbrand.com/case-studies"],
          usage_context="Evidence-based content with metrics and benchmarks",
      ),
  }

  for name, voice in VOICES.items():
      print(f"  {name}: {voice['id']} — {voice.get('tone_adjectives', [])}")
  ```

  ```javascript JavaScript theme={"dark"}
  const API_KEY = process.env.MAVERA_API_KEY;
  const WORKSPACE_ID = process.env.MAVERA_WORKSPACE_ID;
  const BASE = "https://app.mavera.io/api/v1";
  const HEADERS = {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  };

  async function createVoice(label, urls, usageContext) {
    const resp = await fetch(`${BASE}/brand-voices`, {
      method: "POST",
      headers: HEADERS,
      body: JSON.stringify({
        label,
        usage_context: usageContext,
        urls,
        workspace_id: WORKSPACE_ID,
      }),
    });
    const bv = await resp.json();

    for (let i = 0; i < 30; i++) {
      const check = await fetch(`${BASE}/brand-voices/${bv.id}`, { headers: HEADERS }).then((r) => r.json());
      if (check.status === "READY") return check;
      await new Promise((r) => setTimeout(r, 10000));
    }
    throw new Error(`Brand voice ${bv.id} not ready`);
  }

  const VOICES = {
    bold_direct: await createVoice(
      "Bold & Direct",
      ["https://yourbrand.com/blog", "https://yourbrand.com/manifesto"],
      "High-energy marketing copy that challenges the status quo"
    ),
    warm_storytelling: await createVoice(
      "Warm & Storytelling",
      ["https://yourbrand.com/customer-stories", "https://yourbrand.com/about"],
      "Empathetic content that connects through customer narratives"
    ),
    data_driven: await createVoice(
      "Data-Driven & Precise",
      ["https://yourbrand.com/research", "https://yourbrand.com/case-studies"],
      "Evidence-based content with metrics and benchmarks"
    ),
  };

  for (const [name, voice] of Object.entries(VOICES)) {
    console.log(`  ${name}: ${voice.id} — ${voice.tone_adjectives}`);
  }
  ```
</CodeGroup>

***

## Generate A/B Variants

The brief is identical. Only the `brand_voice_id` changes.

<CodeGroup>
  ```python Python theme={"dark"}
  SHARED_BRIEF = {
      "app_id": "email_sequence_generator",
      "input_data": {
          "sequence_goal": "Convert free trial users to paid plans",
          "emails_count": 3,
          "target_audience": "SaaS trial users in their first week",
          "key_points": [
              "Highlight the top 3 features they haven't tried",
              "Include a customer success story",
              "End with a time-limited upgrade offer",
          ],
      },
  }


  def wait_for_generation(gen_id, max_wait=300):
      for _ in range(max_wait // 10):
          resp = requests.get(f"{BASE}/generations/{gen_id}", headers=HEADERS)
          data = resp.json()
          if data.get("status") == "COMPLETED":
              return data
          time.sleep(10)
      raise TimeoutError(f"Generation {gen_id} timed out")


  variants = {}
  total_credits = 0

  for voice_name, voice in VOICES.items():
      print(f"\nGenerating variant: {voice_name}...")

      payload = {
          **SHARED_BRIEF,
          "title": f"A/B Variant — {voice['label']}",
          "brand_voice_id": voice["id"],
          "workspace_id": WORKSPACE_ID,
      }

      resp = requests.post(f"{BASE}/generations", headers=HEADERS, json=payload)
      resp.raise_for_status()
      gen = resp.json()

      if gen.get("status") in ("PENDING", "RUNNING"):
          gen = wait_for_generation(gen["id"])

      credits = gen.get("usage", {}).get("credits_used", 0)
      total_credits += credits

      variants[voice_name] = {
          "voice_label": voice["label"],
          "voice_id": voice["id"],
          "output": gen.get("output", ""),
          "credits": credits,
      }
      print(f"  ✓ {credits} credits | {len(gen.get('output', ''))} chars")

  print(f"\nGenerated {len(variants)} variants | {total_credits} total credits")
  ```

  ```javascript JavaScript theme={"dark"}
  const SHARED_BRIEF = {
    app_id: "email_sequence_generator",
    input_data: {
      sequence_goal: "Convert free trial users to paid plans",
      emails_count: 3,
      target_audience: "SaaS trial users in their first week",
      key_points: [
        "Highlight the top 3 features they haven't tried",
        "Include a customer success story",
        "End with a time-limited upgrade offer",
      ],
    },
  };

  async function waitForGeneration(genId, maxWait = 300) {
    for (let elapsed = 0; elapsed < maxWait; elapsed += 10) {
      const resp = await fetch(`${BASE}/generations/${genId}`, { headers: HEADERS });
      const data = await resp.json();
      if (data.status === "COMPLETED") return data;
      await new Promise((r) => setTimeout(r, 10000));
    }
    throw new Error(`Generation ${genId} timed out`);
  }

  const variants = {};
  let totalCredits = 0;

  for (const [voiceName, voice] of Object.entries(VOICES)) {
    console.log(`\nGenerating variant: ${voiceName}...`);

    const payload = {
      ...SHARED_BRIEF,
      title: `A/B Variant — ${voice.label}`,
      brand_voice_id: voice.id,
      workspace_id: WORKSPACE_ID,
    };

    const resp = await fetch(`${BASE}/generations`, {
      method: "POST",
      headers: HEADERS,
      body: JSON.stringify(payload),
    });
    let gen = await resp.json();

    if (gen.status === "PENDING" || gen.status === "RUNNING") {
      gen = await waitForGeneration(gen.id);
    }

    const credits = gen.usage?.credits_used || 0;
    totalCredits += credits;

    variants[voiceName] = {
      voice_label: voice.label,
      voice_id: voice.id,
      output: gen.output || "",
      credits,
    };
    console.log(`  ✓ ${credits} credits | ${(gen.output || "").length} chars`);
  }

  console.log(`\nGenerated ${Object.keys(variants).length} variants | ${totalCredits} total credits`);
  ```
</CodeGroup>

***

## Strategy B — Different Personas

Instead of (or in addition to) voices, use different personas. The persona changes the *audience lens*, not just the voice — the content adapts its value propositions, examples, and framing. Pass `persona_id` in `extra_body` when calling Chat, using the same brief content but different personas (e.g. `persona_startup_founder`, `persona_enterprise_cto`, `persona_smb_ops`). Each response is shaped by that persona's priorities, pain points, and communication preferences.

***

## Compare Variants

Use Chat with `response_format` to produce a structured side-by-side analysis.

<CodeGroup>
  ```python Python theme={"dark"}
  COMPARISON_SCHEMA = {
      "type": "json_schema",
      "json_schema": {
          "name": "ab_comparison",
          "strict": True,
          "schema": {
              "type": "object",
              "properties": {
                  "variants": {
                      "type": "array",
                      "items": {
                          "type": "object",
                          "properties": {
                              "name": {"type": "string"},
                              "tone_description": {"type": "string"},
                              "emotional_appeal": {"type": "number"},
                              "clarity": {"type": "number"},
                              "urgency": {"type": "number"},
                              "cta_strength": {"type": "number"},
                              "strengths": {"type": "array", "items": {"type": "string"}},
                              "weaknesses": {"type": "array", "items": {"type": "string"}},
                          },
                          "required": ["name", "tone_description", "emotional_appeal", "clarity", "urgency", "cta_strength", "strengths", "weaknesses"],
                      },
                  },
                  "recommendation": {"type": "string"},
                  "test_hypothesis": {"type": "string"},
              },
              "required": ["variants", "recommendation", "test_hypothesis"],
          },
      },
  }

  variant_texts = "\n\n---\n\n".join(
      f"### {name}\n{data['output'][:2000]}"
      for name, data in variants.items()
  )

  compare_resp = mavera.responses.create(
      model="mavera-1",
      input=[
          {
              "role": "user",
              "content": (
                  "Compare these A/B content variants. Score each 1-10 on emotional appeal, "
                  "clarity, urgency, and CTA strength. Identify strengths and weaknesses. "
                  "Recommend which to test first and state the hypothesis.\n\n"
                  f"{variant_texts}"
              ),
          },
      ],
      extra_body={"response_format": COMPARISON_SCHEMA},
  )

  comparison = json.loads(compare_resp.output[0].content[0].text)
  print(json.dumps(comparison, indent=2))
  ```

  ```javascript JavaScript theme={"dark"}
  const COMPARISON_SCHEMA = {
    type: "json_schema",
    json_schema: {
      name: "ab_comparison",
      strict: true,
      schema: {
        type: "object",
        properties: {
          variants: {
            type: "array",
            items: {
              type: "object",
              properties: {
                name: { type: "string" },
                tone_description: { type: "string" },
                emotional_appeal: { type: "number" },
                clarity: { type: "number" },
                urgency: { type: "number" },
                cta_strength: { type: "number" },
                strengths: { type: "array", items: { type: "string" } },
                weaknesses: { type: "array", items: { type: "string" } },
              },
              required: ["name", "tone_description", "emotional_appeal", "clarity", "urgency", "cta_strength", "strengths", "weaknesses"],
            },
          },
          recommendation: { type: "string" },
          test_hypothesis: { type: "string" },
        },
        required: ["variants", "recommendation", "test_hypothesis"],
      },
    },
  };

  const variantTexts = Object.entries(variants)
    .map(([name, data]) => `### ${name}\n${data.output.slice(0, 2000)}`)
    .join("\n\n---\n\n");

  const compareResp = await mavera.responses.create({
    model: "mavera-1",
    input: [
      {
        role: "user",
        content:
          "Compare these A/B content variants. Score each 1-10 on emotional appeal, " +
          "clarity, urgency, and CTA strength. Identify strengths and weaknesses. " +
          `Recommend which to test first and state the hypothesis.\n\n${variantTexts}`,
      },
    ],
    response_format: COMPARISON_SCHEMA,
  });

  const comparison = JSON.parse(compareResp.output[0].content[0].text);
  console.log(JSON.stringify(comparison, null, 2));
  ```
</CodeGroup>

### Example Comparison Output

```json theme={"dark"}
{
  "variants": [
    {
      "name": "bold_direct",
      "tone_description": "Punchy, urgent, challenge-oriented. Short sentences, imperative verbs.",
      "emotional_appeal": 8,
      "clarity": 9,
      "urgency": 9,
      "cta_strength": 8,
      "strengths": ["Creates FOMO", "Clear value proposition", "Action-oriented language"],
      "weaknesses": ["May feel aggressive to risk-averse buyers", "Less personal"]
    },
    {
      "name": "warm_storytelling",
      "tone_description": "Narrative-driven, empathetic, uses customer stories as proof.",
      "emotional_appeal": 9,
      "clarity": 7,
      "urgency": 5,
      "cta_strength": 6,
      "strengths": ["Builds trust", "Relatable examples", "Emotionally engaging"],
      "weaknesses": ["Lower urgency", "CTA feels soft", "Longer read time"]
    },
    {
      "name": "data_driven",
      "tone_description": "Metric-heavy, analytical, appeals to rational decision-making.",
      "emotional_appeal": 5,
      "clarity": 9,
      "urgency": 6,
      "cta_strength": 7,
      "strengths": ["Credible", "Appeals to analytical buyers", "Easy to skim"],
      "weaknesses": ["Low emotional engagement", "May feel dry"]
    }
  ],
  "recommendation": "Test bold_direct vs warm_storytelling first. They have the widest differentiation.",
  "test_hypothesis": "Bold/direct will drive higher click-through but warm/storytelling will drive higher conversion-to-paid."
}
```

***

## Variations

<AccordionGroup>
  <Accordion title="Voice × persona matrix">
    Combine both strategies: 2 voices × 2 personas = 4 variants. This lets you test voice and audience framing as independent variables in a 2×2 factorial design.
  </Accordion>

  <Accordion title="Focus Group pre-test">
    Before committing to a live test, run a Focus Group with your target persona to predict which variant will win. Ask "Which version would make you more likely to upgrade?" as a MULTIPLE\_CHOICE question.
  </Accordion>

  <Accordion title="Iterative refinement loop">
    Generate variants, compare, then use the comparison's weaknesses to refine the lower-scoring variant via Chat — "Rewrite to strengthen CTA while keeping the same tone."
  </Accordion>
</AccordionGroup>

***

## Credits Estimate

| Operation                                         | Typical Cost          | Notes                              |
| ------------------------------------------------- | --------------------- | ---------------------------------- |
| Brand voice creation (×3)                         | 150 credits           | 50 each; one-time cost — reuse IDs |
| Generation per variant                            | 15–30 credits         | Depends on content length          |
| Comparison analysis (Chat)                        | 1–5 credits           | Single call with response\_format  |
| **Total (3 voice variants, new voices)**          | **\~195–245 credits** | First run with voice creation      |
| **Total (3 voice variants, existing voices)**     | **\~45–95 credits**   | Subsequent runs                    |
| **Total (3 persona variants, existing personas)** | **\~5–20 credits**    | Chat calls only                    |

<Tip>
  Persona-based A/B is dramatically cheaper than voice-based A/B (no voice creation cost). Use personas when you want to vary the *audience framing*; use voices when you want to vary the *brand expression*.
</Tip>

***

## What's Next

<CardGroup cols={2}>
  <Card title="Brand Voice Content Library" icon="palette" href="/playbooks/brand-voice-content-library">
    Create a full content library from a single brand voice
  </Card>

  <Card title="Content Localization" icon="globe" href="/playbooks/content-localization">
    Adapt variants for different regional markets
  </Card>

  <Card title="Message Testing Matrix" icon="table-cells" href="/playbooks/message-testing-matrix">
    5 messages × 5 personas = 25 quantitative data points
  </Card>

  <Card title="Brand Voice" icon="bullhorn" href="/features/brand-voice">
    Create and manage brand voice profiles
  </Card>

  <Card title="Personas" icon="users" href="/features/personas">
    Pre-built and custom persona reference
  </Card>

  <Card title="Credits & Budget" icon="coins" href="/cookbooks/credits-budget-alerts">
    Track and manage credit usage
  </Card>
</CardGroup>
