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: BigCommerceGET /v3/catalog/products/{id}/reviews → Paginate across products → Batch into Mavera Chat (structured output) → Sentiment, themes, recommendations
Architecture
Code
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}")
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}`));
Example Output
{
"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
Reviews endpoint returns 404
Reviews endpoint returns 404
Not all products have reviews enabled. Filter with
reviews_count > 0 on the catalog products endpoint before fetching reviews.Rate limit 429 responses
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.