# NHTSA Vehicle Recalls, Complaints & Safety Ratings API (`neverempty/nhtsa-vehicle-recalls`) Actor

For dealers, used-car marketplaces, insurers and fleet safety teams: safety recalls, consumer complaints and NCAP crash-test star ratings for any US-market vehicle from NHTSA's own API. Across 4,793 complaints on 2026-09-10, crash and fire flags are rare enough to be worth filtering on. No API key.

- **URL**: https://apify.com/neverempty/nhtsa-vehicle-recalls.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Developer tools, MCP servers, Automation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.65 / 1,000 record returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## NHTSA Vehicle Recalls, Complaints & Safety Ratings API

**For dealers, used-car marketplaces, fleet and safety teams, insurers and automotive researchers**: safety recalls, consumer complaints and NCAP crash-test star ratings for any US-market vehicle, taken from NHTSA's own public API. Give it a list of vehicles and get flat rows back.

**Measured against the live API on 2026-09-10, not read from documentation.** NHTSA lists **332** makes for model year 2020, **244** for 2024 and **154** for 2026, and one make can have dozens of models in a single year. A 2021 Ford F-150 returned **29** recalls; a 2021 Honda Accord returned **226** complaints. There is no pagination — one request returns the whole result.

**NHTSA's two date formats disagree with each other, and this Actor proved which is which.** Recalls use `DD/MM/YYYY` (**30 of 39** values had a first part above 12; none had a second part above 12); complaints use `MM/DD/YYYY` (**1,038 of 1,834** values had a second part above 12; none had a first part above 12). Both are converted to ISO (`2021-12-16`) and **the original string is kept**, so nothing rests on a guess.

**A vehicle NHTSA does not know answers HTTP 400 with a body that says "success"** — and `/SafetyRatings/` answers HTTP 200 with `Count: 0` both for a car it has not tested *and* for a make that does not exist. Neither is passed to you as "no records": a rejected input gets a row saying the input was rejected, and a genuine empty result gets a different one. Spelling matters too, and there is no one spelling that works on all three endpoints — for the 2021 Ford, `F-150` returned **29** recalls and nothing else, while `F-150 SUPER CREW` returned **1,000** complaints and **2** ratings; this Actor asks each endpoint the way that endpoint wants.

**Crash and fire flags are rare, which is why filtering on them is worth it** — measured across **4,793** complaints on five vehicles. VINs come back as the **11** characters NHTSA actually publishes (measured: 221 of 221 complaints), not padded out to a fake 17.

No API key, no scraping, no proxy. Export as JSON, CSV or Excel.

Give it a list of vehicles:

```json
{
  "vehicles": [
    { "make": "Toyota", "model": "Camry",  "modelYear": 2022 },
    { "make": "Honda",  "model": "Accord", "modelYear": 2021 }
  ],
  "datasets": ["recalls", "complaints"]
}
```

### What this fixes about the raw API

Everything below was measured against the live API on 2026-09-10, not read from documentation.

#### 1. The two endpoints use opposite date formats

NHTSA returns recall dates as **DD/MM/YYYY** and complaint dates as **MM/DD/YYYY**. Reading both the same
way puts a record up to eleven months away from where it belongs.

| Endpoint | Field | Format | How it was established |
|---|---|---|---|
| Recalls | `ReportReceivedDate` | `DD/MM/YYYY` | 30 of 39 values had a first part above 12; none had a second part above 12 |
| Complaints | `dateOfIncident`, `dateComplaintFiled` | `MM/DD/YYYY` | 1,038 of 1,834 values had a second part above 12; none had a first part above 12 |

Both are converted to ISO (`2021-12-16`) and **the original string is kept** in `reportReceivedDateRaw` and
`dateOfIncidentRaw`, so you can check the conversion yourself.

#### 2. A vehicle NHTSA does not know answers HTTP 400 - with a body that says "success"

The rejection looks like this:

```
HTTP 400
{"Count":0,"Message":"Results returned successfully","results":[]}
```

Read the body alone and you conclude the car has no recalls. This Actor reads the status first and returns a
row that says the input was rejected, never "no records". A genuine empty result (HTTP 200, zero rows) gets a
different row that does say so. Neither row is charged.

#### 3. Complaint VINs are truncated, and the column name says so

NHTSA publishes 11 characters, not the full 17 (measured: 221 of 221 complaints). The last characters, which
identify the individual vehicle, are not there. The column is called `vinPrefix` rather than `vin` so it is not
mistaken for a full VIN.

#### 4. Star ratings distinguish "not rated" from zero

`OverallRating` comes back as the string `"Not Rated"` for body styles NHTSA has not crash-tested. That becomes
`null`, never `0`, and the original string is kept in `overallRatingRaw`.

### What you get

One row per record. `recordType` says which kind it is.

**Recalls**: `campaignNumber`, `manufacturer`, `component`, `reportReceivedDate`, `summary`, `consequence`,
`remedy`, `notes`, and NHTSA's three warning flags `parkItAway`, `parkOutside`, `fixedByOverTheAirUpdate`.

**Complaints**: `odiNumber`, `components`, `crash`, `fire`, `numberOfInjuries`, `numberOfDeaths`,
`dateOfIncident`, `dateComplaintFiled`, `vinPrefix`, `summary`.

**Safety ratings**: `overallRating`, `overallFrontCrashRating`, `overallSideCrashRating`, `rolloverRating`,
`complaintsCount`, `recallsCount`, `investigationCount`, `vehicleDescription`.

#### 5. The three endpoints do not share a model vocabulary

Recalls, complaints and crash-test ratings each keep their own list of model names, and for some vehicles there
is no spelling that works on all three. Measured for the 2021 Ford:

| `model` | recalls | complaints | NCAP ratings |
|---|---|---|---|
| `F-150` | **29** | 0 | 0 |
| `F-150 SUPER CREW` | 0 | **1,000** | **2** |

Plain model names - Camry, Accord, Civic, RAV4, Altima, Corolla, Malibu, Model 3 - work on all three; all eight
were checked. When a vehicle returns nothing from the ratings endpoint, this Actor asks the recall endpoint
about the same vehicle and uses the answer to tell you which of the two it is (see next).

#### 6. The ratings endpoint never says no

`/SafetyRatings/...` answers HTTP 200 with `Count: 0` for a car it has not tested **and** for a make that does
not exist - I asked it for `make/Zorblax/model/Nope` and got exactly the same body as for a real untested car.
On its own it cannot tell you which happened.

So when the ratings come back empty, this Actor puts the same vehicle to the recall endpoint, which does answer
400 for an unknown vehicle. A 400 there means your input was wrong; a 200 means the car is real and simply has
no NCAP rating. Those are two different rows, and neither is charged.

#### 7. Two more things measured on the live API

**NHTSA's own model list and its recall endpoint disagree.** `MUSTANG MACH-E` appears in
`/products/vehicle/models?modelYear=2022&make=ford&issueType=r`, yet the recall endpoint answers HTTP 400 for
it under every spelling tried. When that happens you get a rejected-input row saying so, not a silent blank.

**Crash and fire flags are rare, which is why filtering on them is worth it.** Across 4,793 complaints on five
vehicles, 2.2 per cent carried NHTSA's crash flag and 3.7 per cent its fire flag. `onlyCrashOrFire` filters the
whole result and then applies your row limit, so asking for 60 crash reports gives you up to 60 crash reports -
not the crash reports that happened to fall in the first 60 complaints.

### Input

| Field | What it does |
|---|---|
| `vehicles` | The list to look up. Each entry needs `make`, `model` and `modelYear`. A spelling NHTSA does not have comes back as a rejected input, not as "no recalls". |
| `datasets` | Any of `recalls`, `complaints`, `safety-ratings`. Default is recalls and complaints. |
| `maxRecordsPerVehicle` | Upper limit of rows per vehicle per dataset. You are charged only for rows you actually receive. |
| `componentContains` | Keep records whose component text matches, e.g. `AIR BAG`, `POWER TRAIN`. NHTSA writes components in capitals. Crash-test ratings carry no component, so this filter is not applied to them. |
| `onlyCrashOrFire` | Complaints where NHTSA's own crash or fire flag is set - the flag from the filing, not a word search. |
| `onlyWithInjuriesOrDeaths` | Complaints stating at least one injury or death. |
| `maxRetries` | Attempts per request when NHTSA does not answer. HTTP 400 is permanent and is never retried. |

### Scale

Measured on 2026-09-10: NHTSA lists 332 makes for model year 2020, 244 for 2024 and 154 for 2026, and a single
make can have dozens of models in one year. A 2021 Ford F-150 returned 29 recalls; a 2021 Honda Accord returned
226 complaints. There is no pagination - one request returns the whole result.

### Source and licence

NHTSA public API (`api.nhtsa.gov`). Works of the US federal government are in the public domain. This Actor
makes plain HTTPS requests to the documented JSON endpoints; it does not scrape any web page and needs no API
key or proxy.

# Actor input Schema

## `vehicles` (type: `array`):

One entry per vehicle: {"make":"Toyota","model":"Camry","modelYear":2022}. Spelling has to be one NHTSA knows - the recall endpoint answers HTTP 400 for a make, model or year it does not have, and that 400 is reported as a rejected input rather than as "no recalls". Note that the three endpoints do not share a model vocabulary: a 2021 Ford "F-150" returns 29 recalls but no complaints and no crash-test ratings, while "F-150 SUPER CREW" returns the complaints and ratings and no recalls. Plain model names such as Camry, Accord, Civic, RAV4 and Altima work on all three.

## `datasets` (type: `array`):

recalls = NHTSA safety recall campaigns. complaints = consumer complaints filed with NHTSA, including whether a crash, fire, injury or death was reported. safety-ratings = NCAP crash test star ratings, which need one extra request per body style.

## `maxRecordsPerVehicle` (type: `integer`):

Upper limit of rows kept for each vehicle and each dataset. You are charged for the rows you actually receive. For reference, measured on 2026-09-10: a 2021 Ford F-150 had 29 recalls, and a 2021 Honda Accord had 226 complaints.

## `onlyCrashOrFire` (type: `boolean`):

Keep a complaint only if NHTSA's own crash or fire flag is true. The flag comes from the filing, not from searching the text for the word crash.

## `onlyWithInjuriesOrDeaths` (type: `boolean`):

Keep a complaint only if it states at least one injury or death. A complaint that does not state a number is treated as zero by NHTSA itself and is dropped by this filter.

## `componentContains` (type: `string`):

Keep a record only if the component text contains this (case-insensitive). NHTSA writes components in capitals, such as "AIR BAGS:SENSOR:OCCUPANT CLASSIFICATION" or "POWER TRAIN". Crash-test ratings carry no component, so this filter is not applied to them - they are returned regardless. Leave empty for all components.

## `maxRetries` (type: `integer`):

How many times to try a request in total when NHTSA does not answer. HTTP 400 is a permanent answer and is never retried.

## Actor input object example

```json
{
  "vehicles": [
    {
      "make": "Toyota",
      "model": "Camry",
      "modelYear": 2022
    },
    {
      "make": "Honda",
      "model": "Accord",
      "modelYear": 2021
    }
  ],
  "datasets": [
    "recalls",
    "complaints"
  ],
  "maxRecordsPerVehicle": 200,
  "onlyCrashOrFire": false,
  "onlyWithInjuriesOrDeaths": false,
  "componentContains": "",
  "maxRetries": 4
}
```

# Actor output Schema

## `results` (type: `string`):

One row per NHTSA record - a safety recall, a consumer complaint or an NCAP star rating - plus one row for each vehicle NHTSA rejected, had no records for, or could not be read.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "vehicles": [
        {
            "make": "Toyota",
            "model": "Camry",
            "modelYear": 2022
        },
        {
            "make": "Honda",
            "model": "Accord",
            "modelYear": 2021
        }
    ],
    "datasets": [
        "recalls",
        "complaints"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/nhtsa-vehicle-recalls").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "vehicles": [
        {
            "make": "Toyota",
            "model": "Camry",
            "modelYear": 2022,
        },
        {
            "make": "Honda",
            "model": "Accord",
            "modelYear": 2021,
        },
    ],
    "datasets": [
        "recalls",
        "complaints",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/nhtsa-vehicle-recalls").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "vehicles": [
    {
      "make": "Toyota",
      "model": "Camry",
      "modelYear": 2022
    },
    {
      "make": "Honda",
      "model": "Accord",
      "modelYear": 2021
    }
  ],
  "datasets": [
    "recalls",
    "complaints"
  ]
}' |
apify call neverempty/nhtsa-vehicle-recalls --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/nhtsa-vehicle-recalls"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/KiZjmX0NQuJvqCSB4/builds/PxuFLl2aobmMBKbQL/openapi.json
