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

# Product Review Sentiment → Brand Health

> Analyze BigCommerce product reviews with Mavera structured output for sentiment scores and brand health metrics

### Scenario

Your BigCommerce store has hundreds of product reviews across your catalog. You pull reviews for your top products, batch them through Mavera Chat with a customer persona and structured JSON output, and get sentiment scores, recurring themes, and actionable recommendations — ready for a brand health dashboard.

**Flow:** BigCommerce `GET /v3/catalog/products/{id}/reviews` → Paginate across products → Batch into Mavera Chat (structured output) → Sentiment, themes, recommendations

### Architecture

```mermaid theme={"dark"}
flowchart LR
A["GET /products"] --> B["GET /products/{id}/reviews"] --> C["responses.create with json_schema"] --> D["Brand Health Dashboard"]
```

### Code

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

  STORE = os.environ["BIGCOMMERCE_STORE_HASH"]
  BC_TOKEN = os.environ["BIGCOMMERCE_ACCESS_TOKEN"]
  MV = os.environ["MAVERA_API_KEY"]
  BC = f"https://api.bigcommerce.com/stores/{STORE}/v3"
  BC_HEADERS = {"X-Auth-Token": BC_TOKEN, "Content-Type": "application/json", "Accept": "application/json"}

  products = requests.get(f"{BC}/catalog/products",
      headers=BC_HEADERS,
      params={"sort": "total_sold", "direction": "desc", "limit": 10},
  ).json().get("data", [])

  all_reviews = []
  for prod in products:
      page = 1
      while True:
          r = requests.get(f"{BC}/catalog/products/{prod['id']}/reviews",
              headers=BC_HEADERS, params={"page": page, "limit": 50})
          if r.status_code == 429:
              time.sleep(2); continue
          r.raise_for_status()
          batch = r.json().get("data", [])
          for rev in batch:
              all_reviews.append({
                  "product": prod["name"], "rating": rev["rating"],
                  "title": rev.get("title", ""), "text": rev.get("text", "")[:300],
              })
          if len(batch) < 50: break
          page += 1
          time.sleep(0.2)

  mavera = OpenAI(api_key=MV, base_url="https://app.mavera.io/api/v1")
  schema = {"type": "json_schema", "json_schema": {"name": "brand_health", "strict": True, "schema": {
      "type": "object", "required": ["overall_sentiment", "themes", "recommendations"],
      "properties": {
          "overall_sentiment": {"type": "number"},
          "themes": {"type": "array", "items": {"type": "object",
              "required": ["theme", "frequency", "avg_sentiment", "sample_quote"],
              "properties": {"theme": {"type": "string"}, "frequency": {"type": "number"},
                  "avg_sentiment": {"type": "number"}, "sample_quote": {"type": "string"}}}},
          "recommendations": {"type": "array", "items": {"type": "string"}},
      }}}}

  review_block = "\n".join(f"[{r['rating']}/5] {r['product']}: {r['title']} — {r['text']}"
      for r in all_reviews[:80])

  result = mavera.responses.create(model="mavera-1",
      input=[{"role": "user", "content": f"Analyze {len(all_reviews)} product reviews for brand health.\n\n{review_block}"}],
      extra_body={"persona_id": os.environ.get("CUSTOMER_PERSONA_ID", ""), "response_format": schema})

  health = json.loads(result.output[0].content[0].text)
  print(f"Overall sentiment: {health['overall_sentiment']}/10")
  for t in health["themes"][:5]:
      print(f"  {t['theme']} (n={t['frequency']}, sentiment={t['avg_sentiment']})")
  for rec in health["recommendations"]:
      print(f"  → {rec}")
  ```

  ```javascript JavaScript theme={"dark"}
  import OpenAI from "openai";

  const STORE = process.env.BIGCOMMERCE_STORE_HASH;
  const BC_TOKEN = process.env.BIGCOMMERCE_ACCESS_TOKEN;
  const MV = process.env.MAVERA_API_KEY;
  const BC = `https://api.bigcommerce.com/stores/${STORE}/v3`;
  const bcHeaders = { "X-Auth-Token": BC_TOKEN, "Content-Type": "application/json", Accept: "application/json" };

  const products = await fetch(`${BC}/catalog/products?sort=total_sold&direction=desc&limit=10`,
    { headers: bcHeaders }).then(r => r.json()).then(d => d.data || []);

  const allReviews = [];
  for (const prod of products) {
    let page = 1;
    while (true) {
      const res = await fetch(`${BC}/catalog/products/${prod.id}/reviews?page=${page}&limit=50`,
        { headers: bcHeaders });
      if (res.status === 429) { await new Promise(r => setTimeout(r, 2000)); continue; }
      const batch = (await res.json()).data || [];
      for (const rev of batch) {
        allReviews.push({
          product: prod.name, rating: rev.rating,
          title: rev.title || "", text: (rev.text || "").slice(0, 300),
        });
      }
      if (batch.length < 50) break;
      page++;
      await new Promise(r => setTimeout(r, 200));
    }
  }

  const mavera = new OpenAI({ apiKey: MV, baseURL: "https://app.mavera.io/api/v1" });
  const schema = { type: "json_schema", json_schema: { name: "brand_health", strict: true, schema: {
    type: "object", required: ["overall_sentiment", "themes", "recommendations"],
    properties: {
      overall_sentiment: { type: "number" },
      themes: { type: "array", items: { type: "object",
        required: ["theme", "frequency", "avg_sentiment", "sample_quote"],
        properties: { theme: { type: "string" }, frequency: { type: "number" },
          avg_sentiment: { type: "number" }, sample_quote: { type: "string" } } } },
      recommendations: { type: "array", items: { type: "string" } },
    } } } };

  const reviewBlock = allReviews.slice(0, 80)
    .map(r => `[${r.rating}/5] ${r.product}: ${r.title} — ${r.text}`).join("\n");

  const result = await mavera.responses.create({
    model: "mavera-1",
    input: [{ role: "user", content: `Analyze ${allReviews.length} product reviews for brand health.\n\n${reviewBlock}` }],
    extra_body: { persona_id: process.env.CUSTOMER_PERSONA_ID || "", response_format: schema },
  });

  const health = JSON.parse(result.output[0].content[0].text);
  console.log(`Overall sentiment: ${health.overall_sentiment}/10`);
  health.themes.slice(0, 5).forEach(t =>
    console.log(`  ${t.theme} (n=${t.frequency}, sentiment=${t.avg_sentiment})`));
  health.recommendations.forEach(rec => console.log(`  → ${rec}`));
  ```
</CodeGroup>

### Example Output

```json theme={"dark"}
{
  "overall_sentiment": 7.4,
  "themes": [
    { "theme": "Product quality", "frequency": 38, "avg_sentiment": 8.1, "sample_quote": "Build quality exceeded expectations at this price point" },
    { "theme": "Shipping speed", "frequency": 24, "avg_sentiment": 6.2, "sample_quote": "Took 12 days to arrive, expected faster for the premium tier" },
    { "theme": "Sizing accuracy", "frequency": 19, "avg_sentiment": 5.8, "sample_quote": "Runs a full size small — had to exchange twice" },
    { "theme": "Customer support", "frequency": 14, "avg_sentiment": 8.6, "sample_quote": "Support team resolved my issue within hours" }
  ],
  "recommendations": [
    "Add detailed sizing guide with measurements to product pages — sizing complaints drive 30% of negative reviews",
    "Highlight shipping timelines on checkout page to set expectations",
    "Feature customer support responsiveness in marketing — 8.6 avg sentiment is a differentiator"
  ]
}
```

### Error Handling

<AccordionGroup>
  <Accordion title="Reviews endpoint returns 404">Not all products have reviews enabled. Filter with `reviews_count > 0` on the catalog products endpoint before fetching reviews.</Accordion>
  <Accordion title="Rate limit 429 responses">BigCommerce returns `X-Rate-Limit-Time-Reset-Ms` in response headers. Use this for precise retry timing instead of fixed sleeps.</Accordion>
</AccordionGroup>
