MainMarketAPI Reference
Get Access
Overview
  • Introduction
  • Authentication
  • Errors
Stores
  • List stores
  • Get a store
  • Store sentiment
  • Store coupons
  • Store aisles
Chains
  • List chains
  • Get a chain
  • Chain aisles
  • Resolve a list
Products
  • Catalog search
  • Get a product
  • Coupons for product
Prices
  • Search prices
  • Prices by UPC
  • Prices at a store
  • Cheapest nearby
Coupons
  • List coupons
  • Get a coupon
  • Coupon savings
Indices
  • Published indices
Discovery & meta
  • Discovery routes
  • OpenAPI spec
  • Agent skill spec
Overview
  • Introduction
  • Authentication
  • Errors
Stores
  • List stores
  • Get a store
  • Store sentiment
  • Store coupons
  • Store aisles
Chains
  • List chains
  • Get a chain
  • Chain aisles
  • Resolve a list
Products
  • Catalog search
  • Get a product
  • Coupons for product
Prices
  • Search prices
  • Prices by UPC
  • Prices at a store
  • Cheapest nearby
Coupons
  • List coupons
  • Get a coupon
  • Coupon savings
Indices
  • Published indices
Discovery & meta
  • Discovery routes
  • OpenAPI spec
  • Agent skill spec

Store sentiment & enrichment

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.

GET/v1/stores/{id}?include=places_enrichmentFree
ℹ
This isn't a separate endpoint
There's no dedicated route for sentiment — it's an opt-in expansion on the existing GET /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.

How it's sourced

Two independent passes feed the places object:

SerpAPI Google Maps scrape
One-time per store. Captures rating, review count, popular_times histogram, typical time spent, highlights, service options, and the 8 most relevant Google reviews. Google's official Places API doesn't expose popular_times or typical_time_spent, so SerpAPI is the only practical source for the busyness data.
GPT shopper-sentiment pass
Runs the SerpAPI extensions + reviews through a structured-output GPT prompt that scores the store on 5 fixed axes (service, selection, value, cleanliness, operations). Each axis carries both a 0-100 score and a grounded evidence quote. Composite score + qualitative label (Loved / Strong / Mixed / Frustrated) summarize the whole store.
⚠
Coverage today
Only the 26-store Brooklyn pilot is fully seeded right now. Every other store returns 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.

Request

Request
curl 'https://api.mainmarket.com/v1/stores/8c1a4d1e-30a7-4d92-9e1c-1cb43c6f2e10?include=places_enrichment' \
  -H "Authorization: Bearer mm_live_..."

Response

200 OK (abbreviated)json
{
  "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"
  }
}

places fields

NameTypeDescription
ratingnumber | null0.0–5.0, one decimal place. Sourced from Google rating.
review_countinteger | nullTotal Google reviews.
typical_time_spentstring | nullFree-text, e.g. "People typically spend 15-45 min here". Null when Google doesn't have it.
price_levelstring | null"$" / "$$" / "$$$".
highlightsstring[]Flat de-duped tag list flattened from SerpAPI extensions. First 3-5 are typically the most distinctive.
service_optionsobject | nullMap of { key: boolean }. Render only the truthy keys.
popular_timesobject | null{ current_day, graph }. graph has up to 7 keys (monday..sunday); each is a list of { time, busyness_score (0-100), label }.
review_topicsobject[]Optional { keyword, mentions } list. Empty array when Google doesn't return topic keywords.
sentimentobject | nullGPT structured-output sentiment. See "Sentiment shape" below.
enriched_atISO8601 string | nullWhen SerpAPI scraped the store. One-time seed; no monthly refresh.

Sentiment shape

NameTypeDescription
scoreinteger | null0-100 composite. Null if reviews are too sparse to score.
labelstring | nullHuman label derived from score: Loved (≥85), Strong (70-84), Mixed (50-69), Frustrated (<50).
summarystring | nullOne sentence under 200 chars, balanced (mentions strengths AND weaknesses if both present).
axes.service.{score, evidence}objectStaff friendliness, helpfulness, attentiveness.
axes.selection.{score, evidence}objectVariety, stock-outs, organic / specialty options.
axes.value.{score, evidence}objectPerceived pricing, deals, value-for-money.
axes.cleanliness.{score, evidence}objectStore condition, hygiene, freshness of perishables.
axes.operations.{score, evidence}objectCheckout speed, parking, layout, wait times.
ℹ
Per-axis null handling
Each axis returns { 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.

Popular times

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:

NameTypeDescription
timestringe.g. "9 AM", "3 PM". Always 12-hour format.
busyness_scoreinteger0-100. 0 means closed or no data — render as a tiny dot or skip the bar entirely, don't show a flat empty bar.
labelstring | nullTooltip 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.

ℹ
No live busyness
SerpAPI's 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.

UI rendering tips

  • Star + count: ★ {rating.toFixed(1)} ({review_count.toLocaleString()} reviews).
  • Highlights: render as a horizontal chip rail; first 3-5 chips are the most distinctive.
  • Sentiment label: color-code — Loved=green, Strong=blue, Mixed=yellow, Frustrated=red.
  • Per-axis bars: render only the 5 axes whose score is non-null; show evidence beneath each bar as a grounded quote.
  • Service options: map the truthy keys to icons client-side (delivery, in_store_pickup, curbside_pickup, etc.).

Errors

NameTypeDescription
404Not Foundstore_id is not a known canonical or redirect id.
401UnauthorizedMissing or invalid Authorization header.