# NHTSA Vehicle Recalls & Complaints Scraper - By Make & Model (`groundplanes/nhtsa-recalls-complaints`) Actor

Scrape official NHTSA safety recalls and owner defect complaints by vehicle make, model, and model year with crash, fire, injury, and component details.

- **URL**: https://apify.com/groundplanes/nhtsa-recalls-complaints.md
- **Developed by:** [Groundplane Labs](https://apify.com/groundplanes) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.10 / 1,000 records

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## NHTSA Vehicle Recalls & Complaints Scraper - By Make & Model

Retrieve official National Highway Traffic Safety Administration (NHTSA) safety recall campaigns and owner-submitted defect complaints by vehicle make, model, and model year. Filter by record mode (`BOTH`, `RECALLS`, or `COMPLAINTS`), component keyword, crash/fire flags, or automatically enumerate all vehicle models for a make and year.

> **Disclaimer:** This Actor is an independent open-data integration tool and is not affiliated with, endorsed by, or sponsored by the National Highway Traffic Safety Administration (NHTSA) or the U.S. Department of Transportation.

***

### What it does

- Queries three official NHTSA public REST APIs:
  - `https://api.nhtsa.gov/recalls/recallsByVehicle`
  - `https://api.nhtsa.gov/complaints/complaintsByVehicle`
  - `https://api.nhtsa.gov/products/vehicle/models` (when `allModelsForMake` is enabled)
- Normalizes both NHTSA recall dates (`DD/MM/YYYY`) and complaint dates (`MM/DD/YYYY`) into strict ISO `YYYY-MM-DD` strings.
- Outputs unified, schema-validated records (`recordType: "RECALL"` or `"COMPLAINT"`) with campaign/ODI numbers, affected components, defect summaries, safety consequences, remedies, crash/fire/injury/fatality counts, Do-Not-Drive (`parkIt`) and Over-The-Air (`overTheAirUpdate`) flags, and truncated 11-character VIN prefixes (`vinPrefix`).
- Deduplicates on `recordType + (nhtsaCampaignNumber or odiNumber) + make + model + modelYear` and sorts newest first by record date (`reportReceivedDate` or `dateComplaintFiled`).

### Who it's for

- **Fleet safety & commercial vehicle operations teams** monitoring active safety recall campaigns, Do-Not-Drive (`parkIt`) advisories, and OTA remedy availability across fleet makes and models.
- **Automotive dealers, wholesalers & remarketing platforms** checking model-year recall and defect trends across inventory.
- **Automotive quality, engineering & market researchers** analyzing component-level defect complaints, crash/fire incident rates, and manufacturer recall remedies.

***

### Input

| Field | Type | Default / Prefill | Description |
|---|---|---|---|
| `mode` | string (enum) | `"BOTH"` | `"BOTH"` (recalls and complaints), `"RECALLS"` (recalls only), or `"COMPLAINTS"` (complaints only). |
| `vehicles` | array of objects | `[{"make": "honda", "model": "accord", "modelYear": 2020}]` | List of `{make, model, modelYear}` vehicle selectors. |
| `allModelsForMake` | boolean | `false` | When `true`, enumerates all NHTSA models for each `make` + `modelYear` in `vehicles`. |
| `componentFilterKeyword` | string | `""` | Optional case-insensitive substring to match against `component` (e.g. `"ELECTRICAL"`, `"BRAKES"`). |
| `crashOrFireOnly` | boolean | `false` | When `true`, keeps only records where `crash`, `fire`, or `parkIt` is `true`. |
| `includeComplaintNarrative` | boolean | `false` | When `false` (default), complaint rows omit the `summary` field entirely. When `true`, complaint rows include the consumer complaint narrative in `summary`. See the privacy note below. |
| `maxItems` | integer | `200` | Maximum number of records to return, sorted newest first. |

#### Example input JSON

```json
{
  "mode": "BOTH",
  "vehicles": [
    {
      "make": "honda",
      "model": "accord",
      "modelYear": 2020
    }
  ],
  "allModelsForMake": false,
  "componentFilterKeyword": "",
  "crashOrFireOnly": false,
  "includeComplaintNarrative": false,
  "maxItems": 200
}
```

#### Example run options (default max charge $50)

When starting a run via the Apify API or Console, set a default maximum charge limit of **$50** (`"maxTotalChargeUsd": 50`):

```json
{
  "maxTotalChargeUsd": 50
}
```

The Actor checks your remaining spending limit before every dataset push and stops cleanly if the limit is reached.

***

### Output

#### Output fields

| Field | Type | Description |
|---|---|---|
| `recordType` | string | `"RECALL"` or `"COMPLAINT"`. |
| `make` | string | Vehicle make (uppercase, e.g. `"HONDA"`). |
| `model` | string | Vehicle model (uppercase, e.g. `"ACCORD"`). |
| `modelYear` | integer | 4-digit vehicle model year. |
| `nhtsaCampaignNumber` | string | null | NHTSA recall campaign number (`RECALL` records, e.g. `"26V332000"`). |
| `odiNumber` | string | null | NHTSA Office of Defects Investigation number (`COMPLAINT` records). |
| `manufacturer` | string | null | Vehicle manufacturer legal name. |
| `component` | string | null | Affected vehicle system or component. |
| `summary` | string | null | Recall defect summary (`RECALL` records, always included), or owner complaint narrative (`COMPLAINT` records, present only when `includeComplaintNarrative` is `true`; omitted entirely when it is `false`). |
| `consequence` | string | null | Safety consequence description (`RECALL` records). |
| `remedy` | string | null | Manufacturer recall remedy description (`RECALL` records). |
| `reportReceivedDate` | string | null | Date NHTSA received the recall report in `YYYY-MM-DD` format (`RECALL` records). |
| `dateComplaintFiled` | string | null | Date the complaint was filed with NHTSA in `YYYY-MM-DD` format (`COMPLAINT` records). |
| `dateOfIncident` | string | null | Date of reported incident in `YYYY-MM-DD` format (`COMPLAINT` records). |
| `crash` | boolean | null | Whether the complaint involved a vehicle crash (`COMPLAINT` records). |
| `fire` | boolean | null | Whether the complaint involved a vehicle fire (`COMPLAINT` records). |
| `numberOfInjuries` | integer | null | Number of reported injuries (`COMPLAINT` records). |
| `numberOfDeaths` | integer | null | Number of reported fatalities (`COMPLAINT` records). |
| `vinPrefix` | string | null | Truncated 11-character VIN prefix as published by NHTSA (`COMPLAINT` records). |
| `parkIt` | boolean | null | Do-Not-Drive / Park-It advisory flag (`RECALL` records). |
| `overTheAirUpdate` | boolean | null | Whether the recall remedy is delivered via Over-The-Air (OTA) software update (`RECALL` records). |
| `sourceUrl` | string | Direct NHTSA API URL for the vehicle query. |

#### Sample output row (synthetic example)

```json
{
  "recordType": "RECALL",
  "make": "EXAMPLE MOTORS",
  "model": "SEDAN LX",
  "modelYear": 2024,
  "nhtsaCampaignNumber": "26V999000",
  "odiNumber": null,
  "manufacturer": "Example Motors North America, Inc.",
  "component": "ELECTRICAL SYSTEM:SOFTWARE",
  "summary": "Example Motors is recalling certain 2024 Sedan LX vehicles due to a body control module software fault.",
  "consequence": "Intermittent loss of exterior lighting visibility can increase the risk of a crash.",
  "remedy": "Dealers or an over-the-air (OTA) update will install updated body control module software, free of charge.",
  "reportReceivedDate": "2026-09-15",
  "dateComplaintFiled": null,
  "dateOfIncident": null,
  "crash": null,
  "fire": null,
  "numberOfInjuries": null,
  "numberOfDeaths": null,
  "vinPrefix": null,
  "parkIt": false,
  "overTheAirUpdate": true,
  "sourceUrl": "https://api.nhtsa.gov/recalls/recallsByVehicle?make=example+motors&model=sedan+lx&modelYear=2024"
}
```

***

### How pricing works

Pay per result. You only pay for rows returned.

***

### Data source and terms

- **Official Source:** National Highway Traffic Safety Administration (NHTSA) Public APIs (`https://www.nhtsa.gov/nhtsa-datasets-and-apis`).
- **Update Frequency:** Updated daily by NHTSA.
- **Terms of Use:** NHTSA recall and complaint records are U.S. government open data in the public domain. See the NHTSA Terms of Use (`https://www.nhtsa.gov/about-nhtsa/terms-use`). The structured fields carry no personal consumer identifiers, and VINs arrive pre-truncated by NHTSA to 11-character prefixes (`vinPrefix`, hard-truncated again in this actor).
- **Complaint narratives are off by default:** consumer complaint narratives are free text and live narratives can contain dealer street addresses and phone numbers. For that reason complaint rows omit the `summary` field entirely unless `includeComplaintNarrative` is set to `true`. When you enable it, the narrative is reproduced exactly as NHTSA publishes it. Manufacturer-authored recall text stays as is: the recall `summary` and `remedy` fields are always included, and remedy text can contain the manufacturer toll-free line printed in the source record.

***

### Troubleshooting (`PARTIAL:` status messages)

- If a run finishes with a status message starting with `PARTIAL:`, the Actor preserved and pushed all validated records collected before a query cap (`100` vehicle queries), scan-time budget (`240s`), upstream HTTP retry exhaustion, or schema drift check was triggered. If more than 20% of scanned rows fail validation, the run fails instead of pushing, because the source shape has changed.
- A `PARTIAL:` caused by a source problem (drift, staleness, or source errors) ends with: *"If this keeps happening, please open an issue on this actor's Issues tab and we'll fix it fast."* A `PARTIAL:` caused only by your own limits (maxItems, charge cap, or scan caps) does not carry that line.
- A run that matches nothing finishes successfully with `Complete: 0 rows matched your filters`, with a note on which filters to widen.
- Found a problem or need a field? Open an issue on the Issues tab; we respond quickly.

### Limits and FAQ

- **How can I fetch all models for a brand in a given model year?** Set `"allModelsForMake": true` and provide `{"make": "honda", "model": "", "modelYear": 2020}` in `vehicles`. The Actor enumerates the full model universe for that make and year from NHTSA's `/products/vehicle/models` endpoint (the paginated listing without an issue-type filter, reconciled against the endpoint's own reported total; if the enumeration cannot be shown complete, the run says so in a `PARTIAL:` status instead of silently covering a subset) and then queries every model.
- **How are recall and complaint dates sorted together?** Both `reportReceivedDate` (recalls) and `dateComplaintFiled` (complaints) are normalized to ISO `YYYY-MM-DD` strings and sorted newest first across the combined dataset.
- **Why did `BOTH` mode return few recalls?** In `BOTH` mode recalls and complaints are merged and sorted together newest first, so at `maxItems` the recalls can be truncated by newer complaints. For full recall coverage, use mode `RECALLS`.

***

### Changelog

- **v0.1.3 (2026-11-01):** Grok Red Team fix round. Complaint narratives are now gated behind `includeComplaintNarrative` (default `false`): when off, complaint rows omit the `summary` field entirely, because live narratives can contain dealer street addresses and phone numbers; recall summary and remedy text (manufacturer-authored) is unchanged. Also: README now notes that `BOTH` mode merges recalls and complaints newest first so recalls can be truncated at `maxItems` (use mode `RECALLS` for full recall coverage), and the terms link now points to the NHTSA Terms of Use instead of the privacy policy.

- **v0.1.2 (2026-10-31):** Red Team correction round (the 0.1.1 review was a PULL). Blocker 1: allModelsForMake enumerated models from the issueType-filtered endpoint variant, which silently returns a small subset (honda 2020 returned 11 entries and missed models with real recalls, including CR-V and PILOT); it now enumerates the full paginated model universe (no issueType) and reconciles the collected count against the endpoint total, ending PARTIAL when completeness cannot be shown. Blocker 2: the drift sentinel now covers every headline field (Manufacturer, Summary, Consequence, Remedy for recalls; manufacturer, summary, numberOfInjuries, numberOfDeaths for complaints), checks presence across the whole page instead of only the first record, and non-object result entries are counted as bad rows. Also: defaultRunOptions and an example run input were added to the build; Retry-After HTTP dates are honored; the privacy note now states that free-text summary and remedy fields are reproduced as published and can contain business phone numbers from the source record; a zero-match status under crashOrFireOnly explains the recalls parkIt semantics; the listing description now starts with "Scrape". TEST_NOTES was rewritten from fresh runs after the earlier file was found to contain status and coverage figures that live runs contradicted.

- **v0.1.1 (2026-10-31):** Honest-status fixes: zero matches now finish as `Complete: 0` instead of failing; the Issues-tab line appears only on source-caused `PARTIAL:` runs; charge-limit stops now start with `PARTIAL:`; status counts report pushed (not collected) rows and note when `maxItems` is reached; core-column sentinel added for filter, dedupe, and sort fields; validation failures over 20% now fail the run; `vinPrefix` is hard-truncated to 11 characters; model-enumeration failures now surface in the status.

- **v0.1 (2026-10-31):** Initial release supporting NHTSA recalls, complaints, and model enumeration with ISO date normalization, schema validation, and pay-per-event spending limit protection.

# Actor input Schema

## `allModelsForMake` (type: `boolean`):

When enabled, queries NHTSA's vehicle models endpoint for each make + modelYear in 'vehicles' and fetches recalls/complaints across all models for that make and year.

## `componentFilterKeyword` (type: `string`):

Optional case-insensitive substring to filter by NHTSA component name (e.g. "ELECTRICAL", "BRAKES", "FUEL", "STEERING"). Leave blank for all components.

## `crashOrFireOnly` (type: `boolean`):

When enabled, keeps only records where crash is true, fire is true, or parkIt is true.

## `includeComplaintNarrative` (type: `boolean`):

Defaults to false for privacy. Consumer complaint narratives are free text and can contain dealer street addresses and phone numbers, so when this is false complaint rows omit the summary field entirely. Set to true to include the complaint narrative in the summary field on COMPLAINT rows. Recall summary and remedy text is manufacturer-authored and is always included.

## `maxItems` (type: `integer`):

Maximum number of records to return, sorted newest first by record date.

## `mode` (type: `string`):

Choose whether to return RECALLS only, COMPLAINTS only, or BOTH recalls and complaints.

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

Array of vehicle objects with make, model, and modelYear (e.g. \[{"make": "honda", "model": "accord", "modelYear": 2020}]). When allModelsForMake is true, model may be omitted or left blank.

## Actor input object example

```json
{
  "allModelsForMake": false,
  "componentFilterKeyword": "",
  "crashOrFireOnly": false,
  "includeComplaintNarrative": false,
  "maxItems": 200,
  "mode": "BOTH",
  "vehicles": [
    {
      "make": "honda",
      "model": "accord",
      "modelYear": 2020
    }
  ]
}
```

# Actor output Schema

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

No description

# 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 = {
    "allModelsForMake": false,
    "componentFilterKeyword": "",
    "crashOrFireOnly": false,
    "includeComplaintNarrative": false,
    "maxItems": 200,
    "mode": "BOTH",
    "vehicles": [
        {
            "make": "honda",
            "model": "accord",
            "modelYear": 2020
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("groundplanes/nhtsa-recalls-complaints").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 = {
    "allModelsForMake": False,
    "componentFilterKeyword": "",
    "crashOrFireOnly": False,
    "includeComplaintNarrative": False,
    "maxItems": 200,
    "mode": "BOTH",
    "vehicles": [{
            "make": "honda",
            "model": "accord",
            "modelYear": 2020,
        }],
}

# Run the Actor and wait for it to finish
run = client.actor("groundplanes/nhtsa-recalls-complaints").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 '{
  "allModelsForMake": false,
  "componentFilterKeyword": "",
  "crashOrFireOnly": false,
  "includeComplaintNarrative": false,
  "maxItems": 200,
  "mode": "BOTH",
  "vehicles": [
    {
      "make": "honda",
      "model": "accord",
      "modelYear": 2020
    }
  ]
}' |
apify call groundplanes/nhtsa-recalls-complaints --silent --output-dataset

```

## MCP server setup

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

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/vDRcCBdM414esIe7K/builds/gwuJFHGicNnTOdMcY/openapi.json
