Marketplace price monitoring: build a used-price index by model
Search public Marketplace listings for a fixed basket of models in fixed cities on a schedule, parse each title into a model, drop junk and outliers, and record the weekly median asking price per model and city. A basket of 12 models in 5 cities checked twice a week is about 1,080 credits a month, roughly $2.03 on the $47 pack. Report medians and quartiles of asking prices, not single listings, and store only listing fields, never seller data.
A used-price index answers "what does this model sell for here, and is that going up or down?" One search tells you what something is worth today. An index needs the same searches, run the same way, every week, with the junk taken out. This playbook builds one from public Marketplace listings.
Related posts cover the single-search jobs: what an item is worth locally, alerts for new listings and exporting listings to CSV. This page is about the time series.
Who does this
- Resellers and refurbishers setting buy and sell prices by city.
- Trade-in and buy-back programs checking that their offers track the market.
- Pricing and category analysts measuring depreciation for a product line.
- Journalists and researchers reporting on second-hand markets.
Everything here uses public listings: title, price, condition, city and listing time. Do not store seller names, profile links or messages, and do not contact sellers in bulk.
The data
| Step | Endpoint | When | Credits |
|---|---|---|---|
| Find coordinates for each city | facebook marketplace location search API | Once | 1 per city |
| Search each model, newest first, last 7 days | facebook marketplace search API | Each run | 1 per page |
| Check a suspicious listing | facebook marketplace item API | Spot checks | 1 |
| Optional new-price reference from a brand store | amazon shop API | Monthly | 1 per page |
The search takes lat, lng, radius_km, min_price, max_price, condition, date_listed and sort_by. Fix all of them per basket item. If the radius changes between runs, the index measures your settings, not the market.
Worked example: cost
A basket of 12 models in 5 metros, 2 pages per search, twice a week:
- 12 × 5 × 2 × 2 = 240 credits a week, or about 1,032 a month (× 4.3).
- About 10 item checks a week: 43 a month. Five location lookups once: 5.
- Total about 1,080 credits a month, $2.03 on the $47 pack.
Daily runs on the same basket cost 12 × 5 × 2 × 30 = 3,600 credits, about $6.77. For most durable goods, prices move slowly enough that twice a week is plenty.
Cleaning: where indexes go wrong
- Parse the title. Match each listing to one basket item with a pattern, for example model plus storage size. Listings that match two items or none are dropped.
- Drop junk words. "parts", "broken", "cracked", "locked", "box only", "case", "trade", "wanted", "rent".
- Deduplicate. Same listing id across runs counts once per week. Reposts with the same title and price within 7 days count once.
- Trim outliers. Drop prices below 0.3 × or above 2 × last week's median, then drop anything outside 1.5 × IQR. Placeholder prices like 1 or 1234 disappear here.
- Split by condition. Keep "new" and "used" in separate series; mixing them makes the index follow the mix.
The numbers to report
- Weekly median asking price per model and city, with the 25th and 75th percentiles.
- Sample size. Hide any cell with fewer than 15 clean listings. A median of five listings is noise.
- Depreciation over 8 weeks = median now / median 8 weeks ago − 1.
- City spread = (highest city median − lowest) / lowest. A resale opportunity only exists when the spread covers shipping and time.
- Disappearance rate = listings not seen in two consecutive runs / listings tracked. A listing that vanishes may have sold, or may have been deleted; treat it as a rough demand signal, not sales.
- Discount to new = used median / your recorded new price, if you track one.
Remember these are asking prices. Final prices are often lower and are not public.
The index in Python
import os, re, statistics, requests
KEY = os.environ["SCRAPEIO_API_KEY"]
H = {"x-api-key": KEY}
MODEL = re.compile(r"iphone\s*15\b(?!\s*(pro|plus))\D*128", re.I)
JUNK = re.compile(r"parts|broken|cracked|locked|box only|case|trade|wanted", re.I)
def listings(lat, lng):
out, cursor = [], None
for _ in range(2):
p = {"query": "iphone 15 128gb", "lat": lat, "lng": lng, "radius_km": 40,
"condition": "used_good", "date_listed": "last_7_days",
"sort_by": "creation_time_descend"}
if cursor:
p["cursor"] = cursor
d = requests.get("https://scrapeio.com/v1/facebook/marketplace/search",
params=p, headers=H, timeout=60).json()["data"]
out += d.get("items", [])
cursor = d.get("cursor")
if not cursor:
break
return out
prices = []
for it in listings(30.2672, -97.7431):
title, price = it.get("title") or "", it.get("price")
if isinstance(price, dict):
price = price.get("amount")
if price and MODEL.search(title) and not JUNK.search(title):
prices.append(float(price))
prices.sort()
q1, med, q3 = statistics.quantiles(prices, n=4) if len(prices) >= 4 else (None,) * 3
print(len(prices), "clean listings, median", med, "IQR", q1, q3)
Check the price field shape in your first response; the code handles both a number and an object with an amount.
Pitfalls
- Changing the basket. Adding a model mid-series is fine. Changing a search query is a new series.
- Shipping listings. Delivery listings compete nationally. Use
delivery_method=local_pickupfor a local index. - Seasonality. New model launches move used prices. Mark launch weeks on the chart.
- Personal data. Keep listing ids, titles, prices and city only. If you publish the index, publish aggregates.
Run it from an AI agent
claude mcp add --transport http scrapeio https://scrapeio.com/mcp --header "x-api-key: $SCRAPEIO_API_KEY"
Then ask: "Use facebook_marketplace_location_search to get coordinates for Austin. Then facebook_marketplace_search for 'iphone 15 128gb', used, last 7 days, 40 km, 2 pages. Drop parts and broken listings and anything priced under 30% of the median. Give me the count, median and quartiles. Do not include seller names." See the Facebook Marketplace MCP server page for the tools.
How to do it with ScrapeIO
Marketplace search, item and location endpoints cost 1 credit each and return the standard envelope, with data.cursor for the next page. Credits never expire, so a twice-weekly index costs about $2 a month for as long as you run it, with no subscription to cancel.
100 free credits, no card. Credits never expire.
Get API key