# VIN Decoder & Vehicle Recalls (`cliqtomedia/vin-decoder-vehicle-recalls`) Actor

Decode up to 500 VINs per run with official NHTSA vPIC data: make, model, year, trim, body, engine, fuel, drive and plant, with an honest complete/partial/invalid status. Adds NHTSA recall campaigns for each make/model/year.

- **URL**: https://apify.com/cliqtomedia/vin-decoder-vehicle-recalls.md
- **Developed by:** [Cliqto Media](https://apify.com/cliqtomedia) (community)
- **Categories:** Business, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 vehicle decodeds

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

VIN Decoder & Vehicle Recalls turns a list of VINs into clean vehicle rows with official NHTSA vPIC data: make, model, model year, trim, body, engine, fuel, drive and assembly plant. For each make, model and year it also lists the NHTSA recall campaigns, with component, summary, consequence, remedy and report date.

It is made for fleet lists, dealer and auction inventories, insurance and lending checks, and data cleaning. No login and no API key are needed. A run with the three example VINs takes about 4 seconds and costs about $0.010.

![VIN Decoder & Vehicle Recalls: paste VINs, get decoded vehicles with complete, partial or invalid status and NHTSA recall campaigns for each make, model and year](https://api.apify.com/v2/key-value-stores/0m34xILQjW3h4nXev/records/README-BANNER-20261011.png)

### Contents

- [What it does](#what-it-does)
- [Quick start](#quick-start)
- [Use cases](#use-cases)
- [Input](#input)
- [Output](#output)
- [Pricing](#pricing)
- [Limits](#limits)
- [API and integrations](#api-and-integrations)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)
- [Data source](#data-source)
- [Support](#support)

### What it does

The Actor sends your VINs to the official [NHTSA vPIC](https://vpic.nhtsa.dot.gov/api/) batch decoder, 50 VINs per request. It writes **one vehicle row per VIN** with a clear `decodeStatus`:

- `complete` — vPIC decoded the VIN with no warnings (vPIC error code `0`).
- `partial` — the vehicle is known, but vPIC reported a problem, for example a wrong check digit (code `1`) or an incomplete VIN with `*` (code `6`). The decoded fields are still filled.
- `invalid` — vPIC could not find the make or the model year, or the value is not a VIN. The row says why (`decodeError`, `errorText`) and may have a fix in `suggestedVin`. It costs $0.0001.

Then the Actor groups your vehicles by make, model and year and asks the [NHTSA Recalls API](https://www.nhtsa.gov/nhtsa-datasets-and-apis) **once per group**. Ten VINs of the same 2019 Camry need only one recall request. The recall campaigns go to a separate **Recalls** dataset, one row per campaign and model, with the list of your VINs in `appliesToVins`.

Your VINs go only to vPIC. The Recalls API gets only make, model and year.

#### Good fit

- Decode a fleet, inventory or lead list of up to 500 VINs in one run.
- Find which NHTSA recall campaigns exist for the models in your list.
- Clean VIN columns: find typos, wrong check digits and values that are not VINs.

#### Not a fit

- This is **not** a VIN history report: no owners, accidents, title or mileage.
- It does **not** tell you whether a recall applies to one exact VIN, or whether the repair was done. Recalls are listed for the make, model and year. Check one VIN at [nhtsa.gov/recalls](https://www.nhtsa.gov/recalls) or with the manufacturer.
- vPIC covers vehicles sold in the United States. Other VINs may come back `partial` or `invalid`.

### Quick start

1. Open the **Input** tab.
2. Keep the three example VINs in **VINs** (`vins`) or paste your own, one per line.
3. Keep **Include NHTSA recalls** on.
4. Click **Start**.
5. Open the **Output** tab. **Vehicles** has one row per VIN. **Recalls** has the recall campaigns.

Expected result for the example: 3 vehicle rows (2003 Honda Accord `complete`, 2023 Tesla Model 3 `complete`, 2011 BMW X3 `partial` because the VIN has a `*`) and 40 recall rows (24 + 12 + 4).

### Use cases

| Buyer job | Input | Output | Key fields |
| --- | --- | --- | --- |
| Decode a fleet or dealer inventory | `vins` | Vehicles | `make`, `model`, `modelYear`, `trim`, `bodyClass`, `fuelTypePrimary` |
| See open safety campaigns for the models you own or sell | `vins`, `includeRecalls: true` | Recalls | `nhtsaCampaignNumber`, `component`, `consequence`, `remedy`, `appliesToVins` |
| Clean a VIN column before import | `vins`, `includeRecalls: false` | Vehicles | `decodeStatus`, `checkDigitValid`, `errorCodes`, `suggestedVin` |
| Plan EV or fuel analytics | `vins` | Vehicles | `electrificationLevel`, `fuelTypePrimary`, `engineCylinders`, `displacementL` |

### Input

| Field | Name in the form | Type | Required | Default / prefill | Allowed values | What it changes |
| --- | --- | --- | --- | --- | --- | --- |
| `vins` | VINs | array of strings | yes | prefill: 3 example VINs | 1–500 values | The VINs to decode. Spaces, dashes and case are ignored. Duplicates are decoded once. Use `*` for an unknown position, for example `5UXWX7C5*BA`. |
| `includeRecalls` | Include NHTSA recalls | boolean | no | `true` | `true` / `false` | `true` adds recall rows for each decoded make/model/year. `false` only decodes; it is faster and costs less. |
| `matchCandidateModels` | Try NHTSA model names | boolean | no | `true` | `true` / `false` | When the recall index does not know the decoded model name, look for up to 3 similar NHTSA model names. See [model names](#why-is-recalllookupstatus-not_in_recall_index). |

Notes:

- Empty lines are ignored and counted in `RUN_SUMMARY` warnings. A list with no values fails with a clear message.
- If none of the values can be a VIN (for example only `hello!`), the run fails with a clear message, no rows and no row charges.
- Values that look like a VIN but are wrong still go to vPIC, because vPIC can suggest a fix (`suggestedVin`). They come back as `invalid` ($0.0001 each).

#### Input example

```json
{
  "vins": ["1HGCM82633A004352", "5YJ3E1EA6PF384836", "5UXWX7C5*BA"],
  "includeRecalls": true,
  "matchCandidateModels": true
}
```

### Output

The run has three outputs, all in the **Output** tab:

| Output | Where | What is inside |
| --- | --- | --- |
| Vehicles | default dataset, view `vehicles` | one row per input VIN |
| Recalls | dataset `recalls`, view `recalls` | one row per recall campaign and make/model/year |
| Run summary | key-value store record `RUN_SUMMARY` | counts, warnings, request statistics |

The **Export** button on the run page exports the default dataset only (vehicles). To export recalls, open **Recalls** in the **Output** tab, or use the API link shown there.

#### Vehicle row example

Trimmed. Every vehicle row has the same keys; unknown values are `null`.

```json
{
  "rowType": "vehicle",
  "inputIndex": 1,
  "vin": "1HGCM82633A004352",
  "decodeStatus": "complete",
  "errorCodes": ["0"],
  "checkDigitValid": true,
  "make": "HONDA",
  "model": "Accord",
  "modelYear": 2003,
  "trim": "EX-V6",
  "bodyClass": "Coupe",
  "vehicleType": "PASSENGER CAR",
  "engineCylinders": 6,
  "displacementL": 3,
  "engineHp": 240,
  "fuelTypePrimary": "Gasoline",
  "electrificationLevel": null,
  "driveType": null,
  "plantCity": "MARYSVILLE",
  "plantCountry": "UNITED STATES (USA)",
  "recallLookupStatus": "found",
  "recallCount": 24,
  "recallCampaignNumbers": ["19V182000", "19E068000", "19V499000"],
  "note": "Recalls are listed for the make/model/year, not confirmed for this VIN. Check the VIN at https://www.nhtsa.gov/recalls.",
  "provider": "NHTSA vPIC",
  "fetchedAt": "2026-10-11T06:18:26.048Z"
}
```

#### Vehicle fields

| Field | Type | Meaning |
| --- | --- | --- |
| `rowType` | string | Always `vehicle` in this dataset (`recall` in the Recalls dataset). |
| `inputIndex` | integer | Position in your input after duplicates are removed. Sort by it to get your order back. |
| `inputVin`, `vin` | string | Your value and the normalized VIN. |
| `decodeStatus` | string | `complete`, `partial`, `invalid` or `error` (vPIC was down; the row is free). |
| `decodeError` | string or null | Short reason for `invalid` and `error` rows, for example `vPIC could not identify the make`. vPIC's own text is in `errorText`. `null` for `complete` and `partial`. |
| `errorCodes`, `errorText`, `additionalErrorText` | array, string | vPIC error codes and their text, for example `["1"]` wrong check digit, `["6"]` incomplete VIN. |
| `checkDigitValid` | boolean or null | Local check of the 9th character. `null` for incomplete VINs. |
| `suggestedVin`, `possibleValues` | string or null | vPIC hint for wrong positions. |
| `make`, `manufacturer`, `model`, `modelYear`, `trim`, `series` | string, integer | Vehicle identity. |
| `bodyClass`, `vehicleType`, `doors` | string, integer | Body. |
| `engineCylinders`, `displacementL`, `engineHp`, `engineModel` | integer, number, string | Engine. `displacementL` has 1 decimal. |
| `fuelTypePrimary`, `fuelTypeSecondary`, `electrificationLevel` | string | Fuel and electrification, for example `Electric`, `BEV (Battery Electric Vehicle)`. |
| `driveType`, `transmissionStyle`, `gvwr` | string | Drive, transmission, weight class. |
| `plantCity`, `plantState`, `plantCountry`, `plantCompanyName` | string | Assembly plant. |
| `recallLookupStatus` | string | `found` (campaigns listed), `none_reported` (the model is in the NHTSA recall index with 0 campaigns for this year), `candidates_found` (recalls of similar model names, check them), `not_in_recall_index` (the recall index does not know this model name; it does **not** mean no recalls), `skipped_incomplete_decode` (no make, model or year), `not_requested` or `error`. |
| `recallLookupError` | string or null | Why the recall lookup failed. |
| `recallCount`, `recallCampaignNumbers` | integer, array | Recall campaigns listed for this make/model/year. |
| `recallModelCandidates` | array | NHTSA model names that may match, for example `SILVERADO 1500`. |
| `note`, `provider`, `sourceUrl`, `fetchedAt` | string | Reminder about recalls, source, single-VIN vPIC link, time of the request. |

vPIC returns an empty string for many unknown values. The Actor turns empty strings and `Not Applicable` into `null`, and turns numbers into numbers.

#### Recall row example

Trimmed.

```json
{
  "rowType": "recall",
  "nhtsaCampaignNumber": "19V182000",
  "campaignType": "vehicle",
  "make": "HONDA",
  "model": "ACCORD",
  "modelYear": 2003,
  "matchType": "exact",
  "vehicleModelDecoded": "Accord",
  "appliesToVins": ["1HGCM82633A004352"],
  "component": "AIR BAGS:FRONTAL:DRIVER SIDE:INFLATOR MODULE",
  "consequence": "An explosion of an inflator within the driver frontal air bag module may result in sharp metal fragments striking the driver, front seat passenger or other occupants resulting in serious injury or death.",
  "remedy": "Honda will notify owners, and dealers will replace the driver's air bag inflator with an alternate inflator, free of charge. ...",
  "reportReceivedDate": "2019-03-06",
  "parkIt": false,
  "parkOutside": false,
  "overTheAirUpdate": false,
  "provider": "NHTSA Recalls API"
}
```

#### Recall fields

| Field | Type | Meaning |
| --- | --- | --- |
| `nhtsaCampaignNumber` | string | NHTSA campaign number, for example `19V182000`. |
| `campaignType` | string | `vehicle`, `equipment`, `tire`, `child_seat` or `unknown`. |
| `make`, `model`, `modelYear` | string, integer | The make/model/year in the recall index. |
| `matchType` | string | `exact`, or `candidate_model` when the row comes from a similar NHTSA model name. Check the model before you rely on it. |
| `vehicleModelDecoded` | string | The model name vPIC returned for your VINs. |
| `appliesToVins` | array | Your VINs with this make/model/year. It does not mean each VIN is affected. |
| `manufacturer`, `component`, `summary`, `consequence`, `remedy`, `notes` | string | Recall text from NHTSA. |
| `reportReceivedDate` | string | Date NHTSA got the report, `YYYY-MM-DD`. |
| `parkIt`, `parkOutside`, `overTheAirUpdate` | boolean or null | NHTSA flags: do not drive, park outside, fix by software update. |
| `nhtsaActionNumber` | string or null | Related NHTSA investigation. |
| `provider`, `sourceUrl`, `fetchedAt` | string | Source, query link, time of the request. |

#### Status of the run

| Situation | What you see |
| --- | --- |
| All good | Run `SUCCEEDED`; status message like "3 vehicle row(s) (2 complete, 1 partial, 0 invalid, 0 decode error) and 40 recall row(s)." |
| Some VINs are wrong | Run `SUCCEEDED`; those rows are `invalid` with `decodeError` and `errorText`. |
| No recalls for a model | `recallLookupStatus: none_reported`, `recallCount: 0`. |
| Recalls API failed | `recallLookupStatus: error`. It is never shown as "0 recalls". Vehicle data is kept. |
| vPIC failed | Rows `decodeStatus: error` (free), run status message explains it. |
| No value can be a VIN | Run `FAILED` with a message. |

### Pricing

This Actor uses pay per event. You pay only for these events:

| Event | When | Price |
| --- | --- | ---: |
| Actor start | once per run (per GB of memory; the default 256 MB = 1 event) | $0.002 |
| Vehicle decoded | each `complete` or `partial` vehicle row | $0.0014 ($0.0012–0.0013 on paid Apify plans) |
| Recall record | each recall row | $0.0001 |
| Invalid VIN checked | each `invalid` vehicle row (reason and `suggestedVin`) | $0.0001 |

`error` rows (NHTSA was unavailable) are free. Platform usage is included in the price.

| Example | Events | Price |
| --- | --- | ---: |
| The 3 example VINs with recalls | 1 start + 3 vehicles + 40 recalls | $0.0102 |
| 100 VINs, recalls off | 1 start + 100 vehicles | $0.142 |
| 100 VINs of 20 models with recalls (about 7 recalls per model) | 1 start + 100 vehicles + about 140 recalls | about $0.156 |
| 500 VINs, recalls off | 1 start + 500 vehicles | $0.702 |
| 500 values that all turn out `invalid` | 1 start + 500 invalid | $0.052 |

The number of recall rows depends on the models: a 2003 Honda Accord has 24 campaigns, a new model may have none. Set **Maximum cost per run** in the run options to cap the price. When the cap is near, the Actor stops writing new rows and says so in `RUN_SUMMARY`.

### Limits

- Up to **500 VINs per run**. Time depends mostly on the number of unique make/model/year groups: one recall request per group. In our tests 500 VINs of 92 groups finished in about 60 seconds with 256 MB of memory. For more VINs, start several runs.
- The Actor waits at least 0.3 seconds between requests to NHTSA and retries a few times on errors. It stops source work after 240 seconds, keeps the rows it has and ends with status `CAPPED` or `PARTIAL` in `RUN_SUMMARY`.
- NHTSA data can change at any time. Each row has `fetchedAt`.

### API and integrations

- **API:** start the Actor with the [Apify API](https://docs.apify.com/api/v2) and read the vehicles and recalls datasets. The run's **Output** tab shows both API links.
- **Export:** CSV, JSON, Excel and more from each dataset.
- **Schedules and webhooks:** run the same VIN list every week with an Apify schedule to get fresh recall lists.

#### Use with AI agents (MCP)

- MCP server with only this Actor: `https://mcp.apify.com?tools=cliqtomedia/vin-decoder-vehicle-recalls`
- Smallest useful input: `{"vins": ["1HGCM82633A004352"]}`
- Key output fields: `decodeStatus`, `make`, `model`, `modelYear`, `recallLookupStatus`, `recallCampaignNumbers`; recall rows: `nhtsaCampaignNumber`, `component`, `remedy`.
- A run with a few VINs takes about 4 seconds and costs about $0.01. Up to 500 VINs per run.

### Troubleshooting

#### A VIN comes back `invalid`

Check `decodeError` and `suggestedVin`. A common reason is a typo, or the letters `I`, `O` or `Q`, which a VIN never has. Non-US VINs may also be unknown to vPIC.

#### A VIN comes back `partial`

Read `errorCodes` and `errorText`. Code `1` means the check digit is wrong: the VIN may have a typo. Code `6` means the VIN is incomplete. The decoded fields are still useful, but check them.

#### Why is `recallLookupStatus` `not_in_recall_index`?

vPIC and the recall index sometimes use different model names. For example vPIC says `Silverado`, the recall index has `SILVERADO 1500` and `SILVERADO 2500`. With `matchCandidateModels` on, the Actor adds recalls from up to 3 similar names and marks them `candidates_found` and `matchType: candidate_model`. With more than 3 similar names it only lists them in `recallModelCandidates`. This status does not mean the model has no recalls.

#### Some recalls do not apply to my VIN

That is expected. Recalls are listed for the whole make, model and year. A campaign can cover only some engines, plants or build dates. Check the VIN at [nhtsa.gov/recalls](https://www.nhtsa.gov/recalls).

### FAQ

#### Do I need an NHTSA account or API key?

No. Both NHTSA APIs are public.

#### Does it tell me if the recall repair was done?

No. NHTSA's public recall API does not have repair status per VIN.

#### Why are recalls in a separate dataset?

Vehicles and recalls have different columns. Two datasets give two clean tables. Join them by `vin` and `appliesToVins`, or by `make`, `model` and `modelYear`.

#### Is the output in my input order?

Rows are written in groups. Sort by `inputIndex` to get your order back.

### Data source

Data: [NHTSA vPIC](https://vpic.nhtsa.dot.gov/api/) and the [NHTSA Recalls API](https://www.nhtsa.gov/nhtsa-datasets-and-apis), U.S. government public information. This Actor is not affiliated with or endorsed by NHTSA or the U.S. DOT. NHTSA makes no guarantees about accuracy.

### Support

Use the **Issues** tab of this Actor. Add the run ID and the VINs or status you saw. Do not send API tokens or private data.

# Actor input Schema

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

Up to 500 vehicle identification numbers, e.g. 1HGCM82633A004352. Spaces, dashes and case are ignored; duplicates are decoded once. A partial VIN is allowed: use \* for an unknown position (e.g. 5UXWX7C5\*BA) and it is returned as decodeStatus partial. Values that are not VINs come back as invalid rows ($0.0001 each).

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

Look up NHTSA recall campaigns once per unique make/model/year and add them as recall rows. Recalls are listed for the model, not confirmed for each VIN. Default: true.

## `matchCandidateModels` (type: `boolean`):

When NHTSA's recall index does not know the decoded model name (e.g. vPIC says Silverado, NHTSA lists SILVERADO 1500), look up up to 3 recall-index models that start with the same name and mark those rows matchType candidate_model. Off: such vehicles get recallLookupStatus not_in_recall_index. Default: true.

## Actor input object example

```json
{
  "vins": [
    "1HGCM82633A004352",
    "5YJ3E1EA6PF384836",
    "5UXWX7C5*BA"
  ],
  "includeRecalls": true,
  "matchCandidateModels": true
}
```

# Actor output Schema

## `vehicles` (type: `string`):

One decoded row per input VIN.

## `recalls` (type: `string`):

NHTSA recall campaigns per make/model/year.

## `runSummary` (type: `string`):

Status, counts, charged rows, source attribution and request statistics.

# 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 = {
    "vins": [
        "1HGCM82633A004352",
        "5YJ3E1EA6PF384836",
        "5UXWX7C5*BA"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cliqtomedia/vin-decoder-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 = { "vins": [
        "1HGCM82633A004352",
        "5YJ3E1EA6PF384836",
        "5UXWX7C5*BA",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("cliqtomedia/vin-decoder-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 '{
  "vins": [
    "1HGCM82633A004352",
    "5YJ3E1EA6PF384836",
    "5UXWX7C5*BA"
  ]
}' |
apify call cliqtomedia/vin-decoder-vehicle-recalls --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cliqtomedia/vin-decoder-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/OhEARjRS8OcSQtaJE/builds/A4OMnyK6jkAGrsVOP/openapi.json
