Frequency-ranked aisles for one store. Cascades store-specific → chain-level → generic when fewer than 30% of products at the store carry an aisle tag.
/v1/stores/{store_id}/aisles$0.01| Name | Type | Description |
|---|---|---|
store_idrequired | uuid | Canonical store id. |
| Name | Type | Description |
|---|---|---|
limit | integerdefault: 20 | Max aisles to return. Range 1–100. |
curl 'https://api.mainmarket.com/v1/stores/8c1a4d1e-30a7-4d92-9e1c-1cb43c6f2e10/aisles?limit=20'{
"store_id": "8c1a4d1e-30a7-4d92-9e1c-1cb43c6f2e10",
"source": "store",
"coverage": 0.78,
"aisles": [
"Aisle 1",
"Aisle 2",
"Aisle 3",
"Produce",
"Bakery",
"Deli",
"Meat",
"Dairy",
"Frozen"
],
"aisle_counts": {
"Aisle 1": 312,
"Aisle 2": 287,
"Produce": 185
}
}| Name | Type | Description |
|---|---|---|
store_id | uuid | Echo of the requested store id. |
source | string | Where the aisle list came from: store (this store has its own coverage), chain (aggregated from sibling stores in the same chain), or generic (PoS-noise-filtered fallback list). |
coverage | number | Fraction of store_product_prices rows at the source level that carry a non-null aisle (0.0–1.0). |
aisles | string[] | Normalized aisle names ordered by frequency descending. |
aisle_counts | object | Map of aisle name → product count for diagnostics. Top 50 entries only. |
"Aisle " prefix is stripped then re-prefixed when the remainder is purely numeric (so "Aisle 12", "12", and "AISLE 12, Shelf 3" all collapse to "Aisle 12"). PoS-noise rows (self-checkout, register candy, restrooms, etc.) are dropped.The endpoint runs three resolution attempts in order and returns the first one that produces a usable list:
store_product_prices row at this store, run the normalization pipeline, count survivors. If the store has ≥30% coverage (i.e. at least 30% of its priced products carry a non-null aisle after normalization), return the per-store ranking. This is the gold standard — true to what's actually on this store's shelves.aisles array with coverage: 0.0. Means we don't have aisle metadata for this chain at all (typically because the chain only ships flyer prices without PDP-level aisle info). Show an "aisle data not available" affordance in your UI.The standard shopping-app pattern. Pull aisles for the store, build a tab/picker UI, then load prices grouped by aisle on demand.
import httpx
store_id = "8c1a4d1e-30a7-4d92-9e1c-1cb43c6f2e10"
# Step 1: get the aisle vocabulary
aisles_resp = httpx.get(
f"https://api.mainmarket.com/v1/stores/{store_id}/aisles",
params={"limit": 30},
headers={"Authorization": "Bearer mm_live_..."},
).json()
# Step 2: gauge confidence and adapt UI
if aisles_resp["coverage"] < 0.2:
print("Store doesn't have aisle data — hide the picker")
elif aisles_resp["source"] != "store":
print(f"Using chain-level fallback (coverage {aisles_resp['coverage']:.0%})")
print("\nAisles:")
for name in aisles_resp["aisles"]:
count = aisles_resp["aisle_counts"].get(name, "?")
print(f" {name} ({count} products)")The aisles are most useful when joined to actual price rows. Pass ?normalize_aisles=true on /v1/prices and the aisle field comes back in the same vocabulary this endpoint returns — so you can sort a basket by aisle order without doing string mapping client-side.
import httpx
store_id = "8c1a4d1e-30a7-4d92-9e1c-1cb43c6f2e10"
basket = ["9d4e...", "a1b2...", "c3d4..."]
# Step 1: aisle order
aisles = httpx.get(
f"https://api.mainmarket.com/v1/stores/{store_id}/aisles",
params={"limit": 50},
headers={"Authorization": "Bearer mm_live_..."},
).json()
order = {name: i for i, name in enumerate(aisles["aisles"])}
# Step 2: prices, normalized to the same vocabulary
prices = httpx.get(
"https://api.mainmarket.com/v1/prices",
params={"store_id": store_id, "product_ids": ",".join(basket), "normalize_aisles": True},
headers={"Authorization": "Bearer mm_live_..."},
).json()
# Step 3: sort by walk order
prices["results"].sort(key=lambda r: order.get(r.get("aisle"), len(order)))
for row in prices["results"]:
print(f" {row.get('aisle') or '?':>10} {row['name']} ${row['price']:.2f}")aisles array means generic. When source: "generic", the array is empty and coverage: 0.0. We don't ship a hardcoded "default grocery aisle list" because every chain's vocabulary is different — better to render "no aisle data" than to mislead the user.Aisle distributions change very slowly. Responses send Cache-Control: public, max-age=600, stale-while-revalidate=3600 — a 10-minute cache with a 1-hour stale window. For mobile clients, persist by store_id + chain config_updated_at and refresh on chain config changes.
?normalize_aisles=true for in-store route planning.| Name | Type | Description |
|---|---|---|
404 | Not Found | store_id does not exist. |
422 | Unprocessable Entity | limit out of range. |
402 | Payment Required | Paid route — no payment proof. |