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

Chain aisles

Frequency-ranked aisles aggregated across every store in the chain. Produces a stable per-chain aisle vocabulary suitable for mapping any store's price feed onto a single picker — or as a fallback when a specific store has too-low coverage to use its own aisles.

GET/v1/chains/{slug}/aisles$0.01
ℹ
Use this when one store doesn't have enough data
The sister endpoint Store aisles cascades to this one automatically when a single store's coverage is below 30% — you usually don't need to call this directly. Reach for it when you want a stable chain-level vocabulary independent of any one store's recency, or when you're building a chain comparison view.

Path parameters

NameTypeDescription
slugrequiredstringChain slug, e.g. wegmans.

Query parameters

NameTypeDescription
limitintegerdefault: 20Max aisles to return. Range 1–100.

Request

Request
curl 'https://api.mainmarket.com/v1/chains/wegmans/aisles?limit=25'

Response

200 OKjson
{
  "chain_slug": "wegmans",
  "source": "chain",
  "coverage": 0.81,
  "aisles": [
    "Aisle 1",
    "Aisle 2",
    "Aisle 3",
    "Produce",
    "Bakery",
    "Deli",
    "Meat",
    "Seafood",
    "Dairy",
    "Frozen"
  ],
  "aisle_counts": {
    "Aisle 1": 18420,
    "Aisle 2": 17905,
    "Produce": 9120
  }
}

Response fields

NameTypeDescription
chain_slugstringEcho of the requested slug.
sourcestringAlways chain for this endpoint, except generic when chain coverage is below the threshold.
coveragenumberFraction of sampled SPP rows that carried a non-null aisle (0.0–1.0).
aislesstring[]Normalized aisle names ordered by frequency descending.
aisle_countsobjectMap of aisle name → product count from the sample.
ℹ
Sample size
The aggregation samples up to 50,000 SPP rows. Top aisles converge well below that cap, but the long tail can vary a few rows between calls. Use aisle_counts to gauge confidence — top entries with thousands of product counts are stable; tail entries with a few dozen are noisy.

How the aggregation works

Each call samples up to 50,000 rows from the chain's price catalog, runs them through the normalization pipeline below, then ranks the surviving aisle names by frequency:

1. Strip 'Aisle ' prefix
"Aisle 12", "AISLE 12", "aisle 12" all collapse to "12" for matching, then get re-prefixed to "Aisle 12" on output.
2. Strip shelf suffix
"Aisle 12, Shelf 3", "Aisle 12 - Shelf 3", and "Aisle 12 / Shelf 3" all collapse to "Aisle 12" — shelf-level granularity isn't useful at chain scope.
3. Drop PoS-noise rows
Rows tagged with checkout-only locations (self-checkout, register candy, customer service desk, restrooms, gas pumps, etc.) are filtered out — they're physical locations in the store but not 'aisles' in any shopping sense.
4. Re-prefix pure numerics
A raw value of "12" after stripping is re-prefixed to "Aisle 12" for display. Word aisles like "Produce", "Bakery", "Deli" stay un-prefixed.
5. Rank by frequency
Group by normalized name, count product appearances, sort descending. limit caps the returned list (default 20, max 100).

Workflow: build a chain-wide aisle picker

The classic use: when a user shops the same chain across many stores, you want the aisle list to look the same regardless of which store they're currently in. Pull this once per chain, cache it, then use it as the canonical vocabulary when joining to /v1/prices rows with ?normalize_aisles=true.

Request
import httpx

# Step 1: pull the chain vocabulary once (cache for the session)
chain_aisles = httpx.get(
    "https://api.mainmarket.com/v1/chains/wegmans/aisles",
    params={"limit": 50},
    headers={"Authorization": "Bearer mm_live_..."},
).json()
order = {name: i for i, name in enumerate(chain_aisles["aisles"])}

# Step 2: for any store in the chain, sort prices by chain-level aisle order
prices = httpx.get(
    "https://api.mainmarket.com/v1/prices",
    params={
        "store_id": "8c1a4d1e-30a7-4d92-9e1c-1cb43c6f2e10",
        "product_ids": "9d4e...,a1b2...,c3d4...",
        "normalize_aisles": True,
    },
    headers={"Authorization": "Bearer mm_live_..."},
).json()

# Sort by chain aisle order — items with unknown aisles sink to the bottom
prices["results"].sort(key=lambda r: order.get(r.get("aisle"), len(order)))
for row in prices["results"]:
    aisle = row.get("aisle") or "?"
    print(f"  {aisle:>10}  {row['name']}  ${row['price']:.2f}")

When to use this vs Store aisles

Use Store aisles when:
You have a specific store and want the most accurate ordering for that store. Coverage is usually higher when the store has a recent scrape. Falls back to chain aisles automatically if coverage is low.
Use Chain aisles when:
Building a stable cross-store picker, comparing two chains side-by-side, or bootstrapping a UI before any specific store has been chosen. Also when a store you care about hasn't been scraped recently and you want a more robust signal than the per-store fallback.

Notable behavior

  • Identical normalization to Store aisles. The output vocabulary here is interchangeable with what /v1/stores/{id}/aisles returns — by design, so a user picker built from one works on the other.
  • source = "generic" is rare here. A chain has to have very low aisle coverage chain-wide for the fallback to fire — usually means the chain doesn't expose aisle data on PDPs at all (e.g. chains that only ship their flyer with prices but no aisle metadata). Coverage will be 0.0 when this happens.
  • Counts are sampled, not exact. Don't use aisle_counts for inventory analysis — they're proportional, not absolute. The 50k cap means a chain with 200k SKUs is sampled at 25%.

Caching

Chain aisle distributions change slowly (a remodel might add or rename aisles once a year). The endpoint sends Cache-Control: public, max-age=3600, stale-while-revalidate=7200 — a one-hour cache. For mobile clients, persist by chain slug and refresh once per session.

Related

  • Store aisles — same shape, but for one specific store. Cascades to this endpoint when store coverage is low.
  • Search prices — pass ?normalize_aisles=true so price rows join cleanly to either aisle vocabulary.
  • Get a chain — chain metadata (logos, online-shopping config) without aisle data.

Errors

NameTypeDescription
404Not FoundSlug is not a known chain.
422Unprocessable Entitylimit out of range.
402Payment RequiredPaid route — no payment proof.