Carfax API alternative: a car's title, listing and recall history from its VIN
What developers searching for a Carfax API usually need, the MarketCheck endpoints that return it (NMVTIS title records through VINData, listing history, live listings and recalls), and what none of them covers.
MarketCheck5 min read
For developers building used-car listing, appraisal, insurance and marketplace tools
Developers who search for a Carfax API usually want a vehicle history inside their own product: whether the title carries a brand, what the odometer read over time, where and when the car was offered for sale before, and whether a recall is open.
About Carfax: as of Oct 2026, its developer portal describes CARFAX Connect, a GraphQL API for authorized partners such as auction, consumer and dealer partners, where each partner type sees its own part of the schema, and it directs companies that want to integrate to its business development team.
In short. Three sources answer different history questions. The VINData report, a partner API on NMVTIS data, carries title events, brands, junk, salvage and total-loss records and odometer readings from titles; it needs a paid plan and an accepted agreement, and each generated report is billed. History by VIN is MarketCheck's own record of the car's listings since 2015, with asking prices, mileage, places and dates. Inventory Search with
vinandnodedup=trueshows whether the car is for sale today, and where. None of them returns accident reports, service records or owner counts.
What developers looking for a Carfax API usually need
| Need | MarketCheck endpoint | Notes |
|---|---|---|
| Title brands, junk, salvage and total-loss records | VINData report, GET /v2/vindata/access-report/aamva/{vin} |
Partner API on NMVTIS data: paid plan and agreement |
| Odometer readings over time | VINData report (title events) and History by VIN (listing mileage) | Two independent sets of readings |
| Where and when the car was listed, and at what price | History by VIN, GET /v2/history/car/{vin} |
MarketCheck's listing records since 2015 |
| Whether it is for sale today, and where | Inventory Search, GET /v2/search/car/active with vin and nodedup=true |
Every live copy, one per site |
| Open recalls | AutoRecalls, GET /v2/car/autorecalls/{vin} |
Partner API: paid plan and agreement |
| The VIN from a license plate | CarsXE Plate Decoder, GET /v2/carsxe/plate-decoder/{plate} |
Partner API: paid plan and agreement |
| Whether the VIN is valid, and what was built | Basic VIN Decoder or NeoVIN | MarketCheck's own decoders |
| Accident reports, service records, owner counts | No MarketCheck endpoint |
VINData, AutoRecalls and CarsXE are third-party sources. Each needs its agreement accepted on the 3rd-party API agreements page, and none is on the Free plan.
Title history from NMVTIS, through VINData
- VIN from a listing or trade-in
- Stored VINData report
- New report if none
- Brand and odometer flags
- Reviewer decides
The VINData docs describe the report as NMVTIS data covering title information, odometer readings, brand history and salvage records. There are two calls: access-report returns a stored report and answers 422 when none exists, and generate-report creates one. Generated reports expire after 90 days, and each generation is charged. Here is the documented access-report sample for a 2012 Ford Focus, trimmed to two of its four title events and without the summary message:
curl "https://api.marketcheck.com/v2/vindata/access-report/aamva/1FAHP3F28CL148530?api_key=YOUR_API_KEY"{
"productName": "NMVTIS",
"titleInformation": [
{ "date": "2016-04-21T00:00:00Z", "state": "California", "type": "Current",
"reportedOdometer": "100,720", "measure": "M", "event": "Title issued", "vinChanged": false },
{ "date": "2016-02-17T00:00:00Z", "state": "California", "type": "Historical",
"reportedOdometer": "56,705", "measure": "M", "event": "Title issued", "vinChanged": false }
],
"titleBrandReported": [],
"junkSalvageTotalLoss": [],
"odometerInformation": [],
"summary": { "make": "FORD", "model": "Focus", "year": 2012, "type": "car", "empty": false },
"reportSummary": { "color": "green" }
}Empty titleBrandReported and junkSalvageTotalLoss arrays are the clean result. The odometer on a title event is formatted text ("100,720"), so strip the separator before you compare readings. The docs also list scheduled NMVTIS downtime, daily from 6:00 to 7:00 AM UTC and on Sundays from 6:00 to 10:00 AM UTC, so queue checks that land in those windows. The vehicle risk checks guide builds the full check, recalls included.
Listing history from MarketCheck
History by VIN returns every listing MarketCheck has recorded for the car since 2015: its asking price, the mileage it showed, the place, the dates it was first and last seen, and whether it was listed new or used. The documented sample:
curl "https://api.marketcheck.com/v2/history/car/1FTEW1EP1MFA57388?api_key=YOUR_API_KEY"It returns 16 rows, newest first. Four of them, with seller names and websites removed:
[
{ "id": "1FTEW1EP1MFA57388-39cc2e77-8a00", "price": 32726, "miles": 37251, "inventory_type": "used",
"first_seen_at_date": "2025-07-16T02:17:29.000Z", "last_seen_at_date": "2025-07-22T02:36:28.000Z",
"city": "Holiday", "state": "FL" },
{ "id": "1FTEW1EP1MFA57388-f511c3e4-5021", "price": 32000, "miles": 37251, "inventory_type": "used",
"first_seen_at_date": "2025-06-17T16:26:42.000Z", "last_seen_at_date": "2025-06-29T16:14:36.000Z",
"city": "Ocoee", "state": "FL" },
{ "id": "1FTEW1EP1MFA57388-4b0f68b3-780d", "price": 41752, "inventory_type": "new",
"first_seen_at_date": "2021-04-02T23:18:17.000Z", "last_seen_at_date": "2021-04-13T17:09:11.000Z",
"city": "Elyria", "state": "OH" },
{ "id": "1FTEW1EP1MFA57388-b891de5c-eb99", "price": 43252, "inventory_type": "new",
"first_seen_at_date": "2021-02-26T10:34:23.000Z", "last_seen_at_date": "2021-04-01T12:52:09.000Z",
"city": "Elyria", "state": "OH" }
]In date order, the 16 rows trace one car. It was listed new in Ohio from Feb 26, 2021, on three websites at once, at 43,252, then 44,752, then 41,752 by Apr 13, 2021. It came back as a used car in Florida on Jun 17, 2025, at 32,000 with 37,251 miles, and by Jul 22, 2025 two more websites had listed it at prices from 32,426 to 36,950. The new-car rows carry no mileage, so the reading is left out rather than sent as zero.
Three rules from the docs apply. A page holds up to 50 rows, and fewer than 50 means you have them all. An unknown VIN returns an empty array with a 200, and so does an invalid one, because the VIN is not validated, so decode the VIN first and treat an empty history as unknown. And sort_order=asc returns the oldest rows first. The VIN listing history guide covers odometer drops and dealer hops in depth.
Is it for sale right now
Inventory Search with vin returns the car if it is listed today, and nodedup=true returns every copy instead of one:
curl "https://api.marketcheck.com/v2/search/car/active?api_key=YOUR_API_KEY&vin=1FTFW1E81PKE39204&nodedup=true"The documented sample finds a used 2023 Ford F-150 XLT on two dealer websites in Sayre, PA, both at 39,599 with 46,118 miles, one listed for 15 days and the other for 14. Comparing those copies is the job of the VIN price consistency check.
A history panel from all three sources
- VIN on a detail page
- Title report, listing history and live listings
- History panel with flags
- Buyer or appraiser reads the evidence
This server-side function runs the three calls in parallel and returns what a history panel needs. On a Free key, leave out the VINData call, since partner APIs need a paid plan.
const BASE = 'https://api.marketcheck.com/v2'
async function get(path: string, params: Record<string, string> = {}) {
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, value)
const res = await fetch(url)
return { status: res.status, body: res.ok ? await res.json() : null }
}
interface ListingRow { id: string, price?: number, miles?: number, inventory_type: string, first_seen_at_date: string }
export async function historyPanel(vin: string) {
const [title, history, live] = await Promise.all([
get(`/vindata/access-report/aamva/${vin}`), // 422 when no stored report: generate one (billed) or show none
get(`/history/car/${vin}`, { sort_order: 'asc' }), // up to 50 rows a page; read page=2 when a page is full
get('/search/car/active', { vin, nodedup: 'true' })
])
const rows: ListingRow[] = history.body ?? []
const withMiles = rows.filter(row => row.miles !== undefined)
return {
title: title.body
? {
events: title.body.titleInformation.length,
brands: title.body.titleBrandReported,
junkSalvageTotalLoss: title.body.junkSalvageTotalLoss
}
: null,
listedSince: rows.map(row => row.first_seen_at_date).sort()[0] ?? null,
listings: rows.length,
// A reading below the one before it is a question for a person, not a verdict
mileageDrops: withMiles.filter((row, i) => i > 0 && row.miles! < withMiles[i - 1]!.miles!).map(row => row.id),
forSaleNow: (live.body?.listings ?? []).map((l: { price?: number, miles?: number, source: string }) =>
({ price: l.price, miles: l.miles, site: l.source }))
}
}Show each part with its source and date, title records from NMVTIS through VINData and listings from MarketCheck, because a reader weighs a title brand differently from an asking price.
What none of these covers
- Accidents and damage reports. No MarketCheck endpoint returns accident or damage records. A total-loss or salvage record appears only when it was reported to NMVTIS.
- Service and maintenance records. Not in any MarketCheck response.
- Owner counts. Title events show when and where titles were issued; no response counts owners.
- An inspection. The documented VINData summary recommends an inspection by a qualified mechanic, even when the record is clean.
Limits to design around
- Partner terms first. VINData, AutoRecalls and CarsXE calls wait on their agreements, and none runs on the Free plan.
- Reports cost money to create. Call
access-reportfirst and generate only on a 422; generated reports expire after 90 days. - Paging. History by VIN pages at 50 rows and returns a 422 when you ask for a page past the last.
- Keep sources apart. Title odometer readings and listing mileage come from different systems, so label each reading with its source.
Who this is for
Developers building used-car listing pages, appraisal and trade-in tools, insurance quote and claims tools, and marketplaces that want title, listing and recall records by VIN from one API.
Start here
- Create a free account and run History by VIN and Inventory Search on the documented VINs.
- Check the pricing page for the plans that include the partner reports, then accept the VINData and AutoRecalls agreements on the 3rd-party API agreements page.
- Wire
historyPanelinto a detail page and label every section with its source. - Keep the History by VIN and VINData pages open while you build.