# VIN Decoder & Recalls API - NHTSA Vehicle Lookup (`dami_studio/vin-decoder-recalls-scraper`) Actor

VIN decoder: decode up to 1,000 VINs a run against the US federal vehicle record. Each row has make, model, year, trim, engine, plant and safety equipment, plus the open NHTSA safety recalls for that model: component, consequence and remedy. $0.90 per 1,000 vehicles.

- **URL**: https://apify.com/dami_studio/vin-decoder-recalls-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Automation, Other, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.87 / 1,000 vins

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 for up to 1,000 VINs a run, with the US safety recalls filed against each vehicle's make, model and year on the same row**: make, model, year, trim, engine, body, assembly plant and safety equipment, the way the US federal record has them, plus every open recall filed against that make, model and year, with the fault, what it can lead to and the fix the manufacturer owes.

It is not a history report, though. Nothing about crashes, titles, mileage or owners, and no way to see whether a recall was already repaired on that particular car.

| | |
|---|---|
| **Input** | 17-character VINs, one per line |
| **Output** | One row per decoded vehicle: specification, plant, safety equipment and its open recalls |
| **Ceiling** | 1,000 VINs per run |
| **Account needed** | None, and no API key |
| **Price** | $0.90 per 1,000 vehicles on the Free plan, a little less on paid plans. The free plan's $5 a month covers about 5,500 |

### 🔍 What VIN Decoder & Recalls API does

Each VIN is decoded against NHTSA's own vehicle record, the specification the manufacturer filed for that car.

Recalls are matched on make, model and year, because that is the level NHTSA publishes them at. So a campaign that only covers some build dates, one plant or one engine still shows up on every car of that model year. The `summary` names the range.

Model names do not always line up. The decoder calls a truck "Silverado" while the recall list files it as "SILVERADO 1500", "2500" and "3500". When the plain name finds nothing, the run tries the names NHTSA itself lists for that make and year, preferring the one that carries the VIN's own series, so a 1500 never picks up a 3500's recalls. `recallModelMatched` tells you which name matched.

And sometimes NHTSA's recall lookup returns nothing for a model its own index lists as recalled. In a check of 45 common US vehicles that happened on 5. Those rows say `recallLookup: "unavailable"` instead of claiming zero, so an unknown never passes for a clean record.

### 📋 What data you get from each VIN

| What you get | Field |
|---|---|
| Make, model, year, trim and series | `make`, `model`, `modelYear`, `trim`, `series` |
| Body, vehicle type, doors, drive and fuel | `bodyClass`, `vehicleType`, `doors`, `driveType`, `fuelTypePrimary` |
| Engine and transmission | `engineCylinders`, `displacementL`, `engineHorsepower`, `electrificationLevel`, `transmissionStyle`, `transmissionSpeeds` |
| Manufacturer, plant and weight class | `manufacturer`, `plantCountry`, `plantCity`, `plantState`, `gvwr` |
| Safety equipment | `abs`, `esc`, `tpms`, `airBagLocFront`, `airBagLocSide`, `airBagLocCurtain`, `seatBeltsAll` |
| Open recalls: campaign, date, component, summary, consequence, remedy, park-it warnings | `recalls`, `recallCount`, `recallLookup`, `recallModelMatched` |
| Anything the decoder flagged about the VIN | `nhtsaErrorCodes`, `nhtsaErrorText` |

### ▶️ How to decode VINs and check recalls in bulk

1. Open [VIN Decoder & Recalls API](https://apify.com/dami_studio/vin-decoder-recalls-scraper) and click **Try for free**.
2. Paste your VINs into **VINs**, one per line.
3. Set **Maximum VINs to process** to at least the number of VINs you pasted.
4. Leave **Attach open safety recalls** ticked, unless the specification is all you want.
5. Click **Start**, then export the dataset as CSV, JSON or Excel, filtered on `charged`.

Start it with no VINs and you get one free sample row showing the shape.

### 💰 How much does it cost to decode a VIN?

**$0.90 per 1,000 vehicles** on the Free plan, less on paid plans. That is $0.0009 per decoded vehicle, with recalls or without. The free plan's $5 a month covers about 5,500 vehicles.

Not charged: the sample row, values that are not VINs, VINs NHTSA has no vehicle for, duplicates in your list, and a vehicle whose recall lookup failed, which arrives free. A list of 400 VINs that all decode comes to $0.36.

### 📥 What you give it

```json
{
  "vins": ["1HGCM82633A004352", "3GCUKREC5JG123456", "5YJ3E1EA7JF006588"],
  "includeRecalls": true,
  "maxItems": 25
}
```

| Field | Default | What it is |
|---|---|---|
| `vins` | none; the box starts with three examples | Up to 1,000 VINs. Case, dashes and spaces do not matter, and duplicates are dropped before anything is charged. Left empty, you get the free sample row and no lookup. |
| `includeRecalls` | `true` | Attach open recalls. Turn it off for the specification alone; the price per vehicle is the same. |
| `maxItems` | `25` | How many VINs from the list get decoded, 1 to 1,000. The form starts at 25 too, so raise it when you paste more. |
| `proxyUrls` | empty | Optional, and only if you want the requests to go out through servers of your own, as `http://user:pass@host:port`. Leave it empty for a normal run. |

### 📤 What you get back

A real row from a run on 30 September 2026, with the recall list cut to the first of its seven campaigns and the remedy text shortened:

```json
{
  "ok": true,
  "charged": true,
  "recordType": "vehicle",
  "vin": "3GCUKREC5JG123456",
  "make": "CHEVROLET",
  "model": "Silverado",
  "modelYear": 2018,
  "trim": "LT",
  "bodyClass": "Pickup",
  "vehicleType": "TRUCK",
  "driveType": "4WD/4-Wheel Drive/4x4",
  "fuelTypePrimary": "Gasoline",
  "recallLookup": "ok",
  "recallCount": 7,
  "recalls": [
    {
      "campaignNumber": "19V645000",
      "reportReceivedDate": "2019-09-06",
      "component": "SERVICE BRAKES, HYDRAULIC:POWER ASSIST:VACUUM",
      "summary": "General Motors LLC (GM) is recalling certain 2015-2017 Cadillac Escalade, 2014-2018 Chevrolet Silverado, GMC Sierra, 2015-2018 Chevrolet Suburban, Tahoe, GMC Yukon vehicles.  The amount of vacuum created by the vacuum pump may decrease over time.",
      "consequence": "As the vacuum level drops, the brake assist decreases, increasing braking effort, extending the distance required to stop the vehicle, thereby increasing the risk of a crash.",
      "remedy": "GM will notify owners, and dealers will reprogram the Electronic Brake Control Module, free of charge.  The recall began October 8, 2019. ...",
      "notes": "Owners may also contact the National Highway Traffic Safety Administration Vehicle Safety Hotline at 1-888-327-4236 (TTY 1-800-424-9153), or go to www.safercar.gov.",
      "manufacturer": "General Motors LLC",
      "parkIt": false,
      "parkOutSide": false,
      "overTheAirUpdate": false
    }
  ],
  "series": "1500",
  "doors": 4,
  "engineCylinders": 8,
  "displacementL": 5.3,
  "engineHorsepower": null,
  "electrificationLevel": null,
  "transmissionStyle": null,
  "transmissionSpeeds": null,
  "manufacturer": "GENERAL MOTORS LLC",
  "plantCountry": "MEXICO",
  "plantCity": "SILAO",
  "plantState": null,
  "gvwr": "Class 2F: 7,001 - 8,000 lb (3,175 - 3,629 kg)",
  "abs": null,
  "esc": null,
  "tpms": "Direct",
  "airBagLocFront": "1st Row (Driver and Passenger)",
  "airBagLocSide": "1st Row (Driver and Passenger)",
  "airBagLocCurtain": "All Rows",
  "seatBeltsAll": "Manual",
  "recallModelMatched": "SILVERADO 1500",
  "nhtsaErrorCodes": "1",
  "nhtsaErrorText": "1 - Check Digit (9th position) does not calculate properly",
  "vehicleDescriptor": "3GCUKREC*JG",
  "scrapedAt": "2026-09-30T02:37:59.642Z"
}
```

| Field | How to read it |
|---|---|
| `recallLookup` | Read this before `recallCount`. `ok`: the recall list answered and the campaigns are attached. `none`: it answered and has no campaign for this vehicle. `unavailable`: the count is unknown, not zero. `skipped`: you turned recalls off. |
| `recallCount`, `recalls` | How many campaigns apply, and each one in full, with the "park it" and "park outside" warnings as `parkIt` and `parkOutSide`. The list stops at 60; `recallCount` still counts them all. |
| `recallModelMatched` | The model name the recall list matched. Usually the same as `model`. |
| `modelYear` | The model year, which is not always the year the car was built. |
| `trim` | Sometimes a slash-separated list, when one VIN pattern covers several trims. |
| `nhtsaErrorCodes`, `nhtsaErrorText` | Whatever the decoder flagged. Code `1`, a check digit that does not add up, is common on VINs that decode perfectly well. |

Any field the manufacturer never filed comes back as `null`. Older and low-volume vehicles have more of those than a recent mass-market car, and running them again does not change that.

### 🧾 Reading the output

| Row | How to spot it | Charged |
|---|---|---|
| A vehicle | `charged: true`, `recordType: "vehicle"` | yes |
| The sample row | `_sample: true`, `charged: false`. Only when the run had no VINs at all | no |
| A diagnostic row | `_diagnostic: true`, `charged: false`, and an `errorCode` | no |

The sample row carries `recordType: "vehicle"` as well, so filter on `charged`, not on `recordType`, to keep only the vehicles you paid for. Read `charged` as that marker, not as a receipt.

| `errorCode` | What happened |
|---|---|
| `BAD_INPUT` | The value is not 17 characters, or it contains I, O or Q, which no VIN does. |
| `NO_RESULTS` | NHTSA holds no vehicle for that VIN, which is common for cars never sold in the US, or it read the VIN as incomplete. |
| `RECALL_LOOKUP_FAILED` | The vehicle decoded but its recalls could not be fetched. The whole vehicle is in the row anyway, free. Run that VIN again later for its recalls. |
| `NETWORK` | The decoder did not answer for that group of VINs. |
| `TIME_BUDGET` | The run hit its time limit before it reached that VIN, or before it looked up its recalls. Nothing was charged for it. The run still ends as succeeded, with an amber status message, even when the time limit stopped it before any vehicle came back. |
| `UNEXPECTED_ERROR` | The run stopped early on an unexpected error. The results before this row are kept. |

On a long list of bad VINs, the first 50 failures get a row each and the rest are collected in one `NO_RESULTS` row whose `skipped` list names every VIN and its reason. If 150 VINs in a row produce no vehicle at all, the run stops and says so, since the list is clearly not VINs.

### 💡 What people use it for

- A lot full of trade-ins, where an open airbag or fuel-pump campaign should turn up before the car is listed, not after a buyer finds it.
- A fleet checked every week, where only the new campaigns matter: compare `recallCount` and the campaign numbers against the last run.
- A listings feed where sellers type the model any way they like, and the specification should come from the VIN instead.
- A book of VINs for claims or rating work that needs make, model, year, body and safety equipment without anyone retyping them.

### 🚧 What it does not do

- **No history.** No accidents, titles, salvage brands, odometer readings, thefts or service records. None of that is in the federal record.
- **No owners.** Nothing about who owns or registered the car, and no field that would ask for it.
- **No per-car recall status.** A campaign that applies is not the same as a repair still owed. Whether the work was done is known only to the manufacturer and its dealers.
- **US vehicles only.** A car never certified for US sale decodes partly or not at all, and has no recall record here.
- **Nothing before 1981.** Older VINs are not 17 characters and are refused.
- **Only federally filed recalls.** Recalls issued outside the US, and service campaigns never filed with NHTSA, are not included.
- **At most 60 campaigns listed per row**, and 1,000 VINs per run. Split longer lists across runs.
- **Not instant on mixed lists.** Recalls are fetched once per make, model and year, so a list of the same few models is quick and hundreds of different models take minutes.
- Not an official NHTSA tool: Dami's Studio is independent and is not affiliated with or endorsed by NHTSA.

### 🧭 Which vehicle data scraper do you need?

| If you want | Use |
|---|---|
| The factory specification and open US recalls for a VIN | This one |
| Used-car listings in eight European countries | [AutoScout24 Scraper](https://apify.com/dami_studio/autoscout24-scraper) |
| Car listings in Brazil, priced against the FIPE table | [Webmotors Scraper](https://apify.com/dami_studio/webmotors-scraper) |
| Pump prices at UK filling stations | [UK Fuel Prices Scraper](https://apify.com/dami_studio/uk-fuel-prices-scraper) |

### ❓ Questions people ask

#### Is this a vehicle history report?

No, and it cannot be made into one. If the car's past is what you need, buy a history report.

#### Does a recall count of 0 mean the car is clean?

Only when `recallLookup` says `none`, and that covers recalls filed with NHTSA for that make, model and year, nothing more. When it says `unavailable`, the honest answer is that nobody knows yet.

#### Why does a row say the check digit does not calculate?

That is the decoder's code 1, and it is common on VINs that decode perfectly well. It often means one character was copied wrong, so read the VIN again, but the row is still a real decode.

#### What if a VIN is wrong or made up?

You get one free row saying why, and the run carries on with the others.

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

No. Paste the VINs and start. Nothing is kept between runs, so the same list every week on a schedule is safe.

#### Can I call it from code or connect it to an AI assistant?

The [API tab](https://apify.com/dami_studio/vin-decoder-recalls-scraper/api/python) has code for Python, JavaScript
and the CLI. MCP clients like Claude and ChatGPT use
`https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/vin-decoder-recalls-scraper`; coding assistants can load
[our skill
file](https://api.apify.com/v2/key-value-stores/BTBHU3DdiuoF4JOsK/records/apify-us-public-records-SKILL.md). All three
bill your Apify account at the same price.

#### Is it legal to look up VINs?

Both the specification and the recalls come from records the US government publishes. Apify's write-up on [the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/) is a good starting point, and we are not lawyers.

### 🆘 If something breaks

Open the **Issues** tab on the actor page. Include the run ID and the VINs that went wrong, with the `errorCode` from their rows.

# Actor input Schema

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

One or more 17-character Vehicle Identification Numbers. Up to 1,000 per run. Dashes and spaces are stripped, letter case does not matter, and duplicates are dropped before anything is charged. This Actor looks up no owner, registration or title data.

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

On by default. Each decoded vehicle gets its open US safety recall campaigns attached - component, summary, consequence and the manufacturer's remedy. Recall lookups are cached per make, model and year inside the run, so a list of the same few models costs very little extra time. Turn it off if you only want the specification decode; the price per VIN is the same either way.

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

A hard stop on how many of the VINs above are decoded, so a long pasted list cannot run away with your budget. Keep it low while you are testing - you pay per decoded vehicle. Hard ceiling 1,000 per run.

## `proxyUrls` (type: `array`):

Leave this empty for a normal run. Fill it in only if you want the traffic to leave through proxy servers you already pay for, one URL per line, in the form http://user:pass@host:port.

## Actor input object example

```json
{
  "vins": [
    "1HGCM82633A004352",
    "3GCUKREC5JG123456",
    "5YJ3E1EA7JF006588"
  ],
  "includeRecalls": true,
  "maxItems": 25
}
```

# Actor output Schema

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

Every row in the default dataset: vin, make, model, modelYear, trim, bodyClass, vehicleType, driveType, fuelTypePrimary, recallLookup, recallCount, recalls, series, doors, engineCylinders, displacementL, engineHorsepower, electrificationLevel, transmissionStyle, transmissionSpeeds, manufacturer, plantCountry, plantCity, plantState, gvwr, abs, esc, tpms, airBagLocFront, airBagLocSide, airBagLocCurtain, seatBeltsAll, recallModelMatched, nhtsaErrorCodes, nhtsaErrorText, vehicleDescriptor, scrapedAt. An empty, blocked or unmatched run returns a single uncharged row explaining what happened instead.

# 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",
        "3GCUKREC5JG123456",
        "5YJ3E1EA7JF006588"
    ],
    "includeRecalls": true,
    "maxItems": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/vin-decoder-recalls-scraper").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",
        "3GCUKREC5JG123456",
        "5YJ3E1EA7JF006588",
    ],
    "includeRecalls": True,
    "maxItems": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/vin-decoder-recalls-scraper").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",
    "3GCUKREC5JG123456",
    "5YJ3E1EA7JF006588"
  ],
  "includeRecalls": true,
  "maxItems": 25
}' |
apify call dami_studio/vin-decoder-recalls-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/vin-decoder-recalls-scraper"
        }
    }
}
```

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/mqkNoUAKzO16kccjV/builds/NjdAIIGsXMn16RFDg/openapi.json
