
You need to turn a VIN into reliable vehicle specs and US safety context in one call. By the end of this guide you’ll POST a VIN to the Vehicle API VIN Lookup endpoint, parse specs plus US recalls, owner complaints count, and NCAP ratings, handle nulls and errors correctly, cache decoded VINs, and ship this into your dealer, marketplace, fleet, insurance, or agent workflow.
What the VIN Lookup endpoint does (and doesn’t)
The VIN Lookup API aggregates VIN decoding (year, make, model, trim, engine, transmission, assembly plant) and official US safety data in a single call. It returns:
- VIN decoding from NHTSA vPIC.
- US-market safety context: recall campaigns, owner complaints count, and NCAP crash ratings (matched by make + model + year).
Coverage notes you should build around:
- VIN decoding works worldwide but is most complete for US-market vehicles.
- Recalls, complaints counts, and NCAP ratings are US-market only. Non-US VINs still respond 200 with specs only.
- Dutch plates are supported separately via RDW open data and never include a VIN.
- This is not a vehicle history report. No title, accident, or ownership history is included.
Base URL for all calls: https://vehicles-api.com/api/v1. Authentication uses the X-API-Key header; Authorization Bearer also works. Explore more at vehicles-api.com and the Documentation.
Endpoint and request pattern
Endpoint:
- POST https://vehicles-api.com/api/v1/vin/report
Headers:
- X-API-Key: YOUR_API_KEY
- Content-Type: application/json
Body:
- JSON with a single field: vin
Official curl example
Copy, paste, and run. Keep YOUR_API_KEY as your real key when integrating.
curl -X POST "https://vehicles-api.com/api/v1/vin/report" -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" -d '{"vin":"1HGCM82633A004352"}'
JavaScript example: calling and parsing the response
This fetch example posts a VIN, validates the response envelope, and reads the fields you’ll commonly display or store.
async function lookupVin(vin) {
const url = "https://vehicles-api.com/api/v1/vin/report";
const res = await fetch(url, {
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json"
// Alternatively: Authorization: `Bearer YOUR_API_KEY`
},
body: JSON.stringify({ vin })
});
// Network and HTTP-level error check
if (!res.ok) {
// Handle 400/401/403/404/429/5xx classes as appropriate for your app
// Do not assume body shape on HTTP error; check docs for specifics
throw new Error(`HTTP ${res.status}`);
}
const payload = await res.json();
// Envelope check
if (payload.status !== "success") {
const code = payload?.error?.code || "unknown_error";
const msg = payload?.error?.message || "Unexpected API error";
throw new Error(`${code}: ${msg}`);
}
const { source, data } = payload;
// Core spec fields (always check for nulls)
const decoded = {
vin: data.vin,
valid: data.valid,
year: data.year,
make: data.make,
model: data.model,
trim: data.trim,
body: data.body,
doors: data.doors,
vehicleType: data.vehicle_type,
engine: {
cylinders: data.engine?.cylinders ?? null,
displacementL: data.engine?.displacement_l ?? null,
fuel: data.engine?.fuel ?? data.fuel_type ?? null
},
plant: {
country: data.plant?.country ?? null,
city: data.plant?.city ?? null,
state: data.plant?.state ?? null
},
manufacturer: data.manufacturer ?? null
};
// Safety (US-market only). For non-US coverage these may be null/empty.
const safety = {
coverage: data.coverage, // "us" for US coverage
recallsCount: data.recalls_count ?? 0,
recalls: Array.isArray(data.recalls) ? data.recalls : [],
complaintsCount: data.complaints_count ?? 0,
safetyRatings: data.safety_ratings ?? null
};
// Example: choose a single recall headline, if present
const topRecall = safety.recalls[0] ? {
campaign: safety.recalls[0].campaign,
component: safety.recalls[0].component,
summary: safety.recalls[0].summary,
received: safety.recalls[0].received
} : null;
return { source, decoded, safety, topRecall };
}
// Example usage
lookupVin("1HGCM82633A004352")
.then(({ decoded, safety, topRecall }) => {
console.log("Decoded:", decoded);
console.log("Safety coverage:", safety.coverage);
console.log("Complaints count:", safety.complaintsCount);
console.log("Ratings:", safety.safetyRatings);
if (topRecall) console.log("Top recall:", topRecall);
})
.catch(err => {
console.error("VIN lookup failed:", err.message);
});
Official JSON example and the fields you’ll actually use
The response is always an envelope with status and source. On success, data holds specs (global VIN decode) and, for US-market vehicles, safety fields aggregated from official sources.
{
"status": "success",
"source": "nhtsa",
"data": {
"vin": "1HGCM82633A004352",
"valid": true,
"year": 2003,
"make": "HONDA",
"model": "Accord",
"trim": "EX-V6",
"body": "Coupe",
"doors": 2,
"vehicle_type": "PASSENGER CAR",
"engine": {
"cylinders": 6,
"displacement_l": 3,
"fuel": "Gasoline"
},
"fuel_type": "Gasoline",
"electrification_level": null,
"ev_drive_unit": null,
"battery_kwh": null,
"charger_level": null,
"charger_power_kw": null,
"plant": {
"country": "UNITED STATES (USA)",
"city": "MARYSVILLE",
"state": "OHIO"
},
"manufacturer": "AMERICAN HONDA MOTOR CO., INC.",
"coverage": "us",
"recalls_count": 24,
"recalls": [
{
"campaign": "19V182000",
"component": "AIR BAGS:FRONTAL:DRIVER SIDE:INFLATOR MODULE",
"received": "2019-03-06",
"summary": "Replacement driver air bag inflator may explode.",
"consequence": "Sharp metal fragments may strike occupants.",
"remedy": "Dealers will replace the inflator, free of charge."
}
],
"complaints_count": 2013,
"safety_ratings": {
"match": "partial",
"vehicle_description": "2003 Honda Accord 2-DR.",
"overall": null,
"front": {
"driver": 5,
"passenger": 5
},
"side": {
"driver": 5,
"passenger": 5
},
"rollover": 4
}
}
}
Fields you’ll commonly wire into UI or downstream systems:
- status, source: Envelope status and the authoritative source used to decode.
- data.vin, data.valid: The VIN echoed back and whether it decodes as valid.
- data.year, make, model, trim, body, doors, vehicle_type: Core identity fields for listings, quoting, and search facets.
- data.engine.cylinders, data.engine.displacement_l, data.engine.fuel, data.fuel_type: Show engine configuration; prefer data.engine.fuel when present.
- data.plant.country, .city, .state and data.manufacturer: Manufacturing context and OEM.
- data.coverage: For US-market safety data, the value is "us". Non-US VINs may omit safety fields or return nulls.
- data.recalls_count and data.recalls[]: Use recalls_count for quick badges; show the array for detail views. The example includes a single campaign; live responses can include more.
- data.complaints_count: Aggregate count of consumer complaints from US sources.
- data.safety_ratings: NCAP crash ratings. Some subfields may be null when exact trim isn’t rated; check match and vehicle_description to communicate scope.
Nulls are normal. For example, electrification fields are null for a gasoline vehicle. Always null-check optional areas like safety_ratings.overall or electrification fields before rendering.
Response envelope, errors, and retries
Every response is wrapped in a top-level envelope:
- On success: status: "success", source, and data populated.
- On error: status: "error", with error.code and error.message.
Build your handler to branch on status. Do not assume safety fields are present when coverage is not "us". For HTTP-level failures (auth, throttling, upstream outage), expect a non-2xx status code and handle backoff where appropriate. See the Documentation for the canonical list of error codes and meanings.
Rate limits, monthly quota, and idempotency
Plans include rate limiting and monthly call quotas. Exact thresholds vary by plan and may change; consult your dashboard and the docs for current values. Recommended client behavior:
- Handle 429 Too Many Requests with exponential backoff and jitter.
- Cache decoded VIN results to minimize repeat calls (see next section).
- Batch and queue background refreshes rather than burst traffic on user actions.
The VIN Lookup POST is idempotent with the same input; you can safely retry on transient errors using a capped backoff strategy.
Caching decoded VINs
VIN decoding and static attributes (year, make, model, engine, plant, manufacturer) change rarely. Safety data (recalls, complaints count, NCAP) can be updated by data providers. Suggested cache strategy:
- Static spec cache: 30–90 days TTL. Invalidate if you detect a different source or parsing change.
- Safety cache: 24–72 hours TTL for recalls, complaints_count, safety_ratings to balance freshness with rate limits.
- Key by vin and normalize to uppercase for consistent lookups.
- Store a snapshot of the envelope source and the decoded payload version in case your UI logic depends on subtle field presence.
Remember that non-US VINs will not include US safety data; don’t spin on refresh loops for fields that won’t populate outside the covered market.
Handling nulls and partial matches
Practical rendering rules to prevent broken UI states:
- If data.valid is false or missing, show a clear message and skip downstream calls.
- Guard electrification fields (electrification_level, ev_drive_unit, battery_kwh, charger_level, charger_power_kw) with null-aware checks.
- For safety_ratings, use vehicle_description and match to explain when ratings are for a close configuration rather than an exact trim. If overall is null, display available sub-ratings (front, side, rollover) individually.
- Treat empty recalls arrays and 0 counts as valid “no active recalls found” states, distinct from null when coverage is not "us".
Integration notes for AI agents and back office tools
AI agents using the HTTP endpoint should prefer the minimal JSON subset needed for the task and apply strict schema checks. If you are integrating over the Model Context Protocol, an MCP server is available at MCP to expose the same capabilities behind a tool interface.
For marketplaces and dealer tools, push the decoding into your ingestion pipeline and attach the decoded struct to the listing model. For fleets or insurance quoting, store and reuse decoded specs as a canonical “vehicle profile” object keyed by VIN, and refresh the safety slice on a schedule.
Security and authentication
Send your API key via X-API-Key. Authorization: Bearer YOUR_API_KEY also works if your HTTP client prefers it. Do not embed keys in client-side apps; proxy calls through your backend. Rotate keys per environment (dev, staging, prod), and isolate quotas per application where possible.
US safety coverage and Dutch plates
US recalls, owner complaints counts, and NCAP ratings are included when the VIN decodes to a make + model + year with US coverage. When a VIN decodes to a non-US market, the API still responds 200 with specs only and may omit or null safety fields. Dutch plates are available via RDW open data and never include a VIN; if you are building plate-first flows for the Netherlands, use the plate endpoints and join to VIN-free specs accordingly. Learn more about the platform at vehicles-api.com.
Testing with the control VIN
For sandbox-style verification of your parsing and UI states, the docs example uses VIN 1HGCM82633A004352 (a 2003 Honda Accord). Do not hardcode expectations around live counts; recall campaigns, complaints counts, and ratings are updated as NHTSA and RDW publish new data. Your code should rely on field presence and null checks rather than fixed numbers.
Operational checklists
Minimum viable integration
- POST /vin/report with X-API-Key and JSON body containing vin.
- Check payload.status === "success"; on error, surface error.code and error.message.
- Store vin, valid, year, make, model, trim, body, doors, vehicle_type, engine, plant, manufacturer.
- When coverage === "us", display recalls_count, complaints_count, and safety_ratings with null checks.
Error handling
- HTTP failures: implement retries with capped exponential backoff.
- Throttling: handle 429 with backoff and respect your plan’s concurrency guidance.
- Input validation: normalize VINs to uppercase and trim whitespace.
Performance
- Cache decoded VINs by vin key.
- Pre-render common trims in catalogs to warm caches for high-traffic listings or quotes.
Common field mapping cheatsheet
- Identity: data.year, data.make, data.model, data.trim
- Body and class: data.body, data.doors, data.vehicle_type
- Engine: data.engine.cylinders, data.engine.displacement_l, data.engine.fuel (fallback: data.fuel_type)
- Plant and OEM: data.plant.country, data.plant.city, data.plant.state, data.manufacturer
- Safety (US only): data.coverage, data.recalls_count, data.recalls[], data.complaints_count, data.safety_ratings
Where to go next
Sign up for the 7-day free trial and start testing your VIN flows against production-grade data: Register. If you’ll call the API from an AI runtime, also check the MCP server: MCP.
FAQ
Does the VIN Lookup API return title, accident, or ownership history?
No. This endpoint is not a vehicle history report. It provides decoded specs plus US recalls, complaints count, and NCAP ratings.
Will a non-US VIN return an error?
No. A non-US VIN returns 200 with specs only. Safety fields may be omitted or null when coverage is not US.
How should I handle missing safety_ratings.overall?
Use available sub-ratings (front, side, rollover) and show vehicle_description and match to clarify the scope. Always check for nulls.
What are the rate limits and monthly quota?
All plans include rate limiting and a monthly quota; values vary by plan. Handle 429 with backoff and consult your dashboard and the Documentation for the current limits.
Can I use this API with AI agents?
Yes. Call the HTTP endpoint directly or use the Model Context Protocol server at MCP to expose VIN lookup as a tool.





