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

# Review Text → Brand Voice

> Build a Mavera Brand Voice profile from positive Trustpilot review language

## Scenario

Your best customers describe your product in their own words on Trustpilot — and those words are often better than anything your marketing team writes. You pull 4-5 star reviews, extract the language patterns, and feed them into Mavera's Brand Voice engine. The result is a voice profile that mirrors how satisfied customers actually talk about you.

**Flow:** Trustpilot `GET /business-units/{id}/reviews` (stars 4-5) → Extract review text → Mavera `POST /brand-voices` → Customer-grounded voice profile

## Architecture

```mermaid theme={"dark"}
flowchart LR
    A["Trustpilot GET /reviews (4-5 stars)"] --> B["Aggregate positive review text"]
    B --> C["POST /api/v1/brand-voices"]
    C --> D["Brand Voice profile + test generation"]
```

## Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests, time
  from openai import OpenAI

  TP_KEY = os.environ["TRUSTPILOT_API_KEY"]
  MV = os.environ["MAVERA_API_KEY"]
  BU_ID = os.environ["TRUSTPILOT_BU_ID"]
  TP_BASE = "https://api.trustpilot.com/v1"
  MV_BASE = "https://app.mavera.io/api/v1"
  MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

  # 1. Pull 4-5 star reviews
  reviews = []
  for stars in [5, 4]:
      page = 1
      while len(reviews) < 100:
          r = requests.get(f"{TP_BASE}/business-units/{BU_ID}/reviews",
              params={"apikey": TP_KEY, "stars": stars, "perPage": 50, "page": page,
                       "orderBy": "createdat.desc"})
          if r.status_code == 429:
              time.sleep(2)
              continue
          r.raise_for_status()
          data = r.json()
          batch = data.get("reviews", [])
          if not batch:
              break
          reviews.extend(batch)
          page += 1
          time.sleep(0.2)

  # 2. Extract review text
  samples = []
  for rev in reviews:
      title = rev.get("title", "")
      text = rev.get("text", "")
      stars = rev.get("stars", 0)
      if text and len(text) > 50:
          samples.append(f"[{stars}★] {title}\n{text}")

  # 3. Create Brand Voice
  combined = "\n\n---\n\n".join(samples[:20])

  bv = requests.post(f"{MV_BASE}/brand-voices", headers=MV_H, json={
      "name": "Trustpilot Customer Voice",
      "samples": [combined],
  }).json()
  print(f"Brand Voice: {bv['id']}")

  # 4. Wait for processing
  time.sleep(3)
  detail = requests.get(f"{MV_BASE}/brand-voices/{bv['id']}", headers=MV_H).json()
  print(f"Status: {detail.get('status', 'unknown')}")

  # 5. Test generation
  mavera = OpenAI(api_key=MV, base_url=MV_BASE)
  test = mavera.responses.create(model="mavera-1",
      input=[{"role": "user", "content": "Write a 100-word homepage hero paragraph."}],
      extra_body={"brand_voice_id": bv["id"]})
  print(f"\n{test.output[0].content[0].text}")
  ```

  ```javascript JavaScript theme={"dark"}
  const OpenAI = require("openai").default;
  const TP_KEY = process.env.TRUSTPILOT_API_KEY;
  const MV = process.env.MAVERA_API_KEY;
  const BU_ID = process.env.TRUSTPILOT_BU_ID;
  const TP_BASE = "https://api.trustpilot.com/v1";
  const MV_BASE = "https://app.mavera.io/api/v1";
  const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };

  // 1. Pull 4-5 star reviews
  const reviews = [];
  for (const stars of [5, 4]) {
    let page = 1;
    while (reviews.length < 100) {
      const res = await fetch(
        `${TP_BASE}/business-units/${BU_ID}/reviews?apikey=${TP_KEY}&stars=${stars}&perPage=50&page=${page}&orderBy=createdat.desc`
      );
      if (res.status === 429) { await new Promise((r) => setTimeout(r, 2000)); continue; }
      if (!res.ok) throw new Error(`Trustpilot ${res.status}`);
      const batch = (await res.json()).reviews || [];
      if (!batch.length) break;
      reviews.push(...batch);
      page++;
      await new Promise((r) => setTimeout(r, 200));
    }
  }

  // 2. Extract text
  const samples = reviews
    .filter((r) => r.text && r.text.length > 50)
    .map((r) => `[${r.stars}★] ${r.title || ""}\n${r.text}`)
    .slice(0, 20);

  // 3. Brand Voice
  const bv = await fetch(`${MV_BASE}/brand-voices`, {
    method: "POST", headers: MV_H,
    body: JSON.stringify({ name: "Trustpilot Customer Voice", samples: [samples.join("\n\n---\n\n")] }),
  }).then((r) => r.json());
  console.log(`Brand Voice: ${bv.id}`);

  // 4. Test
  await new Promise((r) => setTimeout(r, 3000));
  const mavera = new OpenAI({ apiKey: MV, baseURL: MV_BASE });
  const test = await mavera.responses.create({
    model: "mavera-1",
    input: [{ role: "user", content: "Write a 100-word homepage hero paragraph." }],
    extra_body: { brand_voice_id: bv.id },
  });
  console.log(test.output[0].content[0].text);
  ```
</CodeGroup>

## Example Output

```text theme={"dark"}
Brand Voice: bv_tp_cust_4k7m (status: ready)

We built this because we were tired of guessing. Tired of launching campaigns
and hoping the message landed. Our customers — from two-person startups to
Fortune 500 marketing teams — tell us the same thing: "It just works."
Set up takes minutes. Your first insight comes before lunch. And every piece
of content you create from here carries the voice your audience already trusts.
```

## Error Handling

<AccordionGroup>
  <Accordion title="API key vs OAuth">Public review endpoints use `?apikey={key}`. Business/private endpoints (reply, flag) require OAuth 2.0. Use OAuth for Job 5 (review responses).</Accordion>
  <Accordion title="Star filter">Pass `stars` as a query param. For multiple stars, make separate requests (Trustpilot doesn't support `stars=4,5` in a single call).</Accordion>
  <Accordion title="Short reviews add noise">One-line reviews like "Great service!" don't contribute meaningful voice data. Filter reviews under 50 characters.</Accordion>
</AccordionGroup>
