Data & Tools · Market Comps API

Market Comps API — local market data for any Year/Make/Model in one call

The Market Comps API returns car market value data for one Year, Make and Model around a US ZIP code.

You send the vehicle, a ZIP and a radius. You get a per-trim market summary and the comparable listings behind it, as JSON.

Get a trial token (1,000 calls) Read the API reference

What one call returns

Each response has a market summary, the comparable listings, and notes on coverage.

Price by trim

For every trim: price, average and median mileage, and average days on market.

Price trends

The 30, 60 and 90-day price change for each trim.

Market size

How many cars were listed, how many are still active, and how many left the market in your time window.

Comparable listings

Each car with VIN, price and price history, miles, colors, dealer, city and state, distance, first and last seen dates, days on market, listing URL and photo.

You can narrow the pool by trim, fuel type, mileage range and look-back window. You can sort listings by distance, price, mileage or days on market.

Who uses market comps data

Market comps answer one question: what are similar cars listed for near this ZIP right now?

Lenders

Check a vehicle's collateral value against comparable listings near the borrower, with price, mileage and trim side by side.

Insurers

Pull actual cash value comparables for the same year, make, model and trim near the loss location, each with its listing URL.

Marketplaces

Show buyers the local market next to a listing: price for the trim, typical days on market and the recent price trend.

Need the free version for a quick price check? See the free AI data API on the API overview page.

API reference

One call answers the question: for a given Year / Make / Model in a market area, what is listed and what has left the market, at what prices, and how does it break down by trim. The response has two layers — a per-trim market summary and the individual comparable listings behind it.

Endpoint

GET https://vinassessment.com/api/v1/market/comps

Authentication

Header Authorization: Bearer <token>

Format

REST · JSON over HTTPS

OpenAPI 3.1 spec Postman collection

Quick start

curl -s "https://vinassessment.com/api/v1/market/comps?year=2022&make=Toyota&model=RAV4&zip=46259&radius=100&days=90&limit=50" \
  -H "Authorization: Bearer <your token>"

Request parameters

Parameter Required Description
year, make, model required Vehicle to analyze (e.g. 2022 / Toyota / RAV4). Year from 1990 through the next model year. Make and model are matched case-insensitively.
zip required 5-digit US ZIP code — center of the market area.
radius required Radius in miles, 10–500. Coverage is computed on precomputed rings of 50 / 100 / 200 / 500 miles: the requested radius snaps up to the nearest ring, and the ring actually used is echoed back as query.radius_miles_effective .
trim optional Restrict the response to a single trim. Matched case-insensitively against the canonical trim labels returned in summary.by_trim .
fuel_type optional Powertrain filter: gas , hybrid (includes plug-in hybrids), electric or all (default). Classified from the VIN build prefix via factory decode data, so "RAV4" and "RAV4 Hybrid" listings mixed in source feeds separate cleanly. Applies to the whole response; vehicles whose build cannot be decoded are excluded while the filter is active — if decode data is temporarily unavailable, a fuel_type filter can return an empty pool (check meta.fuel_types_present ).
odometer_from
odometer_to
optional Mileage range filter. Either bound works on its own: odometer_to=60000 means "60,000 miles or less", odometer_from=10000 means "10,000 miles or more". Applies to the whole response (summary and listings), except the pool-level trend_* fields. Vehicles with unknown mileage are excluded while the filter is active. odometer_from=0 is treated as "no bound"; odometer_to must be at least 1.
days optional Look-back window in days — any value from 1 to 150 (default 90). A vehicle is in the window if it had at least one market observation within the last N days. Mutually exclusive with months . Note: summary-level DOM figures are measured within this window; listing rows report full-history first_seen and DOM (see the listings reference).
months optional Convenience alias for the look-back window in months back from today: 1 to 5 (= 30–150 days). Example: months=3 covers the last 90 days. Mutually exclusive with days .
status optional active , sold (removed from market) or all (default). Filters the listings layer only — the summary always covers both, so listed = active + sold stays true. (This differs from fuel_type , trim and odometer, which narrow the summary too.)
limit, offset optional Page size (default 50, max 500) and pagination offset. meta.total_listings carries the full match count, so pages = ceil(total_listings / limit) .
sort optional distance (default), price , mileage or days_on_market — all ascending. Sorting is applied to the full match set before pagination, so page boundaries are stable.

Response structure

Four top-level blocks: query (echo of what was asked, with effective values), summary (the market answer), listings (the comparables behind it) and meta (coverage and pagination info).

{
  "query":    { ... },     // what you asked, incl. radius_miles_effective
  "summary":  {            // whole-market layer
    "listed": 171, "sold": 110, "active": 61, "avg_days_on_market": 21,
    "by_trim": [ { ... }, ... ]
  },
  "listings": [ { ... }, ... ],   // one comparable vehicle each
  "meta":     { ... }
}

The `query` block

Echo of the request after normalization — store it alongside results so you always know what a cached response actually covers.

Field Type Meaning
year / make / model int / string The vehicle spec the response covers.
zip string Center ZIP of the market area.
radius_miles int The radius you requested.
radius_miles_effective int The precomputed ring actually used (50 / 100 / 200 / 500). Always ≥ the requested radius.
days int The effective look-back window in days, 1–150 ( months × 30 when months was passed).
months int nullable The months alias as passed, or null when the window was set via days .
status string The status filter applied to the listings layer.
trim string nullable The trim filter, or null when all trims were requested.
fuel_type string The powertrain filter applied (gas / hybrid / electric / all).
odometer_from / odometer_to int nullable The mileage bounds applied, or null when that bound was not set.
sort / limit / offset string / int / int The sort key and pagination actually applied.

The `summary` block

The "what is this car worth here, right now" layer. Counts cover the whole match set regardless of the status parameter, so the identity listed = active + sold always holds.

Field Type Meaning
listed int Unique vehicles (VINs) seen on the market in the window, after quality filters. One vehicle listed on four marketplaces counts once.
active int Of those, how many are still in active rotation right now (observed within the last 14 days — see the status field).
sold int How many disappeared from active rotation during the window (including "active"-flagged rows not observed for 14+ days) — removed from market . A strong proxy for a sale, but not a title-verified transaction.
avg_days_on_market int nullable Average days from a vehicle's first observed appearance within the look-back window to its last day in rotation (or today, if still active) — so it is capped by the days parameter. Null when the pool is empty.
by_trim array One row per trim, ordered by listing volume (largest first). Fields below.

Each `by_trim` row

Field Type Meaning
trim string Canonical trim label, resolved from VIN and equipment data against the model's factory trim vocabulary — not from free-text dealer titles. Listings whose trim cannot be confidently resolved are grouped under "Other" .
listed / sold int Vehicle counts for this trim, same semantics as the summary-level fields.
price.min / max int (USD) Lowest and highest asking price in the trim's pool, after junk-price filtering (see "How the numbers are built").
price.p25 / median / p75 int (USD) 25th / 50th / 75th percentile of asking prices (linearly interpolated). The p25–p75 band is the "typical retail range"; price a vehicle against the median.
price.avg int (USD) Arithmetic mean. More outlier-sensitive than the median — prefer the median for pricing decisions.
avg_miles / median_miles int nullable Average / median odometer of vehicles in this trim that report mileage. Null when none do.
avg_dom int Average days on market for this trim — how fast this trim turns in this area. Measured within the look-back window (see the days parameter).
trend_30d_pct / trend_60d_pct / trend_90d_pct float nullable Percent change of the weekly weighted-average asking price over the last 30 / 60 / 90 days. Pool-level: computed over the whole Year/Make/Model pool in the area, so the same values appear on every trim row. Trend windows are fixed at 30/60/90 days regardless of the days months parameter. Computed as the median of the first two vs last two full weeks in the window (single-week anchors when only 3 usable weeks); the still-accumulating current week is dropped, weeks with fewer than 5 listings are ignored, and fewer than 3 usable weeks yields null. Negative = prices softening. Null when there are not enough weekly data points or when trend data is temporarily unavailable.

Each `listings` row

One row per unique vehicle (VIN). When the same car is listed on several marketplaces, the rows are collapsed into one with the most recent state; price_history is the dominant source's cleaned series (see the field reference).

Field Type Meaning
vin string 17-character VIN — the stable identifier to join on.
trim string Canonical trim (same vocabulary as by_trim.trim ), so listings roll up cleanly into the summary.
price int (USD) Most recent asking price observed.
price_history int[] Chronological asking prices across the listing's lifetime from its dominant source (the marketplace with the most observations — mixing sources produced fake daily "sawtooth" swings), one point per day, consecutive duplicates and mid-series bounce-backs to the previous price collapsed (the final point always survives so the tail matches the current price), obvious glitches outside 0.4–2.5× of the current price dropped (the band is skipped when the current price is itself below $1,500, or when it would reject every point). First element = earliest price; comparing first vs last shows the total markdown.
miles int nullable Odometer reading as advertised. Null when the source did not report it.
fuel_type string nullable gas , hybrid (incl. plug-in) or electric — classified from the VIN build prefix via factory decode data. Null when the build could not be decoded.
condition string nullable new (under 500 miles — dealer stock/demo) or used . Null when mileage is unknown.
observations int Distinct days this VIN was observed within the look-back window — a confidence signal (a 1-observation "sold" is weaker evidence than a 15-observation one).
exterior_color / interior_color string nullable Colors as advertised by the seller (free-text, not normalized — expect "Gray" and "Magnetic gray metallic" alike).
dealer string nullable Selling dealer's name as advertised. Null for rows where the seller could not be identified.
city / state string nullable Seller's location. State is always normalized to a 2-letter USPS code ("Indiana" → "IN"); null when the feed value cannot be mapped.
distance_miles float nullable Miles from the requested ZIP. With sort=distance (default) computed from ZIP-code centroids so ordering is stable across pages; with other sort keys, exact listing coordinates are used when available and sanity-checked against the ZIP centroid (a divergence over 25 miles means feed garbage and falls back to the centroid).
first_seen / last_seen date (YYYY-MM-DD) First and most recent day this VIN was observed. Listing rows use the VIN's full price-history span when available, so first_seen can pre-date the look-back window; summary-level DOM figures remain window-scoped.
status "active" | "sold" active = currently in rotation AND observed within the last 14 days (older "active" flags are stale feed artifacts and are reported as sold); sold = removed from market (see the summary note — a proxy, not a verified sale).
days_on_market int Days from first_seen to the last day in rotation (or today, if active). Uses the VIN's full history span, so it CAN exceed the days window — only summary-level DOM figures are window-scoped.
days_on_market_in_window int The same measure restricted to the look-back window (never exceeds days ). sort=days_on_market orders by THIS value, so cross-page ordering stays stable even when full-history DOM differs.
url string nullable Link to the source listing (marketplace VDP or dealer site), always an absolute http(s) URL. Null when the source page cannot be reconstructed, when the value is not a valid absolute URL, or for marketplaces without a working public deep-link (currently iSeeCars). Listings do go offline after a sale — expect some links to 404 for sold vehicles.
photo string nullable Primary photo. Served from our image CDN (stable after the source goes down, sized to fit 800×600) when we host it; otherwise the source's own URL (https-upgraded), which can go offline with the listing.

The `meta` block

Field Type Meaning
total_listings int Total rows matching the query (after the status filter) across all pages — use with limit offset for pagination.
truncated bool True if the match set hit the internal safety cap and the summary may undercount. Practically only possible for nationwide-scale queries.
zip_count int Number of ZIP codes inside the effective radius.
prefix8_count int Number of distinct VIN build-prefixes matched for this Year/Make/Model — a coverage indicator.
ignored_params string[] Present only when the request contained query parameters the API does not understand (e.g. a leftover price_max from another provider) — they were ignored, and this field is the warning.
fuel_types_present object Powertrain mix of the client's slice: counted AFTER the trim and odometer filters but BEFORE the fuel_type filter itself (its point is to show what that filter hides), e.g. { "gas": 66, "hybrid": 105 } — so a mixed gas/hybrid market is never silently averaged without you knowing.
sources string[] Internal dataset identifiers the response was assembled from (for support/debugging).

How the numbers are built

  • One vehicle = one row. The same car scraped from several marketplaces is deduplicated by VIN; counts are counts of cars, not of ads.
  • Junk prices are filtered. Asking prices below $1,500 are always excluded; when the pool has at least 5 priced vehicles, prices far below the pool median (down-payment placeholders) are excluded too. The same $1,500 floor applies to price_history points. Filtered rows appear in neither the statistics nor the listings.
  • "Sold" means removed from market. We track each VIN across marketplaces daily; when it leaves active rotation — or hasn't been observed for 14+ days — we count it as sold. It is a strong proxy, not a title-verified sale. The observations field tells you how many distinct days back the evidence goes.
  • Powertrains are separated. Source feeds mix "RAV4" and "RAV4 Hybrid" under one model name; every VIN is classified from factory decode data and reported in fuel_type , with the pool's mix in meta.fuel_types_present — filter with the fuel_type parameter to price against the right population.
  • Trims are VIN-resolved. Trim labels come from VIN and equipment data matched against the model's factory trim vocabulary, so "SE CVT" and "SE" land in the same bucket and junk titles land in Other .
  • Freshness. The underlying listing archive ingests hundreds of thousands of new listings and millions of updates daily; the comps dataset the endpoint reads from refreshes several times a day.

Discovering valid makes, models and trims

GET /api/v1/market/options (same Bearer auth) returns the vocabulary the comps endpoint understands — call it without parameters for the list of makes, with ?make=Toyota for that make's models, and with ?make=Toyota&model=RAV4 for the canonical trim labels usable in the trim filter. Model spelling is forgiving on the comps endpoint itself — "RAV-4" and "Rav 4" resolve to RAV4 — but exact canonical labels are required for trim .

curl -s "https://vinassessment.com/api/v1/market/options?make=Toyota&model=RAV4" \
  -H "Authorization: Bearer <your token>"
# → { "make": "Toyota", "model": "RAV4", "trims": ["LE", "XLE", "XLE Premium", "SE", ...] }

Errors

Status Body When
400 { "error": "invalid_params", "message": "model is required" } or { "error": "unknown_zip", ... } A parameter is missing or out of range (the message lists every problem at once), or the ZIP code is not a known US ZIP.
401 { "error": "unauthorized", ... } Missing, invalid or expired access token.
403 { "error": "insufficient_scope", ... } or { "error": "token_not_provisioned", ... } Token is valid but lacks the "market" scope (insufficient_scope), or is not linked to a business account (token_not_provisioned).
429 { "error": "rate_limited", "retry_after_seconds": 30, ... } Per-minute rate or monthly quota exceeded. Honor the Retry-After header. Successful and rate-limited responses carry X-RateLimit-Limit , X-RateLimit-Remaining , X-RateLimit-Reset (seconds until the minute window resets) and X-RateLimit-Monthly-Limit X-RateLimit-Monthly-Remaining (401/403 responses carry none).

Plans & limits

Plan Calls / minute Calls / month
Trial (free) 10 1,000
Starter 300 5,000
Professional 1,800 25,000
Enterprise Custom Custom / committed volume

Need a trial token or a bigger plan? Contact us — we provision evaluation keys within two business days.

Get a trial token (1,000 calls) Back to the API overview