# NHTSA VIN Decoder & Vehicle Recalls (`j0401/nhtsa-vehicles`) Actor

US NHTSA vehicle data (public open data, no key): decode a VIN into 69 structured fields - make, model, year, body, engine, plant, safety equipment - and pull the safety recalls for a vehicle or by campaign number, with the remedy and the recall date.

- **URL**: https://apify.com/j0401/nhtsa-vehicles.md
- **Developed by:** [Wenhao Yang](https://apify.com/j0401) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.06 / 1,000 nhtsa vehicle 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?

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 Vehicles - VIN Decode & Safety Recalls

Decode any **VIN** into its full factory specification, and pull **NHTSA safety recalls** for a vehicle or a campaign - from two free US government services, in one actor.

### Low cost

**From $0.0001 per record.** Pay-per-event: you are charged per record delivered, and nothing for the query.

### What you get

| Corpus | One record is | Depth |
|---|---|---|
| `vin` | one decoded VIN | 69 flattened vehicle fields, plus the complete **140-variable** decode |
| `recalls` | one recall campaign for one vehicle | campaign number, component, summary, consequence, remedy, units affected, date |

The two are different grains on purpose. A VIN decode is **one row per VIN**. A recall is **one row per campaign** - a vehicle has many, and a campaign that covers several models comes back as several rows sharing one campaign number.

### VIN decode

NHTSA's decoder answers with 140 `Variable`/`Value` pairs for a single VIN. That is more than a flat row can hold usefully, so **69 are flattened onto the record** - identity, plant, dimensions and weight, engine and electrification, transmission, seating and the safety-equipment block - and the complete 140-pair answer is kept verbatim in `allVariablesJson`. Nothing is lost to the curation.

A real decode, `1HGCM82633A004352`:

```
vin                  1HGCM82633A004352
vehicleDescriptor    1HGCM826*3A
make                 HONDA
manufacturerName     AMERICAN HONDA MOTOR CO., INC.
model                Accord
modelYear            2003
trim                 EX-V6
vehicleType          PASSENGER CAR
bodyClass            Coupe
doors                2
plantCity            MARYSVILLE
plantState           OHIO
plantCountry         UNITED STATES (USA)
engineCylinders      6
displacementL        2.998832712
engineModel          J30A4
engineConfiguration  V-Shaped
engineBrakeHpFrom    240
fuelTypePrimary      Gasoline
valveTrainDesign     Single Overhead Cam (SOHC)
transmissionStyle    Automatic
transmissionSpeeds   5
gvwrFrom             Class 1C: 4,001 - 5,000 lb (1,814 - 2,268 kg)
seatBeltType         Manual
frontAirBagLocations 1st Row (Driver and Passenger)
curtainAirBagLocations 1st and 2nd Rows
decodeErrorCode      0          # 0 = VIN decoded clean
```

Values arrive as **strings even when numeric** (`2.998832712`, `240`, `5`), and a variable NHTSA holds no data for is `''` - which is *not* the same as "the vehicle lacks it", and is not claimed to be.

### Safety recalls

- **By vehicle** - give `make`, `model` and `modelYear` (all three; the source rejects any subset). `honda` / `accord` / `2003` returns **24 recalls**.
- **By campaign** - give a campaign number. `19V182` and `19V182000` both resolve to campaign `19V182000`, which covers **93 vehicle rows** (Acura CL, MDX, TL and more), each carrying the same campaign number and its own model.

```
campaignNumber       19V182000
manufacturer         Honda (American Honda Motor Co.)
reportReceivedDate   2019-03-06
component            AIR BAGS:FRONTAL:DRIVER SIDE:INFLATOR MODULE
summary              Honda (American Honda Motor Co.) is recalling specific 2003 Acura 3.2CL, ...
consequence          ...in the event of a crash necessitating deployment of the driver's frontal air bag...
remedy               ...dealers will replace the driver's frontal air bag inflator...
potentialUnitsAffected  1101534
recallModelYear      2003
recallMake           ACURA
```

**Decode -> recalls in one run.** Set `includeRecalls=true` with `corpus=vin` and each VIN's decoded make/model/year is used to fetch its recalls, emitted next to the decode record. One caveat, stated on every record as `recallScope: "make-model-year"`: **NHTSA publishes recalls by make/model/year, not by VIN**. Some campaigns cover a model-year range, so a VIN's recall set is its make/model/year's recalls - a superset of that exact VIN's campaigns. It is honest about what it is, not silently presented as VIN-exact.

### Modes

- **`rows`** (default) - one record per VIN, or per recall campaign.
- **`aggregate`** - `corpus=recalls` only: one count row per group, by `component`, `manufacturer`, `modelYear` or `recallMake`. Counted over the recall set NHTSA returned for the query (a make/model/year's recall set is small - the largest measured was 27 rows).

### Examples

**Decode a batch of VINs** - `corpus=vin`, `vins=["1HGCM82633A004352","WBA3A5C55DF357074"]`.

**A VIN and everything recalled on it** - `corpus=vin`, `vins=["1HGCM82633A004352"]`, `includeRecalls=true`.

**Takata inflator campaigns** - `corpus=recalls`, `campaignNumber=19V182`.

**What gets recalled on a 2003 Accord** - `corpus=recalls`, `make=honda`, `model=accord`, `modelYear=2003`, `mode=aggregate`, `groupBy=component`:

```
EXTERIOR LIGHTING:HEADLIGHTS                                6
AIR BAGS:FRONTAL:PASSENGER SIDE:INFLATOR MODULE             4
EXTERIOR LIGHTING                                           4
AIR BAGS:FRONTAL:DRIVER SIDE:INFLATOR MODULE                3
AIR BAGS                                                    2
VEHICLE SPEED CONTROL:ACCELERATOR PEDAL                     1
STEERING:HYDRAULIC POWER ASSIST:HOSE, PIPING, AND CONNECTIONS 1
ELECTRICAL SYSTEM:IGNITION                                  1
VISIBILITY:WINDSHIELD WIPER/WASHER:MOTOR                    1
POWER TRAIN:AUTOMATIC TRANSMISSION                          1
```

### Notes on the source

- **`ReportReceivedDate` is `DD/MM/YYYY`, not `MM/DD/YYYY`.** Measured across two endpoints: 35 values with the first component greater than 12, and none with the second greater than 12 - `27/06/2019`, `30/03/2017`. Read as US dates it would mis-date every recall, and `06/03/2019` is unreadable without the rule. It is normalised to ISO `YYYY-MM-DD` on output.
- **`Error Code` is a comma-joined set**, not one number - a clean VIN is `0`, a malformed one is `1,7,11,400`, spelled out in `decodeErrorText`. An invalid VIN is not an error from the API: NHTSA answers HTTP 200 with an error code, so a bad VIN is a decoded record that says so.
- **A 400 from the recalls feed means "not in NHTSA's vehicle table"**, not "this car has no recalls". Those are different statements, so a bad combination is reported back naming the make/model/year rather than served as silence. A vehicle with genuinely no recalls returns 200 with `Count: 0`.
- **Manufacturer casing is inconsistent in the source** - the same company appears as both `Honda (American Honda Motor Co.)` and `HONDA (AMERICAN HONDA MOTOR CO.)` across records. Group by it and both appear; that is the source, not a normalisation this actor applies.
- **Recall counts are verified complete.** The feed has no pagination, and every response is checked so that `Count` equals the rows returned - if NHTSA ever starts truncating, the run says so instead of serving a short recall history as a whole one.

### Source

NHTSA (US National Highway Traffic Safety Administration), two public services: the **vPIC** VIN decoder at `vpic.nhtsa.dot.gov` and the **recalls API** at `api.nhtsa.gov`. Public US government data, no login, no key.

Both are **lookup services, not bulk files**: one VIN decode is one request, and there is no endpoint that enumerates vehicles. So the request count is bounded by design - a hard ceiling of 500 VINs per run, an 8-wide worker pool, pacing jitter, and retry with backoff on 429/5xx. Measured from the build box on 2026-09-13: **no throttling was observed at 50 concurrent decodes (13 req/s), and none in 100 sequential calls** - the ceiling reached was our own concurrency, not NHTSA's. That is not read as "no limit exists": the bounds stay in place because an actor where request count equals user input length is one that can be aimed at the API with a 100,000-VIN list.

A VIN that cannot be fetched after retries is **not** dropped and **not** emitted blank - it comes back as a record with `decodeErrorCode: FETCH_FAILED` and the reason in `error`, so a short answer is visible as short. If failures look systemic (half of a batch), the run fails instead, because that is the source being down and not a bad VIN.

### Output

Every record carries the same key set - all 98 keys - whichever corpus or mode produced it, with `''` for absent values, so a dataset column never shifts row to row. The envelope (`platform`, `source`, `corpus`, `recordKind`, `mode`, `truncated`, `error`, `vin`, `recallScope`, `groupKey`, `groupCount`, `groupBy`) is identical across corpora; `groupKey`/`groupCount`/`groupBy` are `''` outside aggregate mode.

### Related actors

- **FMCSA Carrier Safety** - the federal motor-carrier register: inspections, violations, operating authority and out-of-service orders for the carriers these vehicles run under.

# Actor input Schema

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

rows = one record per VIN, or per recall campaign for the queried vehicle (default). aggregate = one count row per group of the fetched recall set; available for corpus=recalls only.

## `corpus` (type: `string`):

vin = decode VINs from the vins list. recalls = NHTSA safety recalls by make/model/modelYear, or by campaignNumber.

## `vins` (type: `array`):

corpus=vin. VINs to decode, 11-17 characters each. Blank = a built-in list of 3 real VINs, so the default run always returns data. Each VIN is one request; maxVins caps how many are processed.

## `includeRecalls` (type: `boolean`):

corpus=vin. After decoding, pull the safety recalls for each VIN's make/model/year and emit them alongside the decode records. Note: NHTSA returns recalls by make/model/year, not by VIN.

## `maxVins` (type: `integer`):

corpus=vin. Cap on how many VINs are decoded (each is one HTTP request). Default 25; hard limit 500 per run. VINs beyond the cap are dropped, and the run log and record metadata name them rather than letting them vanish.

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

corpus=recalls. A recall campaign number such as '19V182' or '19V182000' - both resolve to the same campaign. Takes precedence over make/model/modelYear when set.

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

corpus=recalls. Vehicle make as NHTSA stores it, e.g. 'honda', 'ford', 'tesla'. Required with model and modelYear unless campaignNumber is set. Case-insensitive.

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

corpus=recalls. Vehicle model as NHTSA stores it, e.g. 'accord', 'f-150', 'model 3'. Required with make and modelYear unless campaignNumber is set.

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

corpus=recalls. Four-digit model year, e.g. '2003'. A range ('2003,2004') or a 2-digit year is rejected by NHTSA. Required with make and model unless campaignNumber is set.

## `groupBy` (type: `string`):

mode=aggregate, corpus=recalls only. Which field of the fetched recall set to count by. Counted over the recalls NHTSA returned for the query.

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

Cap on records emitted in the run, VIN decodes and recalls together. 0 = the internal ceiling. Records beyond it are not emitted.

## Actor input object example

```json
{
  "mode": "rows",
  "corpus": "vin",
  "vins": [],
  "includeRecalls": false,
  "maxVins": 25,
  "campaignNumber": "",
  "make": "",
  "model": "",
  "modelYear": "",
  "groupBy": "component",
  "maxResults": 50
}
```

# Actor output Schema

## `recordsUrl` (type: `string`):

Decoded VIN records, recall campaign records or aggregates - as JSON

## `datasetUrl` (type: `string`):

No description

## `runUrl` (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("j0401/nhtsa-vehicles").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("j0401/nhtsa-vehicles").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 j0401/nhtsa-vehicles --silent --output-dataset

```

## MCP server setup

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

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/orkbyRrhPFuOxmRKx/builds/bgAIznSie6QVZ14J4/openapi.json
