11 synchronous data endpoints plus an asynchronous vehicle-history workflow over one authenticated surface: VIN identity, factory specifications, recalls, market value, depreciation, ownership costs, dealer listings, photos, and account-owned history-report results. Every response is JSON and every error is a problem document.
Base URL
https://api.vehicles.dev
Auth
Authorization: Bearer <key>
Errors
application/problem+json
Price catalog
2026-08-18
Overview
The Vehicles.dev API is a paid, server-to-server HTTP API. Every machine operation lives under /v1/vehicles/ on https://api.vehicles.dev and returnsapplication/json. The 11 data lookups use GET; vehicle history reports start with an idempotent POST and continue through account-owned list, status, and result resources. There is exactly one API version and no version header — the path prefix is the contract.
It is deliberately not browser-callable. No CORS headers are served, cookie authentication is rejected, and a request carrying an Origin header is refused outright. Call it from your backend and keep your key there.
What the data actually is
The synchronous data endpoints are built on a continuously crawled US vehicle dataset, joined against public federal sources where those are authoritative. The separate vehicle-history workflow preserves the canonical JSON returned by its report provider and keeps that result scoped to the ordering account.
1,721,693normalized dealer listings across 1,719,030 distinct vehicles
4,686,766price and mileage observations, appended on every crawl pass, powering valuation and marketplace analysis
3.7%median absolute percentage error of the gradient-boosted asking-price model on holdout data
NHTSA + EPAvPIC for factory specifications and VIN fallback decoding, the recalls API for safety campaigns, and fueleconomy.gov for ownership costs
Provenance is explicit rather than implied. Every successful body carries source, VIN-keyed lookups add origin ("store" when we served it from our own dataset, "vpic" when we decoded it live), and valuation responses echo the exact feature vector the model scored.
What it is not
Market value is an asking price derived from live dealer listings, not a realized transaction price. Depreciation curves are per make and model, not per trim or VIN. Recalls are matched at the year/make/model level, so a campaign listing does not by itself prove a specific VIN is affected, and no public source tells us whether this car was repaired. Specifications are VIN-pattern decodes, not per-car build records, so they carry no factory option codes. Photo galleries hold what the source published and gave us — compare galleryCount against photoCount before assuming a gallery is complete. Each endpoint below states its own coverage limits — read them before you build on the numbers.
Quickstart
From zero to a real, billable response in about a minute. The calls below are copy and paste ready.
01
Sign up
Create an account. Every new workspace lands on the free Starter plan with 1,000 included calls per month across all eleven synchronous vehicle data endpoints, plus a one-time $10 credit balance for usage after that shared allowance.
Open the dashboard, find the API keys panel, name a key and select "Create API key". The secret is displayed exactly once and is never recoverable — copy it immediately. Keys are scoped to Vehicles.dev; a key minted for another product is rejected with 401 invalid_credential.
03
Log in
Install the CLI and log in once. vehicles login saves your key to ~/.vehicles/credentials.json — the MCP server reads the same file, so a single login covers both. Prefer raw HTTP or a server SDK? Export VEHICLES_API_KEY instead; the Bearer examples throughout these docs use it.
shell
npm install -g vehicles-dev-cli
vehicles login
04
Make your first call
Decode a VIN. This is a real, billable call against production data — it draws from your Starter plan's included calls first. (Raw HTTP: GET /v1/vehicles/vin/{vin} with an Authorization: Bearer header, shown under every endpoint below.)
Read the response. "origin": "store" means the VIN matched a row in our scraped store and was served without a live external round trip; it does not guarantee that every field was enriched by vPIC. "vpic" means we decoded it live against NHTSA. Null fields are omitted rather than emitted as null, so treat every key inside vehicle as optional. The response also carried an x-request-id header — keep it if you need to open a support ticket about this call.
What it costVIN Decode is one of the eleven synchronous vehicle data endpoints covered by the Starter plan’s 1,000 included monthly calls, so that first request drew from your entitlement rather than your credit balance. Had it failed, it would have cost nothing at all — billing only settles on 2xx.
Official SDKs
Use the official, typed server-side clients when you do not want to manage bearer headers, query serialization, problem documents, timeouts, or history-report polling yourself. Both SDKs cover all 11 synchronous endpoints and the complete durable history-report workflow.
The first releases are installed from immutable GitHub v0.1.1 tags. npm and PyPI publication are not enabled yet, so use the exact commands below rather than an unversioned registry package.
Server-side onlyKeep VEHICLES_API_KEY in a server-side secret store. Never expose it in browser JavaScript, a mobile application, a public bundle, or a NEXT_PUBLIC_* variable. The API rejects browser Origin andCookie headers by design.
TypeScriptvehicles-dev/typescript-sdkv0.1.1
TypeScript SDK
Node.js 22+ (ESM). A dependency-free ESM client with typed methods for every synchronous lookup and the durable history-report workflow. It uses Node's built-in fetch and defaults to a 30-second request timeout.
The example prints only an allowlisted identity summary. Do not log API keys, VINs, raw responses, report identifiers, complete history reports, or full error objects.
Python 3.11+. Typed synchronous and asynchronous clients powered by httpx. Use Vehicles in regular applications or AsyncVehicles with an async context manager; both expose the same API surface.
import os
from vehicles_dev import Vehicles
with Vehicles(os.environ["VEHICLES_API_KEY"]) as vehicles:
decoded = vehicles.decode_vin("1HGCM82633A004352")
vehicle = decoded["vehicle"]
print({field: vehicle.get(field) for field in ("year", "make", "model", "trim")})
The example prints only an allowlisted identity summary. Do not log API keys, VINs, raw responses, report identifiers, complete history reports, or full error objects.
Runnable examplesClone the Vehicles.dev examples repository for VIN decode, listing search, and explicitly guarded, billable history-report flows in both languages.
Authentication
Every /v1 request must carry a product API key as an HTTP bearer credential. There is no other scheme: no query-string keys, no basic auth, no cookies.
Required header
Authorization: Bearer $VEHICLES_API_KEY
The scheme token is case-sensitive Bearer followed by exactly one space. bearer, a double space, or any other scheme is rejected as 401 invalid_credential.
Keys
Mint and revoke keys in the dashboard. The plaintext secret is returned exactly once at creation and is never recoverable — we store only a hash and the last four characters.
Vehicles keys are prefixed vdev_. Keys are product-scoped: presenting a key minted for another product returns 401 invalid_credential.
Rotation is create-then-revoke. Create the replacement key, deploy it, then revoke the old one — revocation takes effect immediately.
Rate limits and credit balances are enforced per account, not per key, so splitting traffic across several keys does not raise your throughput ceiling.
An unauthenticated request
Calling any endpoint without a key returns a problem document. Note the x-request-id header, which is present on every response the API sends, successful or not.
OrderingAuthentication runs before schema validation. A request that is both unauthenticated and malformed returns 401, never 400 — fix the credential first, then the parameters.
Errors
Every failure is an RFC 9457 problem document served as application/problem+json. The envelope is closed — no fields beyond the ones below ever appear — but key order is not stable, so parse the JSON rather than pattern-matching the string.
Problem document fields
Field
Type
Description
code
string
Stable, machine-readable error slug in snake_case. Branch on this, never on the prose in detail.
detail
string
Human-readable explanation of this specific occurrence.
instance
string (optional)
The request path, including the query string, that produced the error.
invalid_params
array (optional)
Present only on 400 request_validation_failed. Each entry is { name, pointer, reason }: the failing schema keyword, a JSON pointer into the request, and the validator message.
request_id
string
Server-generated UUID, identical to the x-request-id response header. Quote it in support requests.
retryable
boolean
Whether repeating the identical request can plausibly succeed. Every 503 and the 429 are true; other 4xx are false.
status
integer
Mirrors the HTTP status code.
title
string
Short status-level summary, for example "Unauthorized".
type
string (URI)
Opaque problem-type URI. Use it as an identifier; it is not a documentation link. It is normally the code with underscores replaced by hyphens, but branch on code rather than on the URI: request_validation_failed is emitted with type .../problems/request-validation, without the -failed suffix.
Shared error codes
These can be returned by any endpoint. Branch on code, which is stable, rather than on detail, which is prose.
Errors common to every endpoint
Status
code
Retryable
When it happens
400
request_validation_failed
No
A path, query or body value failed its schema. Unknown query parameters are rejected rather than ignored. The problem document carries an invalid_params array naming each failure.
401
authentication_required
No
No Authorization header was sent.
401
invalid_credential
No
The bearer value is malformed, or the key is unknown, revoked, or minted for a different product.
401
cookie_credentials_rejected
No
The request carried a Cookie header. Cookie authentication is never accepted on /v1 — send only the Authorization header.
401
invalid_origin
No
The request reached /v1 without resolving to a product.
402
insufficient_credits
No
Included calls are exhausted and the credit balance cannot cover this request. Add credits or upgrade the plan. Metered data fees are prepaid from credits on every plan — a paid plan buys the rate limit, the lower unit prices, and endpoint access, never an overage bill.
403
plan_upgrade_required
No
The requested operation is not sold on the current plan. Upgrade before retrying.
403
subscription_inactive
No
The account's subscription is not active or trialing.
404
route_not_found
No
Unknown path — or a request whose Host header did not resolve to a product, or that carried an Origin / Access-Control-Request-Method header. The API is not browser-callable and serves no CORS headers.
429
rate_limit_exceeded
Yes
The plan's requests-per-second limit was exceeded. A retry-after header is returned and the billing reservation is released, so the rejected call is free.
500
internal_error
Yes
An unmapped server failure. Retry with backoff and keep the request id.
503
authentication_unavailable
Yes
The API-key verifier is unreachable. Retry with backoff.
Validation failures
A 400 request_validation_failed adds an invalid_params array pointing at each offending value. Unknown query parameters are an error, not a silently ignored extra — every query schema is closed.
400 · application/problem+json
{
"code": "request_validation_failed",
"detail": "The request did not match the operation contract.",
"invalid_params": [
{ "name": "minLength", "pointer": "/state", "reason": "must NOT have fewer than 2 characters" }
],
"request_id": "d890d9c9-e6f9-411b-9e47-e0d947868003",
"retryable": false,
"status": 400,
"title": "Bad Request",
"type": "https://api.data-platform.dev/problems/request-validation",
"instance": "/v1/vehicles/market-value?make=Toyota&model=Camry&year=2021&state=T"
}
Retrying
Use the retryable boolean rather than the status code. Every 503 and the 429 set it to true; the 429 additionally carries a retry-after header in whole seconds. Client errors other than 429 are false — retrying them unchanged will fail identically. Retry with exponential backoff and jitter, and note that none of these endpoints mutate anything, so a retried GET is always safe.
Getting support on a failure
Every response carries an x-request-id header, and on failures the same UUID appears as request_id in the body. It is generated server-side — sending your own x-request-id has no effect and it will not be echoed. Log it. Because Authorization and Cookie headers are redacted from our logs, the request id is the only way we can locate your exact call.
Pricing & limits
Pricing is per successful call, metered at the endpoint level. There are no bundles and no minimums beyond the plan fee.
Only 2xx is billedEvery endpoint is successOnly. A credit reservation is placed before the handler runs and settled only when the response is 2xx; any 4xx or 5xx — including rate-limit rejections and upstream outages — releases the reservation. A failed call costs nothing.
Plans
Vehicles.dev plans
Plan
Platform fee
Included calls
Rate limit
Starter
$0 / month
1,000 / month
5 req / second
Pro
$299 / month
None — every call metered
10 req / second
Scale
$599 / month
None — every call metered
50 req / second
“Included calls” means calls with no per-call charge. Only Starter has any: 1,000 per UTC month, shared by all eleven synchronous vehicle data endpoints listed below. Pro and Scale include none. Those plans have no monthly call ceiling, but that is a volume allowance, not free usage — every successful call is metered at its per-endpoint rate on top of the monthly platform fee, and the fee buys throughput, endpoint access and support rather than calls.
Rate limiting is a token bucket keyed to your account, refilling at the plan’s requests-per-second rate. Exceeding it returns 429 rate_limit_exceeded with a retry-after header, and the reservation is released so the rejected request is free. There are no x-ratelimit-* headers.
Per-endpoint prices
Prices are per billing unit. The second figure in each cell is the equivalent cost per 1,000 billing units. A dash means the endpoint is not sold on that plan and returns403 plan_upgrade_required.
The Starter plan’s 1,000 included monthly calls are shared by all eleven synchronous vehicle data endpoints above: VIN Decode, Search Vehicle Listings, Vehicle Photos, Specifications, Recalls & Safety, Market Value, Depreciation, Ownership Costs, Total Cost of Ownership, Loan & Payments, and Purchase Tax & Fees. The entitlement resets on the UTC calendar month.
Every new account also receives a one-time $10 credit balance. It never resets, and after the shared allowance is used it funds successful calls at the published Starter rate. Plan-gated operations remain unavailable regardless of credit balance.
When entitlement and credits together cannot cover a call, the request is refused with 402 insufficient_credits. That applies on every plan, not just Starter: metered data fees are always prepaid from credits, and a paid plan buys the rate limit, the lower unit prices and endpoint access rather than an overage bill.
There is no machine-readable balance today. Your entitlement, credit balance and usage are visible only in the dashboard: the control-plane endpoints behind it require a dashboard session token, and a vdev_ API key presented there is rejected with 401 invalid_credential. Metered responses carry no balance or usage headers either — the only response headers this API sets beyond the standard ones are x-request-id, retry-after and cache-control. So a server-to-server integration cannot pre-flight or monitor burn-down; 402 insufficient_credits is the only programmatic signal, and you should handle it as an expected outcome rather than trying to predict it.
Concurrency is safe. Reservations hold credit while a request is in flight, so parallel callers cannot oversubscribe a balance.
Vehicle history reports
Vehicle history is an asynchronous, account-owned workflow because provider coverage and preparation time vary by VIN. Creating the report requires a machine key with reports:order, as does explicitly retrying a durable local submitting job. Polling and reading the result require reports:read. A report ordered by one account is not visible to another.
Pricing
Pro: $1.99 per completed report. Scale: $0.99 per completed report. Starter accounts cannot order reports and receive 403 plan_upgrade_required. The charge settles only after canonical report JSON is stored; invalid or unsupported VINs, failed generations, and action_required outcomes cost $0.
List
Recover recent account-owned report IDs, VINs, statuses, and polling cadence. This read never submits or retries provider work.
GET/v1/vehicles/history-reports
Create
Send a canonical 17-character VIN and UUID Idempotency-Key. The response is 202 with a stable ID, Location, and Retry-After.
POST/v1/vehicles/history-reports
Retry submission
Use only when a durable report remains submitting after an uncertain create response. The server preserves the stored provider idempotency key; the machine route requires reports:order and the dashboard route requires billing:write.
POST/v1/vehicles/history-reports/{id}/retry
Poll
Read status at the server-provided cadence. Valid states are submitting, queued, processing, action_required, and completed.
GET/v1/vehicles/history-reports/{id}
Read or print
Fetch canonical JSON after completed and hasResult=true. The dashboard renders the same account-owned result as a branded view that can be viewed or printed; there is no separate file-artifact endpoint.
# Poll status at the Retry-After cadence
curl -i https://api.vehicles.dev/v1/vehicles/history-reports/{id} \
-H "Authorization: Bearer $VEHICLES_API_KEY"
# Only for a durable local status=submitting after an uncertain create response
curl -i -X POST https://api.vehicles.dev/v1/vehicles/history-reports/{id}/retry \
-H "Authorization: Bearer $VEHICLES_API_KEY"
# Fetch canonical JSON only after status=completed and hasResult=true
curl -i https://api.vehicles.dev/v1/vehicles/history-reports/{id}/result \
-H "Authorization: Bearer $VEHICLES_API_KEY"
Result data and coverage
The fixed result envelope is { "metadata": ReportMetadata, "report": Record<string, unknown> }. Metadata binds the stored result to its canonical VIN, provider, retrieval timestamp, schema version, and available coverage sections. The report value is the canonical provider object; Vehicles.dev does not rename its fields or discard provider-specific keys. Process report keys defensively and use metadata coverage rather than treating an absent section as a clean finding.
The example is representative, not exhaustive. Depending on provider coverage, a report may include these categories:
Possible report categories
Category
What may be returned
Vehicle & equipment
Decoded identity, specifications, installed equipment, options, and feature details.
Ownership
Available owner sequence, registration, location, and use records.
Title & brands
Title events and provider-reported brands such as rebuilt or flood.
Odometer & mileage
Reported mileage events and possible odometer inconsistency signals.
Damage & accidents
Reported collision, damage, severity, and event details.
Theft
Available theft records and recovery status.
Junk, salvage & insurance
Junk, salvage, insurance, and total-loss records when available.
Safety recalls
Provider-returned safety recall records.
Sales & auction history
Sale, auction, and listing events, sometimes including price or media.
Market analysis
Available market, valuation, and depreciation context.
Coverage is not a clean-record guaranteeA missing category means the provider did not return coverage for it, not that no incident exists. An empty category that is present means no records were returned in that category. Objects, arrays, labels, dates, and nested fields can vary, and additional provider fields may appear without notice. Preserve unknown fields and do not turn missing data into a “clear” claim.
Idempotency, retries, and billing
The workflow is available on Pro and Scale. Starter accounts receive 403 plan_upgrade_required; credits cannot unlock reports on Starter. Reports do not consume standard included calls.
Reusing one Idempotency-Key with the same VIN returns the existing report. Reusing it for a different VIN returns 409 idempotency_conflict.
If an uncertain create response leaves a durable local report in submitting, call POST /v1/vehicles/history-reports/{id}/retry. This endpoint resubmits with the original provider idempotency key; read-only status polling never performs a submission.
Respect Retry-After. Reading the result before it is ready returns the retryable 409 report_not_ready; a provider throttle returns retryable429 report_rate_limited.
Billing settles when a completed result is stored. Invalid or unsupported VINs, failed generations, and action_required outcomes do not settle a report charge.
Coverage is conditional. When a title, accident, odometer, or ownership section is absent, present it as unavailable — never as a clean record.
Endpoint reference
11 synchronous endpoints in three groups. Each one lists its parameters, a runnable request, a real response captured from production, its own error codes, and the coverage caveats that matter when you build on it. The asynchronous history-report contract is documented separately above because it spans three resources.
Identity & safety
Resolve a VIN into who the vehicle is, what the factory built, and what has been recalled since.
GET/v1/vehicles/vin/{vin}vin_decode
VIN Decode
Decodes a VIN into canonical, normalized vehicle identity: year, make, model, trim, body style, drivetrain, fuel, transmission, cylinders, doors and engine.
Starter
$0.004
Pro
$0.0025
Scale
$0.0015
Per
successful lookup
Timeout
10 s
Data source
Store-first. We look the VIN up in our scraped-listings Postgres store and return a matching row with origin: "store". Store fields can come from the listing source or prior enrichment, and completeness varies. When the VIN is unknown to us we fall back to a live NHTSA vPIC DecodeVinValuesBatch call and return origin: "vpic".
Path parameters
VIN Decode path parameters
Parameter
Type
Required
Description
vin
stringExactly 17 letters or digits; I, O and Q are not allowed
Required
Vehicle Identification Number. Case-insensitive — the API upper-cases it before the lookup and echoes the upper-cased form back in vin. The decoder also rejects a bad check digit for North American VINs (first character 1–5).
"store" = matched and served from our scraped store, whose field sources and completeness can vary. "vpic" = decoded live against NHTSA for this request.
source
"carscrape"
Constant provenance marker for the backing data service.
vehicle
object
Canonical decoded fields. Possible keys: year, make, model, trim, body_style, drivetrain, fuel, transmission, cylinders, doors, engine. Null values are omitted.
vin
string
The upper-cased VIN that was decoded.
Errors
VIN Decode endpoint-specific errors
Status
code
Retryable
When it happens
400
invalid_vin
No
The backing decoder rejected the VIN. The public route requires exactly 17 VIN-safe letters or digits (never I, O or Q); North American VINs whose first character is 1–5 must also pass their check digit.
404
vin_not_decodable
No
Neither our store nor NHTSA vPIC could resolve a make or model for that VIN.
503
decode_upstream_unavailable
Yes
The decode service was unreachable or the 10 s budget elapsed.
503
decode_upstream_error
Yes
The decode service returned an unexpected status — typically when live NHTSA vPIC is down for a VIN we have never seen.
Keys with null values are omitted entirely, so the key set inside vehicle varies per VIN. Always treat every field as optional — vPIC commonly returns no trim or door count, especially for EVs.
engine is a derived string composed from vPIC displacement, engine configuration and cylinder count (for example "3.5L V-Shaped 6cyl") and is frequently absent on store-origin rows.
Values are normalized to a controlled vocabulary: drivetrain resolves to AWD, FWD, RWD, 2WD or 4WD, and unmapped vPIC axle notation is dropped rather than passed through. Use Vehicle Specifications when you want raw vPIC vocabulary instead.
origin: "vpic" responses depend on a live external call, so they are slower and track NHTSA availability.
Returns the full manufacturer spec sheet for a VIN: body class, doors, seats, drive type, cylinders, displacement, horsepower, engine configuration, transmission style and speeds, GVWR class, plant country and manufacturer.
Starter
$0.0015
Pro
$0.0015
Scale
$0.001
Per
successful lookup
Timeout
15 s
Data source
A live NHTSA vPIC DecodeVinValuesBatch call on every request — no store cache is consulted. The raw vPIC row is projected through a fixed 20-field allowlist and blank values are dropped.
Path parameters
Vehicle Specifications path parameters
Parameter
Type
Required
Description
vin
stringExactly 17 letters or digits; I, O and Q are not allowed
Required
Vehicle Identification Number. Case-insensitive; echoed back upper-cased. North American VINs whose first character is 1–5 must pass their check digit.
Constant provenance marker for the backing data service.
specifications
object
vPIC spec sheet on snake_case keys: make, model, year, trim, series, body_class, doors, seats, drive_type, cylinders, displacement_l, engine_hp, fuel_primary, engine_config, transmission_style, transmission_speeds, gvwr, plant_country, manufacturer, vehicle_type. All values are strings; blanks are omitted.
vin
string
The upper-cased VIN the sheet belongs to.
Errors
Vehicle Specifications endpoint-specific errors
Status
code
Retryable
When it happens
400
invalid_vin
No
The backing validator or vPIC rejected the VIN after the public route accepted its exactly 17 VIN-safe characters. North American VINs must pass their check digit.
404
specifications_not_found
No
vPIC returned a row with neither Make nor Model for that VIN.
503
specifications_upstream_unavailable
Yes
The specifications service was unreachable or the 15 s budget elapsed.
503
specifications_upstream_error
Yes
An unexpected upstream status, notably when NHTSA vPIC itself is erroring.
Every value inside specifications is a raw vPIC string. year, doors, seats, cylinders, engine_hp, displacement_l and transmission_speeds are NOT numbers — parse them client-side.
Values keep unnormalized vPIC vocabulary: make comes back as "TOYOTA" and drive_type as axle notation like "4WD/4-Wheel Drive/4x4". Use VIN Decode when you want normalized values.
Blank fields are omitted, and coverage varies heavily by manufacturer and model year. Older VINs and EVs commonly lack trim, seats and engine_hp.
This is an uncached live external call, so latency tracks NHTSA vPIC against a 15 s route budget.
Resolves a VIN to its year, make and model, then returns every NHTSA safety recall campaign for that vehicle — component, summary, consequence, remedy, campaign number and report date.
Starter
$0.01
Pro
$0.01
Scale
$0.007
Per
successful lookup
Timeout
15 s
Data source
Two stages. We resolve year/make/model store-first, falling back to a live vPIC decode when the VIN is unknown to us, then query the NHTSA recallsByVehicle API and project each campaign onto six fields.
Path parameters
Recalls & Safety path parameters
Parameter
Type
Required
Description
vin
stringExactly 17 letters or digits; I, O and Q are not allowed
Required
Vehicle Identification Number. Case-insensitive; echoed back upper-cased. North American VINs whose first character is 1–5 must pass their check digit.
{
"count": 5,
"make": "Toyota",
"model": "Highlander",
"recalls": [
{
"component": "EQUIPMENT:OTHER:LABELS",
"summary": "Gulf States Toyota, Inc. (GST) is recalling certain 2022 4Runner, Tacoma, Highlander, and Highlander Hybrid vehicles. The load carrying capacity modification labels may not be permanent and can fade, becoming illegible.",
"consequence": "An illegible label may allow the vehicle to be overloaded, increasing the risk of a crash.",
"remedy": "GST will notify owners by mail, including a replacement load carrying capacity modification label for their vehicle and detailed replacement instructions, free of charge.",
"campaign_number": "22V310000",
"report_date": "06/05/2022"
},
{
"component": "STRUCTURE:BODY:BUMPERS",
"summary": "Toyota Motor Engineering & Manufacturing (Toyota) is recalling certain 2020-2023 Highlander & Highlander Hybrid vehicles. During normal vehicle operation, minor impact to the front lower bumper cover may result in the cover coming loose or detaching.",
"consequence": "A detached front bumper cover can become a road hazard, increasing the risk of a crash.",
"remedy": "Dealers will repair or replace the upper and lower front bumper covers as necessary, free of charge.",
"campaign_number": "23V720000",
"report_date": "26/10/2023"
}
],
"source": "carscrape",
"vin": "5TDDZRBHXNS221317",
"year": 2022
}
Response fields
Recalls & Safety response fields
Field
Type
Description
count
integer
Number of campaigns returned; always equal to recalls.length.
make
string
Make the lookup ran against, as resolved from the store or vPIC.
model
string
Model the lookup ran against, as resolved from the store or vPIC.
recalls
array<object>
One entry per NHTSA campaign with component, summary, consequence, remedy, campaign_number and report_date. Individual values may be null.
source
"carscrape"
Constant provenance marker for the backing data service.
vin
string
The upper-cased VIN that was looked up.
year
integer
Model year the lookup ran against.
Errors
Recalls & Safety endpoint-specific errors
Status
code
Retryable
When it happens
400
invalid_vin
No
The backing validator rejected the VIN. North American VINs must pass their check digit.
404
recalls_not_resolvable
No
A valid VIN could not be resolved to year/make/model from either the store or vPIC, so no recall query could be made.
503
recalls_upstream_unavailable
Yes
The recalls service was unreachable or the 15 s budget elapsed.
503
recalls_upstream_error
Yes
An unexpected upstream status, notably when the NHTSA recalls API or the fallback vPIC decode is down.
Recalls are matched at the year/make/model level, not per VIN. A campaign may name sibling models or a narrower build range ("certain 2022 4Runner, Tacoma, Highlander…") and does not prove this specific VIN is affected — confirm the campaign number with the manufacturer.
"count": 0 with HTTP 200 is a normal, chargeable result meaning the vehicle resolved and NHTSA lists no campaigns for it.
report_date is passed through verbatim from NHTSA and its formatting is inconsistent across campaigns — both 06/05/2022 and 26/10/2023 appear in live data. Do not assume a single date format.
Any of the six per-recall fields can be null when NHTSA omits them, and resolution depends on our normalized make/model matching NHTSA's vocabulary.
What the vehicle is worth today, how that value decays, and what it costs to run.
GET/v1/vehicles/market-valuemarket_value
Market Value
Returns an ML-predicted current market asking price in USD for a year/make/model, optionally sharpened with mileage, trim, state and other attributes, plus the model's median absolute percentage error.
Starter
$0.015
Pro
$0.015
Scale
$0.01
Per
model estimate
Timeout
5 s
Data source
A gradient-boosted asking-price model trained on our scraped US dealer-listings store. The estimate is a market ASKING price from live dealer listings, not a realized transaction price.
Query parameters
Market Value query parameters
Parameter
Type
Required
Description
make
string1–64 characters
Required
Vehicle make. Canonicalized upstream, so casing is forgiving here.
model
string1–64 characters
Required
Vehicle model. NOT canonicalized — must match the store's canonical casing ("Camry", "F-150", "4Runner") or the model treats it as an unseen category.
year
integer1900–2100
Required
Model year. Vehicle age is derived as max(currentYear − year, 0).
miles
integerminimum 0
Optional
Odometer reading. Also drives a derived miles_per_year feature.
trim
string1–64 characters
Optional
Trim level, for example "SE" or "Limited".
state
stringexactly 2 characters
Optional
US state code. Must be UPPERCASE ("TX"); lowercase is an unseen category and behaves identically to omitting it.
condition
string1–16 charactersdefault used
Optional
Vehicle condition.
drivetrain
string1–16 characters
Optional
Drivetrain, for example "awd", "fwd" or "4wd".
fuel
string1–32 characters
Optional
Fuel type, for example "gasoline", "hybrid" or "electric".
transmission
string1–32 characters
Optional
Transmission type, for example "automatic".
body_style
string1–32 characters
Optional
Body style, for example "sedan", "suv" or "pickup".
color
string1–32 characters
Optional
Exterior color; normalized upstream to a base color.
base_msrp
integerminimum 0
Optional
Original MSRP in USD, used as a model feature when known.
Echo of the non-null feature vector the model actually scored — the fastest way to confirm which of your inputs were used. Null features are omitted, and miles_per_year can be fractional.
medianApePct
number
Median absolute percentage error of the loaded pricing model on holdout data. 3.7 means a typical estimate lands within about 3.7%.
source
"carscrape"
Constant provenance marker for the backing data service.
Errors
Market Value endpoint-specific errors
Status
code
Retryable
When it happens
500
valuation_upstream_error
No
The valuation service returned an unexpected non-2xx status.
503
valuation_model_unavailable
Yes
No trained pricing model is currently loaded.
503
valuation_upstream_unavailable
Yes
The valuation service was unreachable or the 5 s budget elapsed.
There is no 404 path. The model always returns a number, even for a make/model it has never seen — always inspect inputs to confirm which of your parameters were actually recognized.
Casing matters. Verified on live data for a 2021 Camry SE at 45,000 miles: model="Camry" with state="TX" returns $26,096, model="camry" returns $23,882, and state="tx" returns $21,248 (identical to omitting state). Send Title-Case model and uppercase state.
Omitting miles materially lowers accuracy: it also drives a derived miles_per_year feature.
medianApePct is a model-wide holdout metric, not a per-vehicle confidence score.
Returns a per-model resale-value curve: MSRP retention observed by model year, a smoothed log-linear retention curve extrapolated across ages 0–12, and a single fitted annual decay rate.
Starter
$0.015
Pro
$0.015
Scale
$0.01
Per
model forecast
Timeout
5 s
Data source
A precomputed depreciation table built from median scraped listing prices joined against model-year MSRP. Live coverage today is 1,732 model-year rows across 223 make/model pairs and 28 makes, model years 2013–2026.
Query parameters
Depreciation query parameters
Parameter
Type
Required
Description
make
string1–64 characters
Required
Vehicle make. Matched with exact, case-sensitive equality, so canonical Title Case is required: "Toyota", "Mercedes-Benz", "Jeep". "toyota" returns 404.
model
string1–64 characters
Required
Vehicle model, also exact and case-sensitive: "Camry", "Grand Cherokee", "4Runner", "S-Class". No fuzzy matching or trim stripping.
This is a MODEL-level curve, not VIN- or trim-level. There are no year, mileage or trim parameters, and the same curve comes back regardless of the specific car.
Array elements keep upstream snake_case keys (n_listings, median_price, curve_beta) while the top-level envelope is camelCase. That asymmetry is intentional.
curveByAge retention is capped at 1.0, but byModelYear.retention can approach or exceed 1.0 for current model years where the median listing price runs above base MSRP — the 2026 Camry row shows median_price 34,779 against msrp 33,058.
A small number of stored models have no fitted curve_beta. For those, annualDecay and curveByAge are both null while byModelYear is still populated.
Returns EPA-sourced annual and five-year fuel cost, combined MPG, fuel type and tailpipe CO2 for a year/make/model. Fuel and operating cost only — maintenance, insurance and depreciation are excluded.
Starter
$0.045
Pro
$0.045
Scale
$0.035
Per
model estimate
Timeout
15 s
Data source
Two live calls to EPA fueleconomy.gov per request: one to resolve year/make/model to trim configurations, then one for the first configuration returned. Not scraped data, and not cached.
Query parameters
Ownership Costs query parameters
Parameter
Type
Required
Description
make
string1–64 characters
Required
Vehicle make. EPA lookup is case-insensitive; the value is echoed back verbatim.
model
string1–64 characters
Required
Vehicle model as EPA names it ("Camry", "F150 Pickup 2WD"). Case-insensitive; echoed back verbatim.
year
integer1900–2100; EPA coverage starts at 1984
Required
Model year. Earlier years fail upstream rather than returning a clean 404.
EPA estimated annual fuel cost in USD. Null when EPA omits the field.
co2GramsPerMile
integer | null
Tailpipe CO2 in grams per mile, truncated to an integer. 0 for battery-electric vehicles.
combinedMpg
integer | null
EPA combined city/highway MPG, truncated to an integer. MPGe for EVs.
config
string | null
Human-readable label of the specific EPA trim configuration that was priced, for example "Auto (S8), 6 cyl, 3.5 L".
fiveYearFuelCostUsd
integer | null
annualFuelCostUsd × 5. Not inflation- or discount-adjusted.
fuelType
string | null
EPA fuel label, for example "Regular", "Premium" or "Electricity".
make
string
Echo of the requested make, verbatim.
model
string
Echo of the requested model, verbatim.
trimsAvailable
integer
Number of EPA trim configurations that exist for this year/make/model. Greater than 1 means the returned numbers describe only the config trim.
note
string
Fixed disclaimer string, present on every response.
source
"carscrape"
Constant provenance marker for the backing data service.
year
integer
Echo of the requested year.
Errors
Ownership Costs endpoint-specific errors
Status
code
Retryable
When it happens
404
ownership_not_found
No
EPA returned a well-formed menu with zero trim options. This is rarer than you would expect — see the note below.
503
ownership_upstream_unavailable
Yes
The service was unreachable or the 15 s budget elapsed.
503
ownership_upstream_error
Yes
In practice this is the most common failure for bad inputs: an unknown model or a pre-1984 year makes EPA return an empty body, which surfaces here as 503 rather than 404.
Fuel only. No maintenance, insurance, tires, taxes, fees or depreciation are included — which is why the fixed note string ships in every response. Pair this with Depreciation for a fuller total-cost-of-ownership picture.
The figures describe the first EPA trim configuration only. When trimsAvailable is greater than 1, treat that as a signal that costs vary across trims. For the 2021 Camry, EPA resolves the V6 configuration, not the best-selling four-cylinder.
Unlike Depreciation, make and model matching is case-insensitive here because EPA does the lookup — but make and model are echoed back exactly as you sent them, unnormalized.
annualFuelCostUsd assumes EPA's 15,000 mi/yr at current national fuel prices, and fiveYearFuelCostUsd is simply that figure times five with no inflation or discounting.
Two live EPA round trips per call with no caching means latency is upstream-bound against a 15 s route budget.
Models the cost of owning a vehicle over a holding period, component by component, with each component tagged by how much we actually know about it.
Starter
$0.06
Pro
$0.045
Scale
$0.035
Per
model estimate
Timeout
15 s
Data source
A composite. Depreciation comes from our own dealer-asking-price curves; fuel from the EPA combined rating with the fuel price implied by the EPA annual cost; taxes and fees from the state rate table; financing from the loan calculator. Maintenance, repairs and insurance are class-level planning assumptions, not observations of the vehicle.
Query parameters
Total Cost of Ownership query parameters
Parameter
Type
Required
Description
year
integer1900–2100
Required
Model year of the vehicle.
make
stringExact and case-sensitive
Required
Vehicle make.
model
stringExact and case-sensitive
Required
Vehicle model.
purchase_price
number> 0
Optional
Purchase price. Defaults to the model year's median asking price when we hold one.
years
integer1–10; defaults to 5
Optional
Holding period in years.
annual_miles
integer100–200000; defaults to 12000
Optional
Miles driven per year. Fuel, maintenance and repairs all scale with it.
state
stringTwo-letter state code
Optional
Registration state, used for the purchase tax and fee component.
apr
number0–1
Optional
Nominal annual loan rate as a decimal. Financing interest is omitted entirely when absent.
down_payment
number≥ 0
Optional
Cash down, used only to size the financed amount.
loan_months
integer1–120; defaults to 60
Optional
Loan term in months.
maintenance_per_mile
number≥ 0
Optional
Replace the class-level maintenance assumption with your own USD per mile.
repair_per_mile
number≥ 0
Optional
Replace the class-level repair assumption with your own USD per mile.
insurance_per_year
number≥ 0
Optional
Replace the class-level insurance assumption with your own USD per year.
Read `components[].basis` before trusting a number. `measured` is our depreciation model, `published` is EPA, `computed` is loan arithmetic, `caller_supplied` is your own input, and `reference` is a national class-level assumption that is NOT an observation of this vehicle.
`measuredShareOfTotal` tells you what fraction of the total does not rest on a reference assumption. A low value means most of the estimate is a planning average.
A component we cannot compute is null with basis `unavailable` and is EXCLUDED from the total, never counted as zero. `missingComponents` lists them.
Maintenance, repair and insurance can each be replaced with your own figures; do that whenever you have real data, because insurance in particular is driven far more by driver and coverage than by vehicle class.
Depreciation inherits the make/model scope of our depreciation curves, so it is not trim- or VIN-specific.
`apr` is the nominal annual rate as a decimal compounded monthly (0.069 for 6.9%), which is how US auto loans quote and amortize.
Negative trade-in equity is FINANCED, not subtracted: when trade_in_payoff exceeds trade_in_value, the shortfall increases the amount financed. Getting this backwards understates the loan by twice the negative equity.
A price fully covered by down payment, rebate and trade equity returns a zero loan rather than a negative one.
Excludes gap insurance, service contracts, and lender-specific fees. Add them through `fees` if you want them financed.
Estimates purchase sales or use tax, including the per-state trade-in credit rule, plus title, registration and documentation fees.
Starter
$0.001
Pro
$0.0007
Scale
$0.0005
Per
calculation
Timeout
5 s
Data source
A state-level reference table of motor-vehicle sales/use tax rates and trade-in credit rules, plus national planning averages for title, registration and documentation fees.
Query parameters
Purchase Tax & Fees query parameters
Parameter
Type
Required
Description
price
number> 0
Required
Vehicle sale price.
state
stringTwo-letter state code
Optional
State of registration. Determines both the rate and the trade-in rule.
trade_in_value
number≥ 0
Optional
Trade-in allowance. Reduces the taxable amount only where the state permits it.
rebate
number≥ 0
Optional
Cash rebate applied before tax.
tax_rate
number0–1
Optional
Override the reference rate, for example to include a known local surtax. The state's trade-in rule still applies.
title_and_registration
number≥ 0
Optional
Replace the national title and registration planning average.
doc_fee
number≥ 0
Optional
Replace the national documentation fee planning average.
This is an estimate, not tax advice. Confirm with the state revenue department or the selling dealer before relying on a figure.
Whether a trade-in reduces the taxable amount is a per-state rule and is the part of this worth having: it moves the tax by the trade-in value times the rate, which dwarfs rate precision on any real deal.
State-level rates only. County, city and special-district surtaxes are excluded and are material in several states.
An unknown state returns a null taxRate and a null salesTax rather than a guessed rate. Pass `tax_rate` to supply your own.
`rateTableAsOf` dates the table. Rates change by legislation; treat a stale date as a reason to verify.
The underlying marketplace: live listings and their imagery.
GET/v1/vehicles/listingslive_listings
Search Vehicle Listings
Search the normalized US car-listings store by make, model, year, price, mileage and state, and return a paginated page of listing rows plus the total match count.
Starter
$0.002
Pro
$0.0015
Scale
$0.001
Per
successful API call
Timeout
5 s
Data source
Our normalized US car-listings store, scraped from autolist, truecar, iseecars and dealer JSON-LD sites and joined against the canonical vehicles table.
Query parameters
Search Vehicle Listings query parameters
Parameter
Type
Required
Description
make
string1–64 characters
Optional
Canonical make, exact and case-sensitive: "Ford", "Toyota".
model
string1–64 characters
Optional
Canonical model, exact and case-sensitive: "F-150", "Camry".
year_min
integer1900–2100
Optional
Minimum model year, inclusive.
year_max
integer1900–2100
Optional
Maximum model year, inclusive.
price_min
integerminimum 0
Optional
Minimum current asking price in USD, inclusive.
price_max
integerminimum 0
Optional
Maximum current asking price in USD, inclusive.
mileage_max
integerminimum 0
Optional
Maximum odometer reading in miles, inclusive.
state
stringexactly 2 characters
Optional
US state code of the listing location. Upper-cased server-side, so "ct" and "CT" behave identically.
condition
string1–32 characters; "used", "new", "cpo" are present in the store
Optional
Listing condition, exact match.
seller_type
string1–32 characters; "dealer" and "auction" are present in the store
An empty search is not a 404. It returns 200 with total 0, count 0 and results [] — and it is still a billable successful call.
The route always applies the mainstream, priced segment. Auction sources (bringatrailer, carsandbids) and call-for-price rows are never returned, and there is no geo-radius search. Consequently sold=true currently matches nothing, because realized auction sales only exist in the excluded auction segment.
titleStatus reflects the marketplace's own salvage flag, not a DMV title record: "clean" means the source explicitly did not flag the car as salvage, which is weaker than a verified clean title. It is null where the source said nothing either way. Frame damage is deliberately not folded in, because a frame-damaged car can still hold a clean title.
zip is null for nearly all rows because our sources do not expose it, and miles is null on some rows.
days_on_market counts days since we first observed the listing, not the dealer's own listing age.
Paginate with limit and offset against total for a single page, or pass the previous response's nextCursor as cursor to walk the whole set. The cursor is keyset-based, so rows written between pages cannot duplicate or skip results the way offset can. There are no Link headers.
Set facets=true to get the top values and counts per field over the same filtered set, in the same call.
Returns the photo gallery for a VIN in the source's own order, the dealer's advertised photo count, and a link back to the source listing.
Starter
$0.001
Pro
$0.0009
Scale
$0.0007
Per
successful lookup
Timeout
5 s
Data source
The listings table. We pick the VIN's best current listing that has an image — active listings win, then the most recently ingested. Image URLs are CDN links owned by the source marketplace and are not rehosted by us.
Path parameters
Vehicle Photos path parameters
Parameter
Type
Required
Description
vin
stringExactly 17 letters or digits; I, O and Q are not allowed
Required
Vehicle Identification Number. Upper-cased before the lookup. North American VINs whose first character is 1–5 must pass their check digit.
True only when galleryCount accounts for every photo photoCount reports. False means the source withheld part of the gallery, or the listing predates gallery capture.
galleryCount
integer
Number of URLs in photos. This is what we can actually serve, as opposed to what the source claims exists.
listingSource
string | null
Scrape source of the listing the photo came from, for example "autolist".
listingUrl
string | null
Vehicle detail page URL on the source marketplace.
photoCount
integer | null
Number of photos the source listing advertises. Null when the source exposed no count — this is NOT the number of URLs we return.
photos
string[]
The gallery in the source's own order; photos[0] is the hero image. Empty when no gallery was captured, which is not the same as the vehicle having no photos.
primaryImage
string
Absolute URL of the hero image. Never null on a 200.
source
"carscrape"
Constant provenance marker for the backing data service.
vin
string
The upper-cased VIN.
Errors
Vehicle Photos endpoint-specific errors
Status
code
Retryable
When it happens
400
invalid_vin
No
The backing validator rejected the VIN. North American VINs must pass their check digit.
404
photos_not_found
No
No listing with a primary image exists for that VIN.
503
photos_upstream_unavailable
Yes
The photos service was unreachable or the 5 s budget elapsed.
503
photos_upstream_error
Yes
An unexpected upstream status other than 404 or 422. This route returns no 500 — every unexpected status maps to 503.
Check galleryComplete before treating photos as the whole set. photoCount is the count the SOURCE advertised; galleryCount is how many URLs we can actually serve. They differ when the source publishes only a hero image, or when the listing was last crawled before gallery capture shipped.
An empty photos array with a non-zero photoCount means we know photos exist but do not hold their URLs. It does not mean the vehicle has no photos — follow listingUrl for the rest.
Gallery coverage grows as listings are re-crawled; older listings keep their hero image until they are seen again.
A VIN that appears in Search Vehicle Listings can still 404 here if its listing row has no primary image.
Image URLs point at third-party CDNs and can rot when the listing is taken down. Do not treat them as permanent — mirror anything you need to keep.
Connect Vehicles.dev to any MCP-capable AI agent — Claude Desktop, Claude Code, Cursor, or one you write yourself — so it can look up vehicle data by being asked, instead of by being programmed.
The server is a local process that speaks the Model Context Protocol over stdio. It exposes the 11 synchronous endpoints plus the durable report workflow as 15 tools, each carrying a description precise enough for a model to pick the right one and fill its arguments unaided. Your key stays in the server’s own environment: the agent never sees it, and every tool call is a direct request from your machine to https://api.vehicles.dev with nothing proxied in between.
Tool calls are metered exactly like direct callsThe eleven synchronous lookup tools use the same per-endpoint prices and successOnly rule as direct API calls. Report list, status, and result reads do not create another report charge. Ordering is a separate Pro or Scale action: the MCP tool requires an explicit confirmation and caller-supplied UUID, then billing settles only after canonical JSON completes. An agent answering one question may make several lookups, so usage is visible in the dashboard.
Setup
The package is vehicles-dev-mcp, published on npm under the MIT licence. The configurations below run it with npx, so there is nothing to install or build first — your MCP client fetches it on launch. Node.js 20 or newer is required.
shell · optional
npm install -g vehicles-dev-mcp
Installing globally puts a vehicles-dev-mcp binary on your PATH, which you can use in place of npx in any configuration below. It is only worth doing if you would rather pin the version yourself than let your client fetch the latest on launch.
Environment
MCP server environment variables
Variable
Type
Required
Description
VEHICLES_API_KEY
string
Optional
Your Vehicles.dev API key, sent as Authorization: Bearer <key> on every tool call. Optional if you have run vehicles login — the server then reads the key from ~/.vehicles/credentials.json; set it explicitly for servers and CI. With neither, the server prints how to fix it on stderr and exits non-zero rather than failing every call with a 401. The key never reaches stdout, and it is redacted out of any text a tool returns.
VEHICLES_API_BASE_URL
stringabsolute http or https URLdefault https://api.vehicles.dev
Optional
Origin the /v1/vehicles/ paths are resolved against. Only useful for pointing the server at a staging deployment.
VEHICLES_API_TIMEOUT_MS
integer1–600000default each tool's own budget: 10, 15 or 20 s
Optional
Replaces every per-tool timeout. By default each tool allows its endpoint's upstream budget plus 5 s of headroom, so a slow call still returns the API's problem document instead of a bare client-side timeout.
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json on macOS (on Windows, %APPDATA%\Claude\claude_desktop_config.json), then restart the app — it reads this file only at launch.
If the tools do not appear, the reason is on stderr in ~/Library/Logs/Claude/mcp-server-vehicles-dev.log. A missing or blank VEHICLES_API_KEY is the usual cause, and the server names the fix there rather than starting in a broken state.
Add --scope project to write a committable .mcp.json instead of your personal configuration. Verify with claude mcp list, or /mcp inside a session. Note the argument ordering: never put the server name directly after --env — keep another flag (here --transport) between them.
Cursor, VS Code and everything else
Any client that launches a stdio MCP server works. Cursor reads .cursor/mcp.json using the same object shape as Claude Desktop above; VS Code 1.99+ in Agent mode reads .vscode/mcp.json, which nests servers under a servers key and wants an explicit "type": "stdio" alongside command, args and env. The command, arguments and environment are identical in all three.
Tools
14 read-only GET tools are annotated readOnlyHint. The remaining report-order tool is idempotent and makes no API call unless confirm_order=true; callers must supply and reuse a stable UUID. Successful results preserve the API JSON as both text and structured content. A non-2xx becomes an isError result carrying structured status, machine code, request_id, retry timing, and a recovery hint so an agent can repair or resume the workflow safely.
Order a confirmed Pro or Scale report, or resume the same logical order with its stable idempotency key.
A worked example
Ask your agent, in whatever words you like:
You
What's a 2022 F-150 with 20k miles worth?
The agent matches the question against the 15 tool descriptions and selects get_market_value on its own. You never name the tool, and nothing about the wording has to be exact.
It fills the tool's schema from your sentence. The schema states that model is matched case-sensitively against our canonical naming, so it sends "F-150" rather than "f150", and it converts "20k miles" to the integer 20000.
The server issues one GET to https://api.vehicles.dev/v1/vehicles/market-value with your key on the Authorization header, and returns the response body to the agent verbatim.
The agent reads estimateUsd for the number and the inputs echo to confirm which of its arguments the model actually scored, then answers in prose — normally flagging that this is an asking price from live dealer listings rather than a sale price, because the tool description says so.
Add a state and it improves: "…worth in Texas?" makes the agent send state: "TX", uppercase, as the schema requires. Ask "and what is actually listed near me?" and it follows up with search_listings — a second tool call, and a second billable request.
The Claude API can attach a remote MCP server to a message — Anthropic makes the connection server-side, so no local process is involved. That requires an MCP endpoint reachable over HTTPS. The Vehicles.dev deployment is configured for https://mcp.vehicles.dev/mcp; the Render service, DNS and TLS must be live before clients can use it.
Your key, your accountSet authorization_token to your vdev_ API key. Anthropic forwards it as a Bearer credential on each MCP request; the hosted service forwards that same key to the Vehicles.dev API. It does not use a shared account or retain your key between requests, so your own plan, rate limits, credits and successful-call metering still apply.
The request has two halves that must agree. mcp_servers declares the connection; a matching mcp_toolset entry in tools grants access to it. Both are required — omitting the toolset is a validation error, not a silent no-op — and the beta header mcp-client-2025-11-20 gates the whole feature.
mcp_server_name must match the name on the server entry exactly; that string is how the toolset binds to the connection. Keep the credential in authorization_token, never in the prompt. If you prefer to keep the key and MCP connection entirely on your own machine, the stdio setup above remains available.
Conventions
Money. Data endpoints return whole US dollars — estimateUsd, annualFuelCostUsd, listing price. Billing and pricing figures are quoted in USD micros elsewhere in the platform, where 1,000,000 micros is $1. Do not mix the two.
Naming. The top-level envelope is camelCase. Pass-through objects and arrays — vehicle, specifications, inputs, results, observations, recalls, identity, byModelYear elements — keep their upstream snake_case keys. That asymmetry is deliberate and stable.
Nulls.null is a first-class “unknown”. Some objects omit null keys entirely rather than emitting them, so treat every nested field as optional.
Dates. ISO-8601 strings throughout. The one exception is recall report_date, which NHTSA supplies in inconsistent formats and we pass through verbatim.
Pagination. Only Search Vehicle Listings paginates, via limit (1–500, default 50) and offset against the total match count. No cursors, no Link headers. Every other endpoint is a single-object lookup.
Query handling. Query strings are coerced to the declared integer and boolean types, and unknown parameters are rejected rather than dropped. VINs in the path are upper-cased server-side.
Timeouts. Each endpoint has an upstream budget of 5, 10 or 15 seconds, listed on the endpoint. Set your client timeout above the endpoint’s budget so you receive the API’s 503 rather than timing out blind.
Idempotency. Every data endpoint is a GET and safe to repeat. The metering layer is internally idempotent on the request id, so a retry never double-charges a single request.
Versioning, availability & freshness
What we will change, what it costs when we change prices, what we do not promise about uptime, and how current the data actually is. Read this before you put the API on a critical path.
Versioning & changes
There is one API version. The /v1/ path prefix is the contract — there is no version header, no date-pinned version, and no per-account pinning.
Additive changes ship without notice. New endpoints, new fields on an existing response, new optional query parameters and new code values on an existing status can appear at any time. Parse defensively: ignore unknown JSON fields, and fall back to the HTTP status when you meet a code you do not recognize.
Breaking changes are removing an endpoint, removing or renaming a response field, adding a required parameter, tightening an existing parameter’s constraints, changing a field’s type, or changing what an existing code means. Note that a nested value going null — or a key disappearing from an object that omits nulls — is not a breaking change; every nested field is documented as optional for that reason.
There is no changelog page and no contractual deprecation window on self-serve plans today. If your integration needs advance written notice before a breaking change, email support@vehicles.dev and we will agree a notice period in writing rather than leaving you to discover it in production.
When prices change
The 2026-08-18 shown as Price catalog in the header is the version of the pricing catalog this page was generated from. Prices are not quoted live at call time: your subscription pins a catalog version, every metered event records the unit price that was actually applied, and a new catalog is adopted at the start of your next UTC monthly period. A price change therefore never reprices calls you have already made, and it never takes effect mid-period.
Availability
We publish no uptime target, status page or incident channel today, and self-serve plans carry no SLA. What you get instead is explicit failure: every dependency outage surfaces as a documented 503 with retryable: true rather than as a hang or a silently degraded body, upstream calls are bounded by the per-endpoint timeout listed above, and the x-request-id on every response is how we trace an incident. Report an outage to support@vehicles.dev with a request id and a UTC timestamp. If you need a contractual uptime commitment, ask before you build.
Data freshness
Live upstream, uncached. Vehicle Specifications, Recalls & Safety and Ownership Costs call NHTSA vPIC, the NHTSA recalls API and EPA fueleconomy.gov on every request, as does the "vpic" fallback inside VIN Decode. They are exactly as current — and as available — as those agencies are.
Store-backed, crawl-paced. Listings, Photos and the store-origin path of VIN Decode come from our own continuously running crawl. We do not publish a maximum staleness, and no listings field carries a crawl timestamp: is_active tells you the row was still live at the last crawl pass that covered it, not when that pass ran, and days_on_market counts from our first observation rather than the dealer’s listing date.
Precomputed. Depreciation is a periodically rebuilt table and Market Value is a periodically retrained model; both are refreshed on our schedule rather than per request, so two identical calls minutes apart will agree.
If your use case needs a hard freshness bound or a per-row crawl timestamp, say so before you build on these endpoints — neither is available through the API today.
the x-request-id (or request_id) of a failing call — this is the single most useful thing you can send;
the approximate UTC timestamp of the request;
the endpoint and the parameters you sent, with the API key redacted to its last four characters.
Never send us a full API key. If one has leaked, revoke it in the dashboard and mint a replacement — revocation is immediate.
Usage terms
Standard plans cover use inside your own product or internal systems. Bulk redistribution or resale of raw records needs a separate written agreement.
Photo URLs point at CDNs owned by the source marketplace. We link to them; we do not license the imagery, and it is not ours to sublicense.
Specifications, recalls and ownership costs are derived from public NHTSA and EPA data and are passed through with their own accuracy limits, which are described on each endpoint.
This page is the current statement of usage rights — there is no separate terms-of-service, acceptable-use or DPA page yet. For a redistribution licence, a data-processing agreement, or vendor-security paperwork, email support@vehicles.dev and we will put it in writing.
Machine-readable spec
The OpenAPI 3.1 document is generated from the same schemas that validate every request and is served unauthenticated at https://api.vehicles.dev/openapi.json. It declares https://api.vehicles.dev as its server, so you can import it into Postman or Insomnia, or point a client generator at it, without hand-editing a base URL.
It describes the whole platform, not only VehiclesOne document covers every route the API process exposes: the 11 synchronous /v1/vehicles/* operations documented here, the asynchronous /v1/vehicles/history-reports workflow, plus the /v1/control/* dashboard control plane, the /v1/ops/* operator routes, the payment webhook, the health probes, and the other product’s data endpoints. Only /v1/vehicles/* is callable with a vdev_ key — the control-plane and operator routes require a dashboard session token and reject an API key with 401 invalid_credential. Generate clients from the /v1/vehicles/ paths and drop the rest.