
You need a reliable way to surface owner-reported safety issues for a vehicle identified by its VIN so your marketplace, dealer, fleet, insurance, or AI workflow can flag risk. By the end of this guide, you’ll be able to call the Vehicle Complaints endpoint, parse the response envelope, display the 50 most recent NHTSA complaints along with the total count, handle coverage and error edge cases, and cache results efficiently.
What this endpoint delivers and when to use it
The Vehicle Complaints endpoint returns owner complaints filed with NHTSA’s Office of Defects Investigation (ODI) that match the vehicle’s year, make, and model. It’s designed for Transportation use cases where you need trustworthy US safety data, including:
- Dealer and marketplace listings: show complaint count and latest issues next to a VIN-decoded vehicle.
- Fleet and insurance scoring: incorporate complaint prevalence signals for underwriting and maintenance planning.
- AI agents: answer “what do owners report most often?” for a specific make-model-year context.
Coverage specifics:
- US-only for complaints: if the VIN is not a US-market vehicle, you receive a 422 error with code coverage_us_only.
- VIN decoding is global (not covered here), but complaints data are matched by make + model + year for US-market vehicles.
- Dutch plates (RDW open data) never include a VIN and are not part of this endpoint. This endpoint requires a VIN.
- This is not a vehicle history report. It does not include title, accident, or ownership history.
Endpoint and authentication
Base URL: https://vehicles-api.com/api/v1
Endpoint: GET /vin/complaints
Query parameters:
vin– The 17-character VIN. Example below uses the docs control VIN for a 2003 Honda Accord.
Authentication:
- Header
X-API-Key: YOUR_API_KEY(recommended), or Authorization: Bearer YOUR_API_KEY
Every response uses an envelope with status, source, and data on success, or status: "error" with an error object.
Read the full reference at the Vehicle API Documentation and explore the MCP server option at MCP.
cURL: one complete request you can copy
Official Vehicle API docs example. Owner complaints filed with NHTSA ODI: the 50 most recent, plus the true total in count. US-market coverage only: a valid non-US VIN returns 422 coverage_us_only here. Matched by make + model + year.
Source: docs_fixture
cURL:
curl "https://vehicles-api.com/api/v1/vin/complaints?vin=1HGCM82633A004352" -H "X-API-Key: YOUR_API_KEY"
Official JSON response envelope and field guide
JSON:
{
"status": "success",
"source": "nhtsa",
"data": {
"vin": "1HGCM82633A004352",
"year": 2003,
"make": "HONDA",
"model": "Accord",
"coverage": "us",
"count": 2013,
"complaints": [
{
"odi_number": "11459899",
"date": "2022-03-15",
"crash": false,
"fire": false,
"injured": 0,
"deaths": 0,
"components": [
"AIR BAGS"
],
"summary": "Driver airbag warning light illuminated."
}
]
}
}
Fields you’ll actually use
status: "success" on success. If "error", checkerror.codeanderror.message.source: "nhtsa" indicating the data provider for complaints.data.vin,data.year,data.make,data.model: Echoed context you can display in your UI or use for logging.data.coverage: "us" confirms that this dataset applies to US-market vehicles.data.count: Total number of complaints in NHTSA ODI for this make-model-year. This may be larger than the returned list length.data.complaints: Array of up to 50 most recent complaints with:odi_number: Stable NHTSA complaint identifier for linking or deduplication.date: Complaint date in YYYY-MM-DD format.crash,fire: Booleans indicating incident context.injured,deaths: Integers suitable for safety severity summaries.components: Array of component categories (e.g., "AIR BAGS"). Useful for grouping and filtering.summary: Short text description. Display carefully to users; it is free text from owners.
Important: The list is intentionally capped to the 50 most recent complaints. Use count to represent the complete population, and the returned array for latest context.
JavaScript example: fetch, parse, and render highlights
This sample calls the same endpoint, checks the standard response envelope, and maps fields you’ll likely render in a Transportation application.
async function fetchComplaints(vin) {
const url = `https://vehicles-api.com/api/v1/vin/complaints?vin=${encodeURIComponent(vin)}`;
const res = await fetch(url, {
headers: {
'X-API-Key': 'YOUR_API_KEY'
// Alternatively: 'Authorization': 'Bearer YOUR_API_KEY'
}
});
// Basic error handling for non-2xx
if (!res.ok) {
// Attempt to parse envelope-style error
let errBody;
try {
errBody = await res.json();
} catch (_) {
throw new Error(`HTTP ${res.status}`);
}
if (errBody && errBody.status === 'error' && errBody.error) {
const code = errBody.error.code || `HTTP_${res.status}`;
const msg = errBody.error.message || 'Unknown error';
throw new Error(`${code}: ${msg}`);
}
throw new Error(`HTTP ${res.status}`);
}
const body = await res.json();
if (body.status !== 'success' || !body.data) {
throw new Error('Unexpected response envelope');
}
const { vin: echoedVIN, year, make, model, coverage, count, complaints } = body.data;
// Example: aggregate top components from the returned 50 complaints
const componentCounts = {};
for (const c of complaints || []) {
for (const comp of (c.components || [])) {
componentCounts[comp] = (componentCounts[comp] || 0) + 1;
}
}
return {
echoedVIN, year, make, model, coverage,
totalCount: count,
recentSampleSize: (complaints || []).length,
componentCounts,
recent: complaints
};
}
// Usage
fetchComplaints('1HGCM82633A004352')
.then(data => {
console.log('Vehicle:', data.year, data.make, data.model);
console.log('Total complaints recorded by NHTSA for this make-model-year:', data.totalCount);
console.log('Complaints returned (most recent):', data.recentSampleSize);
console.log('Top components in recent subset:', data.componentCounts);
console.log('First item summary:', data.recent?.[0]?.summary);
})
.catch(err => {
console.error('Complaints fetch failed:', err.message);
});
Handling errors, coverage, and nulls
Error envelope: on failure, the API responds with { "status": "error", "error": { "code": "...", "message": "..." } }. Your handler should read both error.code and error.message and avoid assuming HTTP status codes convey full detail.
Coverage rule: if you query a valid non-US VIN for this endpoint, expect a 422 with error.code = coverage_us_only. For such cases, consider:
- Fallback UI: “Complaints data are available for US-market vehicles only.”
- In multi-region apps, use VIN decoding for global specs, but guard US-only safety features like complaints, recalls, and NCAP ratings.
Nulls and empties: some subfields (e.g., components, summary) can be empty or absent in older records. Defensive parsing is recommended:
- Treat missing arrays as empty arrays.
- Do not assume
summaryis always populated or well-formed text. - Numbers like
injured/deathsare integers; handle zero gracefully without special-casing truthiness.
Data shape: 50 most recent plus the total count
Vehicles with long production runs or high volumes often have thousands of complaints. This endpoint returns:
- Up to the 50 most recent complaint records under
data.complaints. - The true total under
data.count, which can be larger than 50.
There is no pagination in this response. If you need deeper historical slices for offline analytics, use the count to indicate prevalence in your UI and limit live views to recent context. For batch analytics beyond the 50 recent records, contact support through the Documentation.
Caching strategy and refresh cadence
Practical caching for Transportation apps:
- Cache by VIN key for the complaints endpoint.
- Because this list is “most recent,” it’s safe to cache for hours to a day for consumer UIs; fleet and insurance scoring jobs may refresh nightly.
- Invalidate or refresh when a listing is updated, or when your app detects material changes (e.g., a new model year selection in a form).
Remember that data.count can change as NHTSA publishes new complaints. If you show both the count and the latest 50, refresh them together to keep the UI consistent.
UI patterns that help users
- Badge + tooltip: show “Complaints: 2,013” with a tooltip “Showing the most recent 50 from NHTSA ODI.”
- Components filter: aggregate
componentsto summarize categories like “AIR BAGS,” “ENGINE,” “SERVICE BRAKES.” - Severity cue: highlight when
crashordeathsis non-zero. - Linking: when you store
odi_number, you can correlate external references in your system or allow staff to look up the record.
Python example: envelope-first parsing and safe defaults
import os
import requests
API_KEY = os.getenv('VEHICLES_API_KEY', 'YOUR_API_KEY')
BASE_URL = 'https://vehicles-api.com/api/v1/vin/complaints'
def get_complaints(vin: str) -> dict:
resp = requests.get(
BASE_URL,
params={'vin': vin},
headers={'X-API-Key': API_KEY}
# Or: headers={'Authorization': f'Bearer {API_KEY}'}
)
# Try to decode JSON regardless of status
try:
body = resp.json()
except ValueError:
resp.raise_for_status()
raise RuntimeError('Unexpected non-JSON response')
if body.get('status') == 'error':
err = body.get('error', {})
code = err.get('code', f'HTTP_{resp.status_code}')
message = err.get('message', 'Unknown error')
raise RuntimeError(f'{code}: {message}')
if body.get('status') != 'success' or 'data' not in body:
raise RuntimeError('Unexpected response envelope')
data = body['data']
complaints = data.get('complaints') or []
# Minimal projection for downstream use
return {
'vin': data.get('vin'),
'year': data.get('year'),
'make': data.get('make'),
'model': data.get('model'),
'coverage': data.get('coverage'),
'count': data.get('count', 0),
'recent': [
{
'odi_number': c.get('odi_number'),
'date': c.get('date'),
'components': c.get('components') or [],
'summary': c.get('summary') or '',
'crash': bool(c.get('crash', False)),
'fire': bool(c.get('fire', False)),
'injured': int(c.get('injured', 0) or 0),
'deaths': int(c.get('deaths', 0) or 0),
}
for c in complaints
]
}
if __name__ == '__main__':
out = get_complaints('1HGCM82633A004352')
print(f"{out['year']} {out['make']} {out['model']} - complaints total: {out['count']}")
print(f"Recent returned: {len(out['recent'])}")
if out['recent']:
first = out['recent'][0]
print(f"First ODI #{first['odi_number']} on {first['date']}: {first['summary']}")
Rate limits, monthly quota, and performance
Vehicle API plans include rate limits and a monthly quota. Exact numbers vary by plan; check the Documentation for current limits. Practical guidance:
- Batch thoughtfully: when scoring a large inventory, space calls and cache results by VIN.
- Retry policy: implement exponential backoff for transient network errors. Respect error envelopes; do not blindly retry client errors (4xx), especially
coverage_us_only. - Downstream latency: the complaints payload is lightweight (up to 50 records). Group requests in your app layer if you need to parallelize across many VINs.
Every plan includes a 7-day free trial. Starter is $19/mo. You can begin in minutes at Register.
How this fits with other Vehicle API datasets
While this guide focuses on the Vehicle Complaints endpoint, many Transportation apps combine it with:
- VIN decoding (year, make, model, trim, engine, transmission, assembly plant) from NHTSA vPIC to standardize input and label complaints against the correct model year.
- US recalls and NCAP crash ratings for a broader safety picture (also US-only and matched by make + model + year).
- Dutch plates (RDW) for non-US registrations; remember RDW data never includes a VIN, and complaints are US-only.
If you’re building an AI agent, consider connecting via the MCP server at MCP to resolve VINs and fetch structured safety data as tools within your agent framework.
Operational tips and safeguards
- Sanitize free text: complaint
summarycomes from owners. Escape/strip unsafe characters before rendering in the browser. - Consistent timestamps: complaint
dateappears as YYYY-MM-DD. Display or convert consistently in your locale-aware UI. - Accessibility: when surfacing severity (e.g., injuries/deaths), provide textual labels in addition to color cues.
- Expect variation: component labels are standardized categories but can vary in casing across providers. Use received values as-is or normalize per your taxonomy.
Testing with the control VIN
Use the documented control VIN 1HGCM82633A004352 (a 2003 Honda Accord) for sandboxing your logic. The official example above is stable for integration tests: verify envelope parsing, counters, and at least one complaint item. For production, connect your own key and rotate test VINs from your inventory to vet cache behavior and rate-limit handling.
What to log for observability
- Request: VIN, endpoint path, timestamp, and a request ID if you have one.
- Response:
status,source, and thedata.countvalue. - Errors:
error.code,error.message, HTTP status, and duration. - Cache hits/misses: track to tune your TTL for stable performance.
Security and compliance considerations
- API keys: store your key securely (server-side secrets). Do not embed in client-side code for public apps.
- PII: complaints are not PII but include user-submitted summaries. Treat as public safety content; still moderate display if needed.
- Attribution: the
sourcefield indicates NHTSA; you may wish to note “Data: NHTSA ODI via Vehicle API” in your UI.
FAQ
Does the complaints endpoint return data for non-US vehicles?
No. Complaints coverage is US-market only. For a valid non-US VIN, you’ll receive a 422 error with coverage_us_only.
Why do I only get 50 complaints when the count is higher?
The endpoint returns the 50 most recent complaints to keep payloads performant. Use count to display the total population and the returned list for recent trends.
Is a VIN required for this endpoint?
Yes. This endpoint takes a VIN. Dutch plate lookups come from RDW and never include a VIN; they are not part of this route.
What does the response envelope look like on errors?
On error, you’ll get { "status": "error", "error": { "code": "...", "message": "..." } }. Read both fields; do not rely only on HTTP status.
How should I cache results?
Cache by VIN and refresh on a schedule appropriate to your app. For consumer UIs, hours to a day is common; for fleet/insurance analytics, nightly refresh is typical. Always refresh count and complaints together.
Ready to integrate the Vehicle Complaints API into your Transportation app? Start your 7-day free trial and get an API key in minutes at Register. Explore reference details and more endpoints in the Documentation, and consider tooling your AI agents via MCP at vehicles-api.com.




