Scenario
Each location has distinct review themes — downtown customers love the espresso, suburban customers love the parking. You extract themes per location and generate location-specific marketing content that highlights what each location does best, in the language customers already use. Flow: Per-location review themes → MaveraPOST /mave/chat (theme extraction) → POST /generations (location content) → Per-location marketing assets
Code
import os, requests, time
GOOG = os.environ["GOOGLE_ACCESS_TOKEN"]
ACCT = os.environ["GOOGLE_ACCOUNT_ID"]
MV = os.environ["MAVERA_API_KEY"]
GB_BASE = "https://mybusiness.googleapis.com/v4"
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
GB_H = {"Authorization": f"Bearer {GOOG}"}
STAR_MAP = {"ONE": 1, "TWO": 2, "THREE": 3, "FOUR": 4, "FIVE": 5}
locations = requests.get(f"{GB_BASE}/{ACCT}/locations",
headers=GB_H, params={"pageSize": 50}).json().get("locations", [])
for loc in locations[:5]:
loc_id = loc.get("name", "")
loc_name = loc.get("locationName", "Unknown")
city = loc.get("address", {}).get("locality", "Unknown")
r = requests.get(f"{GB_BASE}/{loc_id}/reviews",
headers=GB_H, params={"pageSize": 50})
if r.status_code != 200:
continue
reviews = r.json().get("reviews", [])
positive = [rev.get("comment", "")[:250] for rev in reviews
if STAR_MAP.get(rev.get("starRating"), 3) >= 4 and rev.get("comment")]
negative = [rev.get("comment", "")[:250] for rev in reviews
if STAR_MAP.get(rev.get("starRating"), 3) <= 2 and rev.get("comment")]
# 1. Theme extraction
themes = requests.post("https://app.mavera.io/api/v1/mave/chat", headers=MV_H,
json={"message": f"""Extract themes from reviews for {loc_name} ({city}).
POSITIVE ({len(positive)} reviews):
{chr(10).join(positive[:10])}
NEGATIVE ({len(negative)} reviews):
{chr(10).join(negative[:10])}
Return: top 5 positive themes, top 3 negative themes, each with frequency and representative quote."""}).json()
print(f"\n{'='*50}")
print(f"THEMES: {loc_name} ({city})")
print(themes.get("content", "")[:500])
# 2. Generate location-specific content
gen = requests.post("https://app.mavera.io/api/v1/generations", headers=MV_H,
json={"prompt": (
f"Generate location-specific marketing content for {loc_name} in {city}.\n\n"
f"Themes from customer reviews:\n{themes.get('content','')[:500]}\n\n"
f"Create:\n"
f"1. Google Business Profile post (150 words, highlight top strength)\n"
f"2. Local Instagram caption (casual, with hashtags)\n"
f"3. Location-specific tagline (10 words max)\n"
f"4. Local SEO paragraph for website (100 words, include city name naturally)"
)}).json()
print(f"\nCONTENT:")
print(gen.get("output", gen.get("content", gen.get("text", "")))[:600])
time.sleep(1)
const GOOG = process.env.GOOGLE_ACCESS_TOKEN;
const ACCT = process.env.GOOGLE_ACCOUNT_ID;
const MV = process.env.MAVERA_API_KEY;
const GB_BASE = "https://mybusiness.googleapis.com/v4";
const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const GB_H = { Authorization: `Bearer ${GOOG}` };
const STAR_MAP = { ONE: 1, TWO: 2, THREE: 3, FOUR: 4, FIVE: 5 };
const locations = (await fetch(`${GB_BASE}/${ACCT}/locations?pageSize=50`, { headers: GB_H })
.then((r) => r.json())).locations || [];
for (const loc of locations.slice(0, 5)) {
const locId = loc.name || "";
const locName = loc.locationName || "Unknown";
const city = loc.address?.locality || "Unknown";
const res = await fetch(`${GB_BASE}/${locId}/reviews?pageSize=50`, { headers: GB_H });
if (!res.ok) continue;
const reviews = (await res.json()).reviews || [];
const positive = reviews.filter((r) => (STAR_MAP[r.starRating] || 3) >= 4 && r.comment)
.map((r) => r.comment.slice(0, 250));
const negative = reviews.filter((r) => (STAR_MAP[r.starRating] || 3) <= 2 && r.comment)
.map((r) => r.comment.slice(0, 250));
// 1. Themes
const themes = await fetch("https://app.mavera.io/api/v1/mave/chat", {
method: "POST", headers: MV_H,
body: JSON.stringify({
message: `Themes for ${locName} (${city}):\n\nPOSITIVE:\n${positive.slice(0, 10).join("\n")}\n\nNEGATIVE:\n${negative.slice(0, 10).join("\n")}`,
}),
}).then((r) => r.json());
console.log(`\n${"=".repeat(50)}\nTHEMES: ${locName} (${city})`);
console.log((themes.content || "").slice(0, 500));
// 2. Content
const gen = await fetch("https://app.mavera.io/api/v1/generations", {
method: "POST", headers: MV_H,
body: JSON.stringify({
prompt: `Marketing for ${locName} (${city}).\n\nThemes: ${(themes.content || "").slice(0, 500)}\n\nCreate: 1) GBP post (150 words) 2) Instagram caption 3) Tagline (10 words) 4) Local SEO paragraph (100 words)`,
}),
}).then((r) => r.json());
console.log("\nCONTENT:");
console.log((gen.output || gen.content || gen.text || "").slice(0, 600));
await new Promise((r) => setTimeout(r, 1000));
}
Example Output
==================================================
THEMES: Downtown Café (Austin)
Positive: Espresso quality (18x), Fast service (14x), Staff friendliness (11x)
Negative: Crowding at noon (6x), No outdoor seating (4x)
CONTENT:
## Google Business Profile Post
The best espresso in downtown Austin — and your neighbors agree. Our baristas
pull 200+ shots daily, each one dialed in before the morning rush. Stop by
for your 11am pick-me-up. Lines move fast (we've heard you on the wait times).
## Instagram Caption
That 3pm espresso hit different when it's from downtown 🫠☕
Come see why Austin keeps coming back. Open 6am-8pm, every day.
#AustinCoffee #DowntownATX #EspressoLovers #ATXEats
## Tagline
"Downtown Austin's fastest espresso. No compromises."
## Local SEO
Looking for the best coffee in downtown Austin? Our Downtown Café
serves handcrafted espresso, cold brew, and pastries steps from
Congress Avenue. Austin locals rate us 4.6/5 on Google with 234 reviews.
Error Handling
Locations with few reviews
Locations with few reviews
Locations with fewer than 10 reviews produce weak themes. Skip or combine with nearby locations for analysis.
Multi-language reviews
Multi-language reviews
In tourist-heavy locations, reviews come in multiple languages. Pass
?language=en to filter, or let Mave handle mixed-language input.