Scenario
Your G2 reviews contain the exact language your customers use to describe what they love and hate about your product. These phrases are gold for brand voice — “love” phrases become preferred terms; “hate” phrases become terms to avoid. You extract these from reviews and feed them into Mavera’s Brand Voice engine with explicit preferred and avoid term lists. Flow: G2GET /survey-responses (your product) → Extract love/hate phrases → Mavera POST /brand-voices (with preferred_terms + avoid_terms)
Architecture
Code
import os, requests, time, re
from collections import Counter
G2 = os.environ["G2_API_KEY"]
MV = os.environ["MAVERA_API_KEY"]
G2_BASE = "https://data.g2.com/api/v1"
G2_H = {"Authorization": f"Token token={G2}", "Content-Type": "application/vnd.api+json"}
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
# 1. Pull own product reviews
reviews = []
page = 1
while len(reviews) < 200:
r = requests.get(f"{G2_BASE}/survey-responses",
headers=G2_H,
params={"page[size]": 50, "page[number]": page})
if r.status_code == 429:
time.sleep(1)
continue
r.raise_for_status()
data = r.json().get("data", [])
if not data:
break
reviews.extend(data)
page += 1
time.sleep(0.1)
# 2. Extract love and hate phrases
love_texts = []
hate_texts = []
for rev in reviews:
attrs = rev.get("attributes", {})
for key, val in attrs.get("comment_answers", {}).items():
text = val if isinstance(val, str) else val.get("text", "")
if not text.strip():
continue
k = key.lower()
if "love" in k or "best" in k or "like most" in k:
love_texts.append(text)
elif "dislike" in k or "hate" in k or "don't like" in k or "cons" in k:
hate_texts.append(text)
# 3. Extract key phrases
def extract_phrases(texts, min_count=2):
words = Counter()
bigrams = Counter()
for t in texts:
clean = re.sub(r'[^\w\s]', '', t.lower())
tokens = clean.split()
words.update(tokens)
for i in range(len(tokens) - 1):
bigrams[f"{tokens[i]} {tokens[i+1]}"] += 1
stop = {"the", "a", "an", "is", "it", "to", "and", "of", "in", "for", "that", "this", "with", "on", "i", "we", "our"}
phrases = [(p, c) for p, c in bigrams.items() if c >= min_count and not all(w in stop for w in p.split())]
return sorted(phrases, key=lambda x: -x[1])[:20]
love_phrases = extract_phrases(love_texts)
hate_phrases = extract_phrases(hate_texts)
preferred_terms = [p for p, _ in love_phrases[:15]]
avoid_terms = [p for p, _ in hate_phrases[:15]]
# 4. Create Brand Voice
love_samples = "\n\n---\n\n".join(love_texts[:15])
bv = requests.post(f"{MV_BASE}/brand-voices",
headers=MV_H,
json={
"name": "G2 Customer Voice",
"samples": [love_samples],
"preferred_terms": preferred_terms,
"avoid_terms": avoid_terms,
}).json()
print(f"Brand Voice: {bv['id']}")
print(f"\nPreferred terms ({len(preferred_terms)}):")
for p, c in love_phrases[:10]:
print(f" ✓ '{p}' ({c}x)")
print(f"\nAvoid terms ({len(avoid_terms)}):")
for p, c in hate_phrases[:10]:
print(f" ✗ '{p}' ({c}x)")
# 5. Test with a generation
from openai import OpenAI
mavera = OpenAI(api_key=MV, base_url=MV_BASE)
test = mavera.responses.create(model="mavera-1",
input=[{"role": "user", "content": "Write a 100-word product description for our landing page."}],
extra_body={"brand_voice_id": bv["id"]})
print(f"\n--- Test Generation ---\n{test.output[0].content[0].text}")
const G2 = process.env.G2_API_KEY;
const MV = process.env.MAVERA_API_KEY;
const G2_BASE = "https://data.g2.com/api/v1";
const MV_BASE = "https://app.mavera.io/api/v1";
const G2_H = { Authorization: `Token token=${G2}`, "Content-Type": "application/vnd.api+json" };
const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const OpenAI = require("openai").default;
// 1. Pull reviews
const reviews = [];
let page = 1;
while (reviews.length < 200) {
const res = await fetch(`${G2_BASE}/survey-responses?page[size]=50&page[number]=${page}`, { headers: G2_H });
if (res.status === 429) { await new Promise((r) => setTimeout(r, 1000)); continue; }
const data = (await res.json()).data || [];
if (!data.length) break;
reviews.push(...data);
page++;
await new Promise((r) => setTimeout(r, 100));
}
// 2. Extract love/hate
const loveTexts = [], hateTexts = [];
for (const rev of reviews) {
for (const [key, val] of Object.entries(rev.attributes?.comment_answers || {})) {
const text = typeof val === "string" ? val : val?.text || "";
if (!text.trim()) continue;
const k = key.toLowerCase();
if (k.includes("love") || k.includes("best")) loveTexts.push(text);
else if (k.includes("dislike") || k.includes("hate") || k.includes("cons")) hateTexts.push(text);
}
}
// 3. Extract phrases
function extractPhrases(texts, minCount = 2) {
const bigrams = {};
const stop = new Set(["the", "a", "an", "is", "it", "to", "and", "of", "in", "for", "that", "this", "with", "on", "i", "we"]);
for (const t of texts) {
const tokens = t.toLowerCase().replace(/[^\w\s]/g, "").split(/\s+/);
for (let i = 0; i < tokens.length - 1; i++) {
const bg = `${tokens[i]} ${tokens[i + 1]}`;
bigrams[bg] = (bigrams[bg] || 0) + 1;
}
}
return Object.entries(bigrams)
.filter(([p, c]) => c >= minCount && !p.split(" ").every((w) => stop.has(w)))
.sort(([, a], [, b]) => b - a).slice(0, 20);
}
const lovePhrases = extractPhrases(loveTexts);
const hatePhrases = extractPhrases(hateTexts);
// 4. Brand Voice
const bv = await fetch(`${MV_BASE}/brand-voices`, {
method: "POST", headers: MV_H,
body: JSON.stringify({
name: "G2 Customer Voice",
samples: [loveTexts.slice(0, 15).join("\n\n---\n\n")],
preferred_terms: lovePhrases.slice(0, 15).map(([p]) => p),
avoid_terms: hatePhrases.slice(0, 15).map(([p]) => p),
}),
}).then((r) => r.json());
console.log(`Brand Voice: ${bv.id}`);
lovePhrases.slice(0, 10).forEach(([p, c]) => console.log(` ✓ '${p}' (${c}x)`));
hatePhrases.slice(0, 10).forEach(([p, c]) => console.log(` ✗ '${p}' (${c}x)`));
// 5. Test generation
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 landing page description." }],
extra_body: { brand_voice_id: bv.id },
});
console.log(`\n--- Test ---\n${test.output[0].content[0].text}`);
Example Output
Brand Voice: bv_g2_voice_8m2
Preferred terms (15):
✓ 'easy use' (42x)
✓ 'customer support' (31x)
✓ 'saves time' (28x)
✓ 'intuitive interface' (19x)
✓ 'game changer' (14x)
Avoid terms (15):
✗ 'steep learning' (18x)
✗ 'slow loading' (12x)
✗ 'limited integrations' (9x)
✗ 'pricing confusing' (7x)
✗ 'missing features' (6x)
--- Test Generation ---
Stop guessing what your customers think — know it. Our platform turns raw
feedback into tested messaging in minutes, not months. The interface is
intuitive enough for marketers and powerful enough for researchers. Set up
your first focus group before lunch; have persona-validated copy by EOD.
Error Handling
Insufficient review volume
Insufficient review volume
Phrase extraction needs 50+ reviews for meaningful bigrams. With fewer, use individual word frequency or pass raw text directly to Brand Voice without phrase extraction.
Multilingual reviews
Multilingual reviews
G2 reviews may be in multiple languages. Filter by
attributes.language or use Mave to translate before phrase extraction.