# NHTSA VIN Decoder API — Batch vPIC VIN Lookup (`malonestar/nhtsa-vpic-vin-decoder`) Actor

Batch-decode VINs via NHTSA vPIC into compact, normalized rows: make, model, year, body class, drivetrain, engine, GVWR, and plant country. One clean row per VIN. Built for fleet, auto-insurance underwriting, and used-car marketplace enrichment. Keyless government data.

- **URL**: https://apify.com/malonestar/nhtsa-vpic-vin-decoder.md
- **Developed by:** [Kyle Maloney](https://apify.com/malonestar) (community)
- **Categories:** Lead generation, Developer tools, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.75 / 1,000 results

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/platform/actors/running/actors-in-store#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 VIN Decoder API — Batch vPIC VIN Lookup

Decode a batch of **VINs** into clean, normalized rows using the official
**NHTSA vPIC** database (keyless, free, US government source). Every VIN returns
**one row with a stable 125-field schema** — make, model, year, body class,
drivetrain, engine, transmission, plant, occupant protection and the full NHTSA
ADAS/active-safety field set — ready to join straight into your fleet,
underwriting, or marketplace pipeline.

> **VIN decoder API for bulk vehicle lookups** — make, model, year, body class,
> drivetrain, engine, GVWR, plant, safety systems, normalized and batchable.

### Who it's for

- **Fleet & telematics teams** — enrich vehicle rosters from just the VIN.
- **Auto insurers & underwriting** — standardized vehicle attributes plus the
  safety-equipment fields that drive rating.
- **Used-car marketplaces & dealership tools** — auto-fill listing specs at scale.
- **Warranty & lending providers** — verify class, drivetrain and weight rating.
- **Data teams** — a stable-schema VIN feed you can decode thousands at a time.

### Decode confidence — read this before you trust a row

**NHTSA's `ErrorCode` is the entire correctness contract of a VIN decode**, and
it is a *closed vocabulary* of 15 published values. vPIC returns **combined**
codes, and `0` genuinely co-occurs with warnings.

Measured live on 2026-08-01, on this actor's own example VIN:

```
/decodevinvalues/1HGCM82633A004352?modelyear=2005
  -> ErrorCode "0,12"     Make=HONDA  Model=Accord  ModelYear=2005
/decodevinvalues/1HGCM82633A004352?modelyear=1999
  -> ErrorCode "0,12,14"  Make=HONDA  Model=""      ModelYear=1999
```

That VIN is a **2003** Accord. Code `12` is *"Model Year Warning — the model year
entered for decoding does not match the model year based on the 10th position in
VIN. Decoded data may not be accurate."* Earlier versions of this actor reported
both rows as `decode_valid: true`.

So `decode_valid` now means exactly one thing:

| `decode_valid` | Meaning |
|---|---|
| `true` | vPIC returned `ErrorCode` **exactly `"0"`** — decoded clean, check digit correct. |
| `false` | vPIC answered, and the decode is **not** clean. Read `decode_status`, `error_codes` and `additional_error_text`. |
| `null` | **vPIC did not answer.** A transport failure is not a statement about the VIN. |

`decode_status` gives the full picture: `clean`, `clean_with_warnings`
(vPIC decoded it but flags the data as possibly inaccurate), `partial` (some
vehicle data alongside error codes — an incomplete VIN still returns make and
model), `invalid`, `unavailable`, or `unknown`.

Other guarantees:

- **The closed vocabulary is asserted live on every run.** If NHTSA adds or
  removes an `ErrorCode` value, the run **fails** rather than classifying rows
  by guesswork.
- **A positive canary and a negative control run before anything is billed.** A
  known-good VIN must decode clean; a VIN containing `I`, `O` and `Q` (letters
  that cannot appear in a VIN) must **not**. If the decoder ever starts
  answering `0` for everything, the run fails instead of publishing a fleet of
  confident false decodes.
- **Truncation is never silent.** VINs dropped by `maxVins` are logged and
  counted in `vins_truncated` on every row.
- If vPIC answers for **no** VIN in the run, the run fails and bills nothing.

### Example input

```json
{
  "vins": [
    "1HGCM82633A004352",
    "JH4KA7561PC008269",
    { "vin": "5UXWX7C5XBA000000", "modelYear": 2011 }
  ],
  "extended": false,
  "maxVins": 10000
}
```

Each item is a plain VIN string, or `{ "vin": "...", "modelYear": 2013 }` to help
vPIC disambiguate older VINs. **Supplying a `modelYear` that conflicts with
position 10 of the VIN produces `ErrorCode 12`** and `model_year_warning: true` —
which is exactly the case this actor now reports honestly.

A `*` can stand in for the check digit or unknown positions; a partial VIN
decodes with `decode_status: "partial"` (measured: `5UXWX7C5*BA` returns
BMW X3 2011 with `ErrorCode 6`).

### Output fields

Every row carries the same 125 fields; a field vPIC did not populate is `null`.

#### Identity and status

| Field | Description |
|---|---|
| `vin` | The VIN as requested. |
| `decode_valid` | `true` only when `ErrorCode` is exactly `"0"`. `null` when vPIC did not answer. |
| `decode_status` | `clean`, `clean_with_warnings`, `partial`, `invalid`, `unavailable`, `unknown`. |
| `error_code` | The raw vPIC `ErrorCode`, e.g. `"0"`, `"0,12"`, `"1,7,400"`. |
| `error_codes` | The same value split into tokens. |
| `error_text` | vPIC's own `ErrorText`. |
| `error_code_meanings` | Each token expanded from NHTSA's published vocabulary. |
| `additional_error_text` | vPIC's free-text explanation, e.g. `"Invalid character(s): 1:I, 2:O, 3:Q."`. |
| `suggested_vin` | vPIC's corrected VIN, `!` marking each rejected position, e.g. `"1G1YY22!965105693"`. |
| `has_decode_warnings` | True when any "data may be wrong" code (2, 3, 4, 8, 11, 12, 14) was returned. |
| `model_year_warning` | True when `ErrorCode` includes `12`. |
| `check_digit_valid` | False when `ErrorCode` includes `1`. |
| `requested_model_year` | The `modelYear` you supplied, if any. |
| `vpic_status` | `ok` or `unavailable`. |
| `vpic_error` | Transport error behind `unavailable`. |
| `extended_decode` | Whether the extended endpoint was used. |
| `decoded_field_count` | How many fields vPIC actually populated (45 for the example Accord, 70 for a 2018 Tesla Model 3, 4 for an unregistered manufacturer). |
| `vins_truncated` | VINs dropped by `maxVins` and therefore **not** decoded. |
| `retrieved_at` | ISO timestamp of the decode. |

#### Vehicle

`make`, `model`, `model_year`, `manufacturer`, `manufacturer_id`, `make_id`,
`model_id`, `vehicle_type`, `body_class`, `series`, `series_2`, `trim`, `trim_2`,
`vehicle_descriptor`, `doors`, `gvwr`, `gvwr_to`, `curb_weight_lb`,
`ncsa_body_type`, `ncsa_make`, `ncsa_model`, `base_price_usd`, `note`.

#### Engine and drivetrain

`engine_cylinders`, `displacement_l`, `displacement_cc`, `displacement_ci`,
`engine_hp`, `engine_hp_to`, `engine_model`, `engine_manufacturer`,
`engine_configuration`, `valve_train_design`, `fuel_injection_type`, `turbo`,
`cooling_type`, `other_engine_info`, `fuel_type_primary`, `fuel_type_secondary`,
`drive_type`, `transmission_style`, `transmission_speeds`, `axles`, `wheels`,
`wheel_base_short`, `wheel_base_type`, `wheel_size_front`,
`wheel_size_rear`, `steering_location`, `brake_system_type`, `brake_system_desc`,
`top_speed_mph`.

#### Electrification

`electrification_level`, `ev_drive_unit`, `battery_type`, `battery_kwh`.

#### Body configuration

`bed_type`, `body_cab_type`, `seats`, `seat_rows`,
`entertainment_system`, `trailer_type`, `trailer_body_type`, `trailer_length_ft`,
`bus_type`, `bus_floor_config_type`, `custom_motorcycle_type`,
`motorcycle_chassis_type`, `motorcycle_suspension_type`. vPIC answers
`"Not Applicable"` for categories that do not apply to the vehicle; that is
vPIC's own answer and is preserved rather than nulled.

#### Occupant protection and active safety

`air_bag_loc_front`, `air_bag_loc_side`, `air_bag_loc_curtain`,
`air_bag_loc_knee`, `air_bag_loc_seat_cushion`, `seat_belts_all`,
`other_restraint_system_info`, `pretensioner`, `abs`, `esc`, `traction_control`,
`tpms`, `forward_collision_warning`, `cib`, `pedestrian_aeb`,
`blind_spot_monitoring`, `blind_spot_intervention`, `lane_departure_warning`,
`lane_keep_system`, `lane_centering_assistance`, `adaptive_cruise_control`,
`park_assist`, `rear_visibility_system`, `rear_cross_traffic_alert`,
`rear_automatic_emergency_braking`, `daytime_running_light`, `keyless_ignition`,
`dynamic_brake_support`, `auto_reverse_system`,
`automatic_pedestrian_alerting_sound`, `semiautomatic_headlamp_beam_switching`,
`adaptive_driving_beam`, `active_safety_sys_note`.

#### Manufacturing

`plant_country`, `plant_state`, `plant_city`, `plant_company_name`.

**Populating the optional columns.** Coverage is genuinely vehicle-dependent —
NHTSA's own response says a missing value means "NHTSA does not have data on the
specific variable", not that the feature is absent. Measured 2026-08-01:
a 2003 Honda Accord populates 45 fields, a 2018 Tesla Model 3 populates 66
(including `electrification_level` and the ADAS set), a 2014 Peterbilt 388
populates 37, and an unregistered manufacturer populates 4.
`additional_error_text` and `suggested_vin` populate on VINs with errors (the
example above with `IOQ1234567890ABCD`). The sparse fields all have a verified
populating VIN: `ncsa_*` on 29 of 33 VINs sampled, `plant_company_name` on 19,
`engine_manufacturer` and `brake_system_type` on a 2013 Ford F-150
(`1FTFW1ET5DFC10312`), `battery_kwh` on a 2016 Nissan Leaf (`1N4BZ0CP0GC300000`),
`trailer_length_ft` and `cooling_type` on a trailer VIN (`4X4TSMH23FS000000`),
and `blind_spot_intervention`, `lane_centering_assistance` and
`rear_automatic_emergency_braking` on a 2023 Tesla Model Y
(`7SAYGDEE0PF600000`). Five fields that never populated across 47 diverse VINs
were removed rather than shipped as permanently empty columns. The remaining
sparse fields also have a recorded populating VIN from the same census:
`series` and `series_2` on a 2017 Toyota Camry (`4T1BF1FK5HU123456`) and a 2018
Hyundai Tucson (`KM8J3CA46JU622173`), `wheel_base_type`, `top_speed_mph` and
`brake_system_desc` on the Camry, `engine_hp_to` on a Mack truck
(`1M2AX07C2FM020000`), `wheel_size_front`, `wheel_size_rear`,
`curb_weight_lb` and `entertainment_system` on a 2017 Toyota RAV4
(`JTMWFREV0HD100000`), `active_safety_sys_note` on the Tucson, and
`air_bag_loc_seat_cushion` on the RAV4. `vpic_error` is
`null` on a healthy run — that is good news, not a dead column.

### Use as an MCP tool

Exposed to AI agents via `mcp.apify.com` as a "decode these VINs" tool. The
three-state contract matters here: an agent must not read `decode_valid: null`
as "invalid VIN", and must not treat `decode_valid: false` on a
`clean_with_warnings` row as "no data" — the vehicle fields are populated, they
are just caveated by NHTSA.

### FAQ

**Why is `decode_valid` false on a row that clearly decoded?** Because vPIC
returned a warning code alongside `0` — almost always code `12`, a model-year
mismatch. Check `decode_status`, `error_code_meanings` and `model_year_warning`.
The vehicle data is there; NHTSA is telling you it may not be accurate.

**What is the batch limit?** This actor decodes serially so one bad VIN cannot
take down the rest of the batch. NHTSA's own `DecodeVINValuesBatch` endpoint
caps at **50** VINs per request (measured: 50 returns `Count: 50`; 51 and 100
both return `Count: 0`).

**Does `extended` return more fields?** It returns the same 154 field names but
populates roughly three more of them per VIN (measured on three VINs,
2026-08-01).

**Is this a determination?** No. It decodes the manufacturer's VIN submission to
NHTSA. It is not a title, odometer, accident or recall check.

### Data source (keyless, no anti-bot)

- `https://vpic.nhtsa.dot.gov/api/vehicles/decodevinvalues/{vin}` (and
  `decodevinvaluesextended`).
- `https://vpic.nhtsa.dot.gov/api/vehicles/GetVehicleVariableValuesList/Error Code`
  — the published `ErrorCode` vocabulary, asserted live on every run.

### Pricing (Pay Per Result)

Billed per dataset record returned: one row per VIN, including VINs that vPIC
reports as invalid (a verified negative is an answer). Runs that fail a drift
assertion, or lose vPIC entirely, emit nothing and bill nothing.

# Actor input Schema

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

List of Vehicle Identification Numbers (VINs) to decode. Each item can be a plain VIN string (e.g. "1HGCM82633A004352") or an object { "vin": "...", "modelYear": 2013 } to help vPIC disambiguate older VINs. A "\*" can stand in for the check digit or unknown positions (partial VINs decode but are flagged decode\_valid=false).

## `extended` (type: `boolean`):

Use the vPIC decodevinvaluesextended endpoint. Measured 2026-08-01: it returns the same 154 field names as the standard endpoint but populates roughly 3 more of them per VIN. Leave off for the faster standard decode.

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

Safety cap on how many VINs to decode in one run. VINs beyond the cap are NOT decoded, and every emitted row records how many were dropped in vins\_truncated.

## Actor input object example

```json
{
  "vins": [
    "1HGCM82633A004352",
    "JH4KA7561PC008269"
  ],
  "extended": false,
  "maxVins": 10000
}
```

# Actor output Schema

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

The default dataset of decoded VINs.

# 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",
        "JH4KA7561PC008269"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("malonestar/nhtsa-vpic-vin-decoder").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",
        "JH4KA7561PC008269",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("malonestar/nhtsa-vpic-vin-decoder").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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",
    "JH4KA7561PC008269"
  ]
}' |
apify call malonestar/nhtsa-vpic-vin-decoder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=malonestar/nhtsa-vpic-vin-decoder",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/DKgdK8PsdcA03QBf4/builds/UWauoPVaDmrbdmgnb/openapi.json
