AuctionsWholesaleREST APIDealers

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_type is always auction.
  • 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:

Response (trimmed)json
{
  "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. dealer holds the listing seller's address in Tucker, GA, while car_location puts the car in Chicago Heights, IL. Listing Details carries car_location, so fetch it for lots on a shortlist before you estimate transport.
  • No price. This lot has no price field, so there is nothing to compare until the auction lists one.
  • No odometer reading. It reports miles: 0 on a used car.
  • The VIN was on the market before. dos_active counts the days this source has listed the car, and dom counts 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.

  1. Daily, after 11:00 UTC
  2. Buyer's wish list
  3. Auction Search
  4. Your ranking rules
  5. 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.

  1. Lot from the run list
  2. Auction lots, same model
  3. Dealer listings, same build
  4. 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.

  1. New lots with a price
  2. Same build at dealers nearby
  3. Spread after your costs
  4. Buyer decides on a bid
auction_alerts.pypython
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.

  1. Lots sitting at one auction
  2. VIN history
  3. Earlier auction and dealer listings
  4. Wholesaler follows up
stale-lots.tsts
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_price when a lot carries one. Otherwise price is 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 msrp equals its price. 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_location before you count on a radius or a transport estimate.
  • Treat zero miles as unknown. Use a miles_range floor above zero and has_price=true to 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 start and rows. Going past your plan's pagination depth returns a 422, and a 429 carries a Retry-After header 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

  1. Create a free account and copy your API key. Start on the Free tier for development; plans are on the pricing page.
  2. Run one Auction Search for a model you buy, with has_price=true, and open one lot in Listing Details.
  3. Price a few lots against Inventory Search comparables by hand before you set an alert threshold.
  4. Schedule the daily run after 11:00 AM UTC.

The references are Auction Search, Auction Listing Details, Inventory Search and History by VIN.

Start building on the Free tier.

The Free tier is for development and testing, on the same endpoints you will use in production. When you go live, pick a plan on the pricing page.

More use cases

All use cases