Fetch a single store by canonical id, with three opt-in expansions: chain online-shopping config, weekly hours, and SerpAPI / GPT place enrichment (rating, sentiment, popular times). The store-detail page primary endpoint.
/v1/stores/{store_id}Free?include=... to opt into the nested expansions you actually need.| Name | Type | Description |
|---|---|---|
store_idrequired | uuid | Canonical store id from GET /v1/stores. Soft-deleted dup ids resolve transparently to their canonical survivor. |
| Name | Type | Description |
|---|---|---|
include | string | Comma-separated opt-in fields: chain_online_config, hours, places_enrichment. Each adds a nested object on the response. Combine freely, e.g. ?include=chain_online_config,hours,places_enrichment. |
chain_online_configchain object with the same shape as Get a chain: search URL templates, login URL, native app URL schemes, add-to-cart detection pattern. Use this on a Store Detail screen so a single round-trip gets you everything needed for the "Shop Online" button.hourshours array: 0-7 rows per store with day_of_week (0 = Sunday), open_time, close_time in 24h format. A store closed on a given day omits that row entirely — never assume seven entries.places_enrichmentplaces object: Google rating, review count, weekly busyness histogram (popular_times), highlights, GPT-generated shopper sentiment with 5 grounded axes. Returns null for stores not yet seeded (most stores today — only the Brooklyn pilot is fully enriched). See Store sentiment for the full place-data spec.store_id_redirects and returns the surviving canonical row. Cache the returned id to avoid the redirect on subsequent calls.curl 'https://api.mainmarket.com/v1/stores/8c1a4d1e-30a7-4d92-9e1c-1cb43c6f2e10?include=hours,places_enrichment'{
"id": "8c1a4d1e-30a7-4d92-9e1c-1cb43c6f2e10",
"name": "Wegmans Brooklyn",
"display_name": "Wegmans Brooklyn",
"banner_name": "Wegmans",
"web_external_id": "59",
"chain_name": "Wegmans",
"chain_slug": "wegmans",
"chain_logo_url": "https://.../wegmans.png",
"address": "21 Flushing Ave",
"city": "Brooklyn",
"state": "NY",
"zip": "11205",
"lat": 40.6982,
"lng": -73.9772,
"metro": "nyc",
"borough": "brooklyn",
"format": "supermarket",
"has_pickup": true,
"has_delivery": true,
"is_active": true,
"hours": [
{ "day_of_week": 0, "open_time": "06:00", "close_time": "00:00" },
{ "day_of_week": 1, "open_time": "06:00", "close_time": "00:00" }
]
}Same StoreResponse shape as GET /v1/stores (single object, not wrapped). See List stores for the full field reference.
The "all-in-one" call powering a typical store-detail page. One free round-trip gets every piece of metadata you need to render headers, hours, deep-links, and sentiment.
import httpx
store_id = "8c1a4d1e-30a7-4d92-9e1c-1cb43c6f2e10"
r = httpx.get(
f"https://api.mainmarket.com/v1/stores/{store_id}",
params={"include": "chain_online_config,hours,places_enrichment"},
).json()
# Cache-busting check: returned id may differ if we resolved a stale dup id
if r["id"] != store_id:
print(f"Note: id was redeemed — update your cache: {store_id} → {r['id']}")
# Header
print(f"{r['name']} ({r['chain_name']})")
print(f"{r['address']}, {r['city']}, {r['state']} {r['zip']}")
# Hours
if r.get("hours"):
days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
for h in r["hours"]:
print(f" {days[h['day_of_week']]} {h['open_time']} - {h['close_time']}")
# Sentiment (only present for seeded stores)
if r.get("places", {}).get("sentiment"):
s = r["places"]["sentiment"]
print(f"\nSentiment: {s['score']}/100 — {s['label']}")
print(s["summary"])
# Deep link
chain = r.get("chain") or {}
if chain.get("online_shopping_enabled") and chain.get("storefront_url"):
deep_link = chain["storefront_url"].replace("{store_id}", r["web_external_id"])
print(f"\nShop Online: {deep_link}")On 2026-04-25 we collapsed ~3,000 duplicate store rows into their canonical survivors. Pre-existing client caches still hold the loser ids. This endpoint handles that gracefully:
GET /v1/stores/<loser_id>, the endpoint looks up store_id_redirects, resolves to the canonical id, and returns the canonical row. Returned id may differ from the requested id.response.id to the id you sent. When they differ, a redirect happened — update your local cache to the canonical id so subsequent calls skip the redirect (small but real perf win at scale).GET /v1/stores/{id}, /v1/stores/{id}/aisles, and /v1/stores/{id}/coupons resolve loser ids. Other endpoints (e.g. /v1/prices?store_id=...) 404 on a stale id. Use this endpoint as a one-time migration step.id may differ from the requested id; refresh your client cache when this happens.day_of_week is 0-indexed Sunday. 0 = Sunday, 6 = Saturday. A store closed on a given day omits that row entirely — do not assume seven entries.places is null for most stores. Only the 26-store Brooklyn pilot is fully seeded today. Production seed pending (~$150 SerpAPI cost for ~11.5k chain-active stores). Always null-check before rendering sentiment / popular times.GET /v1/stores/{id} returns exactly the same shape it did before the include token expanded — no new fields silently appearing in existing client responses.Store metadata changes infrequently. Responses send Cache-Control: public, max-age=300, stale-while-revalidate=600 — a 5-minute cache that's CDN-shareable. Browser and edge caches respect that automatically. The id-redirect path is also cached, so repeat calls with the same loser id are still fast.
places expansion (sentiment axes, popular times, highlights).| Name | Type | Description |
|---|---|---|
404 | Not Found | store_id is not a known canonical or redirect id. |
422 | Unprocessable Entity | store_id is not a valid UUID, or include= contains an unknown key. |
500 | Internal Server Error | Unexpected server fault. |