# US NHTSA Vehicle Recall Monitor (`zeekr011/us-nhtsa-monitor`) Actor

Query and monitor official NHTSA vehicle safety recalls by vehicle or campaign.

- **URL**: https://apify.com/zeekr011/us-nhtsa-monitor.md
- **Developed by:** [hugo liu](https://apify.com/zeekr011) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$4.00 / 1,000 result items

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## US NHTSA Vehicle Recall Monitor

Query and monitor official U.S. National Highway Traffic Safety Administration
(NHTSA) vehicle recalls by campaign number or vehicle identity. Get normalized,
traceable recall rows with manufacturer, affected component, remedy, report
date, safety flags, and an official source URL.

Use it for fleet and dealer checks, vehicle-safety research, compliance feeds,
automotive data products, and scheduled recall monitoring. It is a focused
wrapper around NHTSA's structured APIs: easy to run, predictable to integrate,
and explicit about what a vehicle-level recall lookup can—and cannot—prove.

### Before you run

- For a one-off API call, use `lookup` or `export`. `monitor` is stateful and
  emits only new or changed rows after the monitor snapshot is established.
- Actor usage costs **$0.004 per result**, equivalent to **$4 per 1,000
  results**, under Pay Per Event pricing. Runs with zero results have no
  `result-item` usage charge. See the **Pricing** tab for the current terms.
- Data is returned from NHTSA at fetch time. This Actor does not provide a
  real-time freshness guarantee or an SLA for upstream publication delays.
- Start with an exact `campaignNumber` or a specific `make` + `model` +
  `modelYear` and a small `maxResults`. A `make` + `modelYear` query first
  enumerates models and then queries them serially; `maxResults: 1` does not
  avoid model enumeration, and an empty sweep may still contact every model.
  `maxResults: 0` can fan out across all enumerated models and is not an
  unlimited historical archive.
- Successful runs have a per-run Dataset. This Actor does not automatically
  rename it; retain the returned Dataset ID or name/copy important results
  before relying on your account's default retention policy.

### What this Actor does

- Looks up an exact NHTSA campaign number.
- Looks up recalls for one `make` + `model` + `modelYear` vehicle line.
- Discovers NHTSA-recognized models for a `make` + `modelYear`, then queries
  each model with the same bounded result limit.
- Filters normalized records by campaign number and inclusive report-date
  windows.
- Normalizes dates, vehicle identity, booleans, missing values, and source URLs
  into a stable Dataset contract.
- Supports current lookup, bounded export, and incremental monitor modes.
- Emits `CURRENT`, `ADDED`, or `UPDATED` rows with `changedFields`, a content
  hash, and a stable idempotency key.

### Why teams use it

NHTSA recall data is most useful when campaign details and operational flags are
available in a single, automation-ready row. This Actor removes repetitive API
plumbing and preserves the official context needed for a fleet dashboard,
customer workflow, research dataset, or AI agent.

Common use cases:

- Check the current recall campaigns for a make, model, and model year.
- Retrieve one campaign for a compliance or service workflow.
- Export a historical report-date window for research.
- Schedule a stable vehicle scope and alert only when a record is new or
  changed.

### Data source and lookup scope

The Actor uses the official [NHTSA vehicle safety APIs](https://www.nhtsa.gov/nhtsa-datasets-and-apis):

| Input shape | Official endpoint | Behavior |
| --- | --- | --- |
| `campaignNumber` | [`/recalls/campaignNumber`](https://api.nhtsa.gov/recalls/campaignNumber) | Exact campaign lookup; takes precedence over vehicle fields |
| `make` + `model` + `modelYear` | [`/recalls/recallsByVehicle`](https://api.nhtsa.gov/recalls/recallsByVehicle) | One recall query for the exact vehicle line |
| `make` + `modelYear` | [`/products/vehicle/models`](https://api.nhtsa.gov/products/vehicle/models), then `/recalls/recallsByVehicle` | Enumerates NHTSA-recognized models and queries each one |

NHTSA is a structured lookup API. This Actor does not provide arbitrary
full-text recall search, manufacturer-text search, VIN decoding, ownership
history, or a VIN-level determination that a particular vehicle is affected.
A make/year query can create multiple upstream requests; start with a specific
model or campaign and a small `maxResults`.

#### Request and resource notes

- An exact `campaignNumber` lookup makes one campaign request. An exact
  `make` + `model` + `modelYear` lookup makes one vehicle request.
- A `make` + `modelYear` lookup makes one model-enumeration request followed by
  one recall request per returned model, serially. With `maxResults > 0`, the
  Actor can stop after enough normalized rows; with no matches it may query all
  enumerated models.
- `maxResults` limits normalized rows after source filtering. It is not a
  request-count, latency, or cost guarantee, and `maxResults: 0` removes only
  the Actor-side row cap.
- `sourceRecordId` includes campaign, make, model, and model year. The same
  campaign found through different query shapes can therefore have a different
  row identity; for cross-call deduplication prefer `campaignNumber`, or use
  it with normalized vehicle fields when vehicle scope matters.
- The boolean safety fields are normalized with strict source semantics:
  `false` means the source value was not `true`; it is not confirmation that a
  vehicle is safe or that no precaution is needed.

### Run modes

| Mode | What it returns | State behavior |
| --- | --- | --- |
| `lookup` | Matching current records as `CURRENT` | Does not read or change monitor state |
| `export` | A bounded current-source date-window result as `CURRENT` | Does not read or change monitor state |
| `monitor` | New and changed records as `ADDED` or `UPDATED` | Reads and updates the named Key-Value Store |

Use `lookup` for a focused check, `export` for a defined report, and `monitor`
for recurring change detection. The default mode is `monitor`, although a
campaign or vehicle scope is still required.

### Quick start

#### Apify Console

1. Open the Actor and select **Input**.
2. Enter either `campaignNumber`, or `make` plus `modelYear` (with optional
   `model`).
3. Choose `lookup`, `export`, or `monitor`.
4. Add a report-date window and keep `maxResults` small for the first run.
5. Click **Start**, then inspect the Dataset or download JSON, CSV, or Excel
   from the Output tab.

#### Apify CLI

After installing and authenticating the Apify CLI, look up one campaign:

```bash
apify call <ACTOR_ID> \
  --input '{"mode":"lookup","campaignNumber":"26V123000","maxResults":1}' \
  --output-dataset
```

Or query one vehicle line:

```bash
apify call <ACTOR_ID> \
  --input '{"mode":"lookup","make":"Honda","model":"Civic","modelYear":"2023","maxResults":10}' \
  --output-dataset
```

`<ACTOR_ID>` can be the Actor ID or an authorized Actor name. A valid campaign
or vehicle query with no matches completes successfully with zero Dataset items.

#### REST API

Start a run with a JSON request body:

```bash
curl -X POST \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "mode": "lookup",
    "make": "Honda",
    "model": "Civic",
    "modelYear": "2023",
    "maxResults": 25
  }' \
  "https://api.apify.com/v2/acts/<ACTOR_ID>/runs?waitForFinish=60"
```

Read the returned `defaultDatasetId` through the Dataset API:

```text
GET https://api.apify.com/v2/datasets/<DATASET_ID>/items?format=json&clean=true
GET https://api.apify.com/v2/datasets/<DATASET_ID>/items?format=csv&clean=true&attachment=true
GET https://api.apify.com/v2/datasets/<DATASET_ID>/items?format=xlsx&clean=true&attachment=true
```

Use `limit` and `offset` for pagination. Keep `$APIFY_TOKEN` in an environment
variable; never put it in Actor input, source code, logs, or Dataset records.

#### API run lifecycle

The POST request starts an Apify run; it does not return Dataset rows directly.
If `waitForFinish=60` returns a non-terminal status such as `RUNNING`, keep the
returned `runId` and poll `GET /v2/actor-runs/<RUN_ID>` or use a Webhook. Read
`defaultDatasetId` after the run reaches `SUCCEEDED`. A successful empty Dataset
is valid; in `monitor` mode it can mean that no recall changed, not that NHTSA
had no matching recall.

### Input API reference

The input is a JSON object. The [Input schema](./.actor/input_schema.json) is
the machine-readable contract used by the Console and API.

| Field | Type / default | Description |
| --- | --- | --- |
| `mode` | enum, `"monitor"` | `monitor`, `lookup`, or `export`. |
| `campaignNumber` | string | NHTSA campaign in the form two digits, one letter, six digits, for example `23V769000`. When present, it overrides vehicle lookup. Punctuation and letter case are normalized for validation. |
| `make` | string | Vehicle make, used with `modelYear`. |
| `model` | string | Optional vehicle model. Omit it to enumerate models for the make/year. |
| `modelYear` | four-digit string | Model year from `1900` through `2100`, used with `make`. |
| `recalledSince` | `YYYY-MM-DD` | Inclusive lower bound for NHTSA `ReportReceivedDate`. |
| `recalledUntil` | `YYYY-MM-DD` | Inclusive upper bound for `ReportReceivedDate`. |
| `maxResults` | integer, `500` | Maximum emitted rows. Range: `0`–`25000`; `0` removes the Actor-side cap after source queries and filtering, but does not limit make/year fan-out. |
| `emitInitialSnapshot` | boolean, `true` | In `monitor`, emit first-seen records as `ADDED`; set `false` to seed state without emitting the initial snapshot. |
| `monitorId` | string | Optional stable namespace for separate monitors sharing a state store. |
| `stateStoreName` | string, `"us-nhtsa-monitor-state"` | Named Apify Key-Value Store used for monitor fingerprints. |

#### Query behavior and validation

- Provide `campaignNumber`, or provide `make` and `modelYear`. A model is
  optional for a whole-make/model-year sweep.
- Dates must be real calendar dates in `YYYY-MM-DD`; invalid dates such as
  `2026-02-30` are rejected.
- `recalledSince` cannot be after `recalledUntil`.
- `campaignNumber` is validated against the NHTSA campaign shape, such as
  `23V769000`.
- Filtering is applied after source normalization and before `maxResults`.
- For make/year sweeps, model enumeration and upstream recall requests still
  occur according to the lookup scope even when `maxResults` is small.
- Records without a campaign number are discarded because they cannot provide a
  stable source identity.
- A valid query with no matches returns `SUCCEEDED` with an empty Dataset.
  Invalid input, malformed API JSON, or an unrecoverable HTTP error fails the
  run with an explanatory message.

### Example inputs

#### Monitor one campaign

```json
{
  "mode": "monitor",
  "campaignNumber": "26V123000",
  "maxResults": 1,
  "monitorId": "campaign-26V123000"
}
```

#### Look up one current vehicle line

```json
{
  "mode": "lookup",
  "make": "Honda",
  "model": "Civic",
  "modelYear": "2023",
  "maxResults": 10
}
```

#### Export a historical vehicle-recall window

```json
{
  "mode": "export",
  "make": "Ford",
  "modelYear": "2022",
  "recalledSince": "2015-01-01",
  "recalledUntil": "2015-12-31",
  "maxResults": 250
}
```

#### Valid empty result and invalid input

An exact lookup for a campaign with no NHTSA rows is valid and returns an empty
Dataset:

```json
{
  "mode": "lookup",
  "campaignNumber": "26V658000",
  "maxResults": 1
}
```

An input such as `{"make":"Honda"}` is rejected because `modelYear` is
required for a vehicle lookup. An input such as
`{"campaignNumber":"not-a-campaign"}` is rejected before the API is called.

For abnormal upstream behavior, the Actor retries bounded transient failures
such as `429`, `5xx`, network errors, and timeouts. Malformed API JSON and other
unrecoverable responses fail the run with an explanatory error; they are not
silently converted to an empty Dataset.

### Output API reference

Each Dataset item is one normalized NHTSA recall record. Empty source values are
represented by empty strings, `false` booleans, or `null` dates according to the
field contract.

#### Representative historical recall output

This trimmed example is based on a real NHTSA recall record. Hash values and
timestamps are placeholders for the values generated by the run; the complete
row contains every field listed below.

```json
{
  "source": "nhtsa",
  "sourceRecordId": "recall:15V123000:KIA:SOUL:2014",
  "recordType": "recall",
  "campaignNumber": "15V123000",
  "actionNumber": "",
  "manufacturer": "Kia Motors America",
  "make": "KIA",
  "model": "SOUL",
  "modelYear": "2014",
  "component": "VEHICLE SPEED CONTROL:ACCELERATOR PEDAL",
  "potentialUnitsAffected": "208858",
  "summary": "Kia Motors America is recalling certain model year 2014-2015 Soul vehicles; a section of the accelerator pedal may bend and fracture.",
  "consequence": "A bent or broken accelerator pedal may make it difficult to accelerate, increasing the risk of a crash.",
  "remedy": "Kia will notify owners and dealers will add a supporting rubber underneath the pedal stopper, free of charge.",
  "reportReceivedDate": "2015-03-02",
  "doNotDrive": false,
  "parkOutside": false,
  "overTheAirUpdate": false,
  "sourceUrl": "https://www.nhtsa.gov/recalls?nhtsaId=15V123000",
  "changeType": "CURRENT",
  "changedFields": [],
  "detectedAt": "2026-09-02T00:00:00.000Z",
  "contentHash": "<sha256>",
  "idempotencyKey": "<sha256>"
}
```

The three boolean flags are source indicators, not a VIN-level safety
assessment. For `monitor`, the same content fields are accompanied by `ADDED`
or `UPDATED`; `changedFields` lists normalized fields that changed since the
prior snapshot. `CURRENT` is used by `lookup` and `export`.

#### Identity and monitor metadata

| Field | Type | Description |
| --- | --- | --- |
| `source` | string | Always `nhtsa`. |
| `sourceRecordId` | string | Stable identity for a vehicle-scoped row in the form `recall:<campaign>:<make>:<model>:<modelYear>`. For cross-query deduplication, prefer `campaignNumber` because the same campaign can appear with different vehicle scope. |
| `recordType` | string | Always `recall`. |
| `changeType` | string | `ADDED`, `UPDATED`, or `CURRENT`. |
| `changedFields` | string\[] | Normalized fields changed since the previous monitor snapshot; empty for `ADDED` and `CURRENT`. |
| `detectedAt` | ISO timestamp | Time this Actor produced the row. |
| `contentHash` | string | SHA-256 hash of the normalized source snapshot, excluding derived monitor metadata. |
| `idempotencyKey` | string | Stable key for monitor scope, source record, and content version. Use it to deduplicate retries. |

#### Recall fields

| Field | Type | Description |
| --- | --- | --- |
| `campaignNumber` | string | Official NHTSA campaign number. |
| `actionNumber` | string | NHTSA action number when supplied. |
| `manufacturer` | string | Manufacturer named by NHTSA. |
| `make` | string | Vehicle make. |
| `model` | string | Vehicle model. |
| `modelYear` | string | Vehicle model year. |
| `component` | string | Affected vehicle component. |
| `potentialUnitsAffected` | string | Reported potentially affected units. |
| `summary` | string | Recall summary. |
| `consequence` | string | Reported safety consequence. |
| `remedy` | string | Reported remedy. |
| `notes` | string | Additional NHTSA notes. |
| `reportReceivedDate` | string or null | Normalized NHTSA report date in `YYYY-MM-DD`. |
| `doNotDrive` | boolean | NHTSA `parkIt` safety flag. |
| `parkOutside` | boolean | NHTSA `parkOutSide` safety flag. |
| `overTheAirUpdate` | boolean | Whether an over-the-air update is indicated. |
| `sourceUrl` | string | Official NHTSA recall page for the campaign. |

#### Dataset and output links

The Actor pushes structured JSON rows only. Apify generates these views from the
same Dataset:

- JSON: `.../items?format=json&clean=true`
- CSV: `.../items?format=csv&clean=true&attachment=true`
- Excel: `.../items?format=xlsx&clean=true&attachment=true`

Successful non-empty runs have their own Dataset ID. If long-term retention is
required, name or copy the Dataset after the run; this Actor does not merge
separate runs into a shared output Dataset. Dataset lifetime follows the
retention rules of your Apify account and plan; do not treat an unnamed
per-run Dataset as a permanent archive.

### Monitoring and delivery semantics

In `monitor` mode:

1. Records are normalized and deduplicated by `sourceRecordId`.
2. The Actor compares normalized content with the snapshot in the named
   Key-Value Store.
3. New records become `ADDED`; changed records become `UPDATED` with
   `changedFields`.
4. Dataset delivery completes before the checkpoint is written.

This is safe at-least-once delivery. If Dataset delivery succeeds but the
checkpoint write fails, a later run may emit the same event again; downstream
consumers should deduplicate with `idempotencyKey`. Records absent from a later
source response are not emitted as `REMOVED`, because absence may reflect model
enumeration, filtering, upstream coverage, or source-window behavior rather than
an official withdrawal.

For scheduled monitoring, keep the same `monitorId`, `stateStoreName`, and
query scope so later runs compare against the same snapshot.

The HTTP layer starts requests serially within one process, waits one second by
default between request starts, retries transient network errors, `429`, and
`5xx` responses with bounded exponential backoff and jitter, and honors
`Retry-After` when supplied. These locks and limits are process-local; they do
not provide account-wide rate limiting or cross-container exactly-once
delivery.

### Reliability and limitations

- NHTSA endpoints can change fields, throttle requests, or have maintenance
  windows. The Actor retries transient failures within a finite budget.
- A make/year query depends on the model names currently returned by NHTSA;
  spelling changes or incomplete model enumeration can affect coverage.
- A recall is reported by NHTSA for a make/model/year scope. It is not proof of
  eligibility for a particular VIN, and this Actor does not verify repair
  completion or ownership.
- `false` in `doNotDrive`, `parkOutside`, or `overTheAirUpdate` is not a safety
  clearance; it means the normalized source flag was not `true`.
- The Actor reports what NHTSA publishes. It does not independently verify
  defect claims, remedy effectiveness, or legal status.
- A local process lock is not a distributed lock. Strict cross-container
  coordination would require a shared transactional store and global limiter.

### Local development

Requirements: Node.js 20 or newer.

```bash
npm install
npm test
npm run build
apify validate-schema
npm run test:real
```

`npm test` uses deterministic fixtures and injected failures, including
malformed JSON, `429`/`Retry-After`, `5xx`, timeout, rate limiting, state
ordering, and delivery failure. The bounded real smoke script checks a current
campaign, a historical campaign, a valid empty result, and output identity/date
fields; it is not an unbounded vehicle-recall export.

### Attribution

This Actor consumes public NHTSA data. Review the current NHTSA API terms,
availability, and usage guidance before operating at scale. Preserve
`sourceUrl` and `campaignNumber` when passing records downstream so users can
inspect the authoritative recall notice.

# Actor input Schema

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

Choose whether to emit changes, current matches, or a historical export.

## `make` (type: `string`):

Use with modelYear; model is optional for a whole-make sweep.

## `model` (type: `string`):

Optional model; omit it to sweep all models for a make and year.

## `modelYear` (type: `string`):

Four-digit vehicle model year.

## `campaignNumber` (type: `string`):

Example: 23V769000. Overrides vehicle lookup.

## `recalledSince` (type: `string`):

Inclusive report date in YYYY-MM-DD format.

## `recalledUntil` (type: `string`):

Inclusive report date in YYYY-MM-DD format.

## `maxResults` (type: `integer`):

Maximum records emitted; zero returns all results returned by the selected query.

## `emitInitialSnapshot` (type: `boolean`):

Emit all first-seen records during the first monitor run.

## `monitorId` (type: `string`):

Stable identifier that separates independent monitor state namespaces.

## `stateStoreName` (type: `string`):

Named Apify Key-Value Store used for monitor fingerprints.

## Actor input object example

```json
{
  "mode": "monitor",
  "maxResults": 500,
  "emitInitialSnapshot": true,
  "stateStoreName": "us-nhtsa-monitor-state"
}
```

# Actor output Schema

## `json` (type: `string`):

No description

## `csv` (type: `string`):

No description

## `excel` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("zeekr011/us-nhtsa-monitor").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("zeekr011/us-nhtsa-monitor").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 '{}' |
apify call zeekr011/us-nhtsa-monitor --silent --output-dataset

```

## MCP server setup

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

```

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/43YWPTELqcuekbosH/builds/rRwg6KamOgbBxLwbt/openapi.json
