Google Maps + LLM-derived enrichment for one store: star rating, review count, weekly busyness histogram, highlights, and a 5-axis shopper-sentiment score. Returned as the `places` object on the standard store endpoint when you opt in via the include token.
/v1/stores/{id}?include=places_enrichmentFreeGET /v1/stores and GET /v1/stores/{id} endpoints. Pass ?include=places_enrichment and the response gains a nested places object. Combine with other includes like ?include=places_enrichment,hours,chain_online_config for a Store Detail screen.Two independent passes feed the places object:
popular_times or typical_time_spent, so SerpAPI is the only practical source for the busyness data.score + qualitative label (Loved / Strong / Mixed / Frustrated) summarize the whole store.places: null until the production seed (~$150 one-time, ~11.5k chain-active stores) ships. Always null-check the parent places object and fall back to a default UI when it's null.curl 'https://api.mainmarket.com/v1/stores/8c1a4d1e-30a7-4d92-9e1c-1cb43c6f2e10?include=places_enrichment' \
-H "Authorization: Bearer mm_live_..."{
"id": "8c1a4d1e-30a7-4d92-9e1c-1cb43c6f2e10",
"name": "Trader Joe's #558",
"chain_slug": "trader-joes",
"address": "130 Court St",
"city": "Brooklyn",
"state": "NY",
"lat": 40.68958,
"lng": -73.99287,
"places": {
"rating": 4.6,
"review_count": 6314,
"typical_time_spent": "People typically spend 20 min here",
"price_level": "$$",
"highlights": [
"In-store pickup",
"In-store shopping",
"Onsite services",
"Great produce",
"Wheelchair accessible entrance",
"Bakery",
"Organic products"
],
"service_options": {
"in_store_pickup": true,
"in_store_shopping": true
},
"popular_times": {
"current_day": "wednesday",
"graph": {
"monday": [{ "time": "9 AM", "busyness_score": 23, "label": "Usually not too busy" }],
"tuesday": [{ "time": "9 AM", "busyness_score": 35, "label": "Usually not too busy" }],
"wednesday": [{ "time": "9 AM", "busyness_score": 41, "label": "Usually a little busy" }]
}
},
"review_topics": [
{ "keyword": "organic products", "mentions": 119 },
{ "keyword": "friendly staff", "mentions": 84 }
],
"sentiment": {
"score": 69,
"label": "Mixed",
"summary": "Trader Joe's #558 offers a wide selection and friendly service, but suffers from stock issues and a challenging layout with long lines and parking difficulties.",
"axes": {
"service": { "score": 85, "evidence": "Good service friendly" },
"selection": { "score": 70, "evidence": "immense variety of products" },
"value": { "score": 70, "evidence": "Good overall deals" },
"cleanliness": { "score": 30, "evidence": "mouse droppings on the chip shelf" },
"operations": { "score": 50, "evidence": "huge line that goes all around the store" }
}
},
"enriched_at": "2026-04-29T17:04:55Z"
}
}| Name | Type | Description |
|---|---|---|
rating | number | null | 0.0–5.0, one decimal place. Sourced from Google rating. |
review_count | integer | null | Total Google reviews. |
typical_time_spent | string | null | Free-text, e.g. "People typically spend 15-45 min here". Null when Google doesn't have it. |
price_level | string | null | "$" / "$$" / "$$$". |
highlights | string[] | Flat de-duped tag list flattened from SerpAPI extensions. First 3-5 are typically the most distinctive. |
service_options | object | null | Map of { key: boolean }. Render only the truthy keys. |
popular_times | object | null | { current_day, graph }. graph has up to 7 keys (monday..sunday); each is a list of { time, busyness_score (0-100), label }. |
review_topics | object[] | Optional { keyword, mentions } list. Empty array when Google doesn't return topic keywords. |
sentiment | object | null | GPT structured-output sentiment. See "Sentiment shape" below. |
enriched_at | ISO8601 string | null | When SerpAPI scraped the store. One-time seed; no monthly refresh. |
| Name | Type | Description |
|---|---|---|
score | integer | null | 0-100 composite. Null if reviews are too sparse to score. |
label | string | null | Human label derived from score: Loved (≥85), Strong (70-84), Mixed (50-69), Frustrated (<50). |
summary | string | null | One sentence under 200 chars, balanced (mentions strengths AND weaknesses if both present). |
axes.service.{score, evidence} | object | Staff friendliness, helpfulness, attentiveness. |
axes.selection.{score, evidence} | object | Variety, stock-outs, organic / specialty options. |
axes.value.{score, evidence} | object | Perceived pricing, deals, value-for-money. |
axes.cleanliness.{score, evidence} | object | Store condition, hygiene, freshness of perishables. |
axes.operations.{score, evidence} | object | Checkout speed, parking, layout, wait times. |
{ score, evidence } where both fields can be null when reviews don't mention that axis (e.g. cleanliness rarely comes up for a small bodega). Skip the bar entirely when score === null; show score-only when evidence === null.The graph object has up to 7 keys (one per day of the week SerpAPI returned data for). Each value is an array of hourly buckets:
| Name | Type | Description |
|---|---|---|
time | string | e.g. "9 AM", "3 PM". Always 12-hour format. |
busyness_score | integer | 0-100. 0 means closed or no data — render as a tiny dot or skip the bar entirely, don't show a flat empty bar. |
label | string | null | Tooltip text, e.g. "Usually a little busy", "Usually as busy as it gets". Null when Google didn't return one. |
current_day tells the client which day tab to default-select. It's computed from the server's UTC date — non-US clients may want to override it locally with the user's timezone.
live_hash.info ("Busier than usual") is not exposed today — only the typical weekly histogram. Live busyness can be added as a separate fetch path on demand if customers ask.★ {rating.toFixed(1)} ({review_count.toLocaleString()} reviews).score is non-null; show evidence beneath each bar as a grounded quote.| Name | Type | Description |
|---|---|---|
404 | Not Found | store_id is not a known canonical or redirect id. |
401 | Unauthorized | Missing or invalid Authorization header. |