Local Market Saturation & Competitor Density Analyzer avatar

Local Market Saturation & Competitor Density Analyzer

Pricing

from $127.50 / 1,000 market analyzeds

Go to Apify Store
Local Market Saturation & Competitor Density Analyzer

Local Market Saturation & Competitor Density Analyzer

Analyze how saturated a local market is. Give a business category and a location; get an aggregated Google Maps competitive landscape: competitor count, rating/review/price distributions, incumbent strength, and a 0-1 saturation score. Aggregate stats, not a lead list.

Pricing

from $127.50 / 1,000 market analyzeds

Rating

0.0

(0)

Developer

Scott Helvick

Scott Helvick

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 hours ago

Last modified

Share

How crowded is a local market? Give a business category and a location and get back an aggregated competitive-landscape read from Google Maps local listings — how many competitors there are, how good and how established they are, what they charge, and a single 0-1 saturation score — instead of a raw dump of map pins you have to analyze yourself.

What this does

  • Category + location in, a saturation report out — pass markets like {"category": "coffee shops", "location": "Austin, TX"}; get one aggregated report per market. Built for the question "how saturated is this market / where is it underserved" when sizing up competition or deciding where to open.
  • Competitor count + density — how many businesses match the category in that location (deduplicated across result pages), with a flag when the sample is capped.
  • Rating distribution — mean, median, and a bucketed spread of star ratings, so you can see whether incumbents are strong or mediocre.
  • Review-volume distribution — total and median review counts plus a bucketed spread, a proxy for how established and entrenched the field is.
  • Price-tier mix — how the market splits across $$$$$.
  • Incumbent strength — the fraction of competitors rated ≥ 4.5, the fraction with ≥ 1,000 reviews, and a count of strong incumbents (well-rated and review-deep).
  • A transparent saturation score (0-1)low / moderate / high, with its component signals (density, incumbent entrenchment, review depth) and the exact formula returned alongside it. Not a black box.
  • Aggregate-only by design — the report is statistics plus a short most-reviewed-incumbents sample (name, rating, review count). It is not a per-business lead list: no addresses, phone numbers, or review text.

Use cases:

  • An AI agent assessing "should my user open a bakery in this neighborhood" or "which of these five metros is least saturated for HVAC."
  • Market research / site selection: compare competitor density and incumbent strength across candidate locations.
  • Competitive landscape sizing for a category in a city without manually counting pins on a map.
  • Feeding a downstream model clean, aggregated market structure instead of tens of thousands of tokens of raw listings.

Why an aggregate-first design

The raw data — local business listings — is already widely scraped. The problem this solves isn't access to pins; it's that a list of 60 businesses with ratings and review counts is not, by itself, an answer to "how saturated is this market." Someone still has to aggregate it: bucket the ratings, sum the reviews, judge how entrenched the incumbents are, and turn that into a comparable read across locations.

This Actor does that aggregation deterministically and returns the answer shape — distributions, fractions, a saturation score — not the raw inputs. The saturation score is a transparent, documented composite (weighted density + incumbent entrenchment + review depth) with its components exposed, so you can audit how the number was produced and re-weight it yourself if you disagree. It is explicitly a Maps-derived heuristic, not a population-based index — there's no census join here.

How it compares

ApproachOutputAggregatedSaturation scorePer-business PIIBuilt for agents
Raw Maps/places scraperlist of pinsNo — you aggregateNooften yespartial
Manual counting on a mapa number in your headby handNon/aNo
Enterprise location-intelligence platformdashboards + forecastsYesproprietaryvariesNo (seat-based SaaS)
This Actoraggregate report per marketYesYes, transparentNo (aggregate-only)Yes

A raw scraper hands you the soup; an enterprise platform is a seat-licensed dashboard. This is the middle: a cheap, callable, aggregate market read with a transparent score.

Input

FieldTypeRequiredDefaultDescription
marketsarrayyesMarkets to analyze. Each item is {"category": "...", "location": "..."}: category is the business type to count (e.g. coffee shops, plumbers, dentists); location is a city/area (e.g. Austin, TX). 1–20 per run; each becomes one report.
maxPlacesinteger60How many local results to aggregate per market (paginated ~20/page). Higher = more representative distributions but slower. 10–80.
languagestringenGoogle interface language code (hl).
countrystringusTwo-letter Google country code (gl), e.g. us, gb, ca.

Output

One dataset record per market.

{
"market": "coffee shops in Austin, TX",
"category": "coffee shops",
"location": "Austin, TX",
"status": "completed",
"competitorCount": 58,
"resultsCapped": false,
"rating": { "mean": 4.42, "median": 4.5, "distribution": { "<3.0": 1, "3.0-3.9": 6, "4.0-4.4": 19, "4.5-5.0": 32 } },
"reviews": { "total": 184320, "median": 410, "distribution": { "<10": 4, "10-99": 14, "100-999": 22, "1000+": 18 } },
"priceMix": { "$": 7, "$$": 28, "$$$": 11, "$$$$": 2, "unknown": 10 },
"incumbentStrength": { "fractionRatingGe45": 0.552, "fractionReviewsGe1000": 0.31, "strongIncumbents": 16 },
"topCompetitors": [
{ "name": "Mozart's Coffee Roasters", "rating": 4.5, "reviewCount": 11000 },
{ "name": "Cosmic Pickle", "rating": 4.7, "reviewCount": 4300 }
],
"saturation": { "score": 0.74, "band": "high", "components": { "density": 1.0, "entrenchment": 0.276, "reviewDepth": 0.41 }, "formula": "0.5*density + 0.3*entrenchment + 0.2*reviewDepth (densityRef=50)" },
"notice": "Aggregated from public Google Maps local business listings at request time; ... not advice.",
"error": null
}
FieldTypeDescription
market / category / locationstringThe analyzed market and its echoed inputs.
statusstringcompleted (report produced, charged) or failed (no usable results; see error; not charged).
competitorCountinteger | nullDistinct businesses aggregated; a lower bound when resultsCapped is true.
resultsCappedboolean | nullTrue if the maxPlaces cap was hit (more competitors likely exist).
rating / reviewsobject | nullMean/median + bucketed distributions for star ratings and review counts.
priceMixobject | nullCount of competitors per price tier ($$$$$, plus unknown).
incumbentStrengthobject | nullFractions rated ≥ 4.5 and with ≥ 1,000 reviews, plus the strong-incumbent count.
topCompetitorsarrayUp to 5 most-reviewed incumbents (name, rating, review count only).
saturationobject | null0-1 score, band, component signals, and the formula.
noticestringStanding data-source + disclaimer note on every record.
errorstring | nullReason when status is failed; null on success.

Example

{ "markets": [{ "category": "coffee shops", "location": "Austin, TX" }, { "category": "coffee shops", "location": "Portland, OR" }], "maxPlaces": 60 }
curl -X POST "https://api.apify.com/v2/acts/shelvick~local-market-saturation/run-sync-get-dataset-items?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"markets":[{"category":"coffee shops","location":"Austin, TX"}]}'

Calling from an AI agent

Apify MCP server

The Actor is a callable tool on mcp.apify.com. The input schema is self-documenting — an LLM can build a correct call from the field names and descriptions alone. Pay per call via x402 USDC on Base or Skyfire managed tokens.

Apify SDK (Python)

from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("shelvick/local-market-saturation").call(
run_input={"markets": [{"category": "coffee shops", "location": "Austin, TX"}]}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["market"], item["competitorCount"], item["saturation"]["score"], item["saturation"]["band"])

REST API

POST https://api.apify.com/v2/acts/shelvick~local-market-saturation/run-sync-get-dataset-items?token=YOUR_TOKEN

Pricing

Pay-per-event, billed only on success: one charge per market (category × location) successfully analyzed, after the report is pushed — never for a market that returns no usable results. Multi-market runs are billed per completed market; cap a whole run with maxTotalChargeUsd. The Pricing tab on this Store page is authoritative for the current per-market rate and any subscriber discounts.

Behavior

Run-level failures (rare): invalid input fails the run before any work — empty markets, more than 20, a blank category/location, or maxPlaces out of range (10–80). Nothing is charged.

Per-market outcomes:

  • completed — an aggregated report was produced (charged). Check competitorCount, resultsCapped, and the saturation components for confidence.
  • failedno-results (the location/category returned nothing usable) or a fetch error. Never charged.

Performance: each market paginates the local listings (~20 results/page) up to maxPlaces; markets are processed concurrently. A handful of markets complete in well under a minute; large batches stay within the run timeout.

FAQ

Is the saturation score a real index? It's a transparent heuristic, not a population-based saturation index. The score combines competitor density, incumbent entrenchment, and review depth with documented weights, and every component is returned so you can audit or re-weight it. There's no census/population join.

How many competitors does it actually see? Up to maxPlaces (default 60, max 80), drawn from the top-ranked local results — a representative sample of the competitive set, not a guaranteed census of every business. resultsCapped tells you when there are likely more.

Does it return business contact details? No. The output is aggregate statistics plus a small most-reviewed-incumbents sample (name, rating, reviews). No addresses, phones, or review text — it's a market-research tool, not a lead list.

Can I compare several locations at once? Yes — pass multiple markets in one run (e.g. the same category across five cities) and get one comparable report each.

What this doesn't do

  • No per-business lead lists — no addresses, phone numbers, emails, or websites; aggregate statistics only.
  • No review text — review counts and ratings, never the review content.
  • No foot-traffic, demographics, or revenue forecasting — this is competitive structure from listings, not a location-intelligence platform.
  • No historical trend — a point-in-time snapshot at request time, not a time series.
  • No guaranteed census — a top-ranked sample, capped at maxPlaces.

Use a raw places/Maps scraper if you actually want the per-business list with contact details. Use an enterprise location-intelligence platform if you need foot-traffic panels or revenue forecasts. Use this when you want a cheap, callable, aggregate read on how crowded and how entrenched a local market is.


Design notes: www.scotthelvick.com/tools/local-market-saturation