Price the auction run list against retail before the bidding starts
Filter upcoming auction lots for a buyer, flag lots listed well under nearby dealer asking prices, and spot the lots that keep coming back.
MarketCheck5 min read
For developers at dealer groups, wholesalers and remarketing teams
Auction houses and online auction platforms publish their lots as listings, with a VIN, photos, a location and a price when the auction lists one. MarketCheck collects those listings into Auction Search, GET /v2/search/car/auction/active. It takes the same filters as Inventory Search and returns the same listing shape, so a buyer's search for dealer cars carries straight over to auction lots. Auction Listing Details, GET /v2/listing/car/auction/{listing_id}, returns one lot in full.
In short. Search auction lots with the Inventory Search filters and keep the ones that carry a price and an odometer reading. Compare each with nearby dealer listings of the same build, and watch days at the auction and the VIN's listing history for lots that keep coming back. The data updates daily by 11:00 AM UTC.
What an auction lot looks like
The docs list what separates Auction Search from Inventory Search:
- Every result is an auction listing, so
seller_typeis alwaysauction. - It covers US auctions only, with no Canadian listings.
- Data is published daily at the same time as Inventory Search, by 11:00 AM UTC.
Here is one lot, trimmed from the documented Auction Listing Details sample:
{
"id": "W1N0J6EB2PG149336-d084e6e2-cd31",
"vin": "W1N0J6EB2PG149336",
"heading": "2023 Mercedes-Benz Glc Coupe",
"miles": 0,
"dom": 135,
"dom_active": 71,
"dos_active": 69,
"seller_type": "auction",
"inventory_type": "used",
"first_seen_at_mc_date": "2024-01-04T16:13:17.000Z",
"first_seen_at_source_date": "2025-06-14T01:53:33.000Z",
"source": "auto4export.com",
"car_location": { "city": "Chicago Heights", "state": "IL" },
"dealer": { "city": "Tucker", "state": "GA", "zip": "30084" },
"build": { "year": 2023, "make": "Mercedes-Benz", "model": "GLC Coupe", "trim": "Mercedes-AMG" }
}Four details in this sample matter for the builds below:
- The car sits away from the seller's address.
dealerholds the listing seller's address in Tucker, GA, whilecar_locationputs the car in Chicago Heights, IL. Listing Details carriescar_location, so fetch it for lots on a shortlist before you estimate transport. - No price. This lot has no
pricefield, so there is nothing to compare until the auction lists one. - No odometer reading. It reports
miles: 0on a used car. - The VIN was on the market before.
dos_activecounts the days this source has listed the car, anddomcounts its days on the market across every source. MarketCheck first saw the VIN on Jan 04, 2024, and this auction first listed it on Jun 14, 2025.
A buyer's filtered run list
A buyer shops the auctions from a wish list: models, years, a mileage band and a price ceiling. Each line of that list becomes one search.
- Daily, after 11:00 UTC
- Buyer's wish list
- Auction Search
- Your ranking rules
- Buyer reviews the list
Map each line to documented filters: make, model, year_range, miles_range and price_range, plus state or zip and radius for the markets you buy from. Add has_price=true, and use dos_active_range=0-7 to see only lots the auction listed in the last week. Sort with sort_by=price.
Pages hold up to 50 rows. The docs warn that asking for more than 50 falls back to the default of 10, which looks like missing lots. For every lot that passes your rules, call Listing Details. It returns car_location and extra.seller_comments, which in the documented sample carries the seller's notes on title and total-loss history.
If you work from the auction's own run list, with lanes and sale times, join it to these results by VIN. The docs list no lane or sale-time fields, so those stay in the auction's export, and MarketCheck adds each lot's listing data and the VIN's history.
Pre-sale valuation from both sides of the market
Before a sale, a buyer wants two reference points for each lot: where it sits among other auction lots of the same model, and what dealers near the store ask for the same build.
- Lot from the run list
- Auction lots, same model
- Dealer listings, same build
- Buyer sees both medians
Auction Search answers the first with stats=price and rows=0 for the make and model, the pattern of the docs' wholesale price discovery example. Inventory Search answers the second with the vins and match call in the next section.
Show both medians next to the lot with the count behind each one, so the buyer can see when a median rests on a handful of listings.
Unusual use: alerts when a lot is listed under retail
A lot is interesting when its listed price sits well under what dealers near your store are asking for the same build. Inventory Search answers the second half in one call: pass the lot's VIN with vins and match=year,make,model,trim, add your store's zip and radius, and ask for stats=price with rows=0.
- New lots with a price
- Same build at dealers nearby
- Spread after your costs
- Buyer decides on a bid
import os
import requests
BASE = "https://api.marketcheck.com/v2"
KEY = os.environ["MARKETCHECK_API_KEY"]
def get(path, **params):
r = requests.get(BASE + path, params={"api_key": KEY, **params}, timeout=60)
r.raise_for_status()
return r.json()
def new_lots(make, model, states):
"""Lots listed in the last week that carry a price and an odometer reading."""
return get("/search/car/auction/active", make=make, model=model, state=states,
car_type="used", has_price="true", miles_range="1-*",
dos_active_range="0-7", sort_by="price", sort_order="asc", rows=50)["listings"]
def retail(lot, store):
"""Count and median asking price of dealer listings of the same build near the store."""
low, high = max(0, lot["miles"] - store["miles_window"]), lot["miles"] + store["miles_window"]
res = get("/search/car/active", vins=lot["vin"], match="year,make,model,trim",
car_type="used", zip=store["zip"], radius=store["radius"],
miles_range=f"{low}-{high}", stats="price", rows=0)
return res["num_found"], res.get("stats", {}).get("price", {}).get("median")
def alerts(make, model, store):
out = []
for lot in new_lots(make, model, store["buy_states"]):
ask = lot.get("buy_now_price") or lot["price"]
comps, median = retail(lot, store)
if not median or comps < store["min_comps"]:
continue # too few dealer listings to trust a median
spread = median - ask - store["recon"] - store["transport"] - store["fees"]
if spread >= store["min_spread"]:
out.append({"listing_id": lot["id"], "vin": lot["vin"], "ask": ask, "retail_median": median,
"comps": comps, "spread": round(spread), "auction": lot["source"]})
return sorted(out, key=lambda a: a["spread"], reverse=True)store holds your own numbers: the ZIP and radius you retail in, the states you buy from, a mileage window for comparables, and your recon, transport and fee costs. Both prices in the spread are listed prices, the lot's figure on one side and dealers' asking prices on the other, so the alert hands your buyer a shortlist and the buyer sets the bid.
Unusual use: a wholesaler's stale-lot detector
A wholesaler watching an auction platform wants two lists: lots that have sat there longest, and VINs that have been listed at auction before. Both are worth a call before anyone bids. The seller of a lot that sits may be ready to move on price, and a VIN that returns may have a problem the photos miss.
- Lots sitting at one auction
- VIN history
- Earlier auction and dealer listings
- Wholesaler follows up
const BASE = 'https://api.marketcheck.com/v2'
async function get(path: string, params: Record<string, string | number> = {}) {
const url = new URL(BASE + path)
url.searchParams.set('api_key', process.env.MARKETCHECK_API_KEY ?? '')
for (const [key, value] of Object.entries(params)) url.searchParams.set(key, String(value))
const res = await fetch(url)
if (!res.ok) throw new Error(`${path} returned ${res.status}`)
return res.json()
}
interface HistoryRow { id: string, price?: number, seller_type: string, source: string, first_seen_at_date: string }
export async function staleLots(auctionDomain: string, minDaysListed: number) {
// The lots this auction has listed longest come first
const { listings } = await get('/search/car/auction/active', {
source: auctionDomain, dos_active_range: `${minDaysListed}-*`, sort_by: 'dos_active', sort_order: 'desc', rows: 50
})
const report = []
for (const lot of listings) {
// Newest first, up to 50 rows per page: enough to see recent runs
const history: HistoryRow[] = await get(`/history/car/${lot.vin}`)
const earlier = history.filter(row => row.id !== lot.id)
report.push({
vin: lot.vin,
daysAtThisAuction: lot.dos_active,
daysOnMarketLifetime: lot.dom,
earlierAuctionListings: earlier.filter(row => row.seller_type === 'auction').length,
lastDealerAsking: earlier.find(row => row.seller_type === 'dealer')?.price
})
}
return report
}History by VIN returns each earlier listing with its seller_type (dealer, fsbo or auction), source, asking price and dates. A VIN with earlier auction listings has been offered at auction before. A recent dealer listing tells you what a store was asking for the car, a useful anchor for the call. Each lot costs one history call, so run it on the shortlist.
Gotchas
- Know which price you have. Use
buy_now_pricewhen a lot carries one. Otherwisepriceis the lot's current listed figure, so confirm on the lot page what it represents before a spread drives a bid. Stats on auction lots summarize those listed figures; sale results are outside this endpoint. - MSRP can echo the price. In the documented Auction Search sample, a lot's
msrpequals itsprice. Take MSRP from a VIN decode when you need it. - Check the car's location. The details sample shows the car in a different state from the seller, so check
car_locationbefore you count on a radius or a transport estimate. - Treat zero miles as unknown. Use a
miles_rangefloor above zero andhas_price=trueto keep unready lots out of a buyer's list. - Premium rate, daily data. The docs note that Auction Search is billed at a premium rate and updates once a day, so run each search once after 11:00 AM UTC and cache it.
- Paging. Page with
startandrows. Going past your plan's pagination depth returns a 422, and a 429 carries aRetry-Afterheader to honor.
Who this is for
- Used-car buyers at dealer groups who source at auction and want a ranked list each morning.
- Wholesalers watching auction platforms for lots that sit or return.
- Remarketing teams checking how their consigned units are listed and how long they sit.
- Developers building sourcing tools for any of the above.
Start here
- Create a free account and copy your API key. Start on the Free tier for development; plans are on the pricing page.
- Run one Auction Search for a model you buy, with
has_price=true, and open one lot in Listing Details. - Price a few lots against Inventory Search comparables by hand before you set an alert threshold.
- Schedule the daily run after 11:00 AM UTC.
The references are Auction Search, Auction Listing Details, Inventory Search and History by VIN.