# VIN Decoder: Bulk Vehicle Specs from NHTSA vPIC (`ataism/vin-decoder`) Actor

Decode VINs in bulk with NHTSA vPIC: make, model, year, trim, body, engine, plant and GVWR, plus a check-digit flag. $1 per 1,000 decoded; invalid VINs free.

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

## Pricing

$1.00 / 1,000 vin decodeds

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?

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: Bulk Vehicle Specs from NHTSA vPIC

Decode a list of **VINs (vehicle identification numbers)** into **make, model, model year, trim, body class, vehicle type, engine (cylinders, displacement, fuel), drive, transmission, plant (city, state, country), manufacturer and GVWR**. You get one row per VIN. The data comes from **NHTSA vPIC**, the US government's vehicle catalog (free, public, no key). Each VIN is first checked here: it must have the right format, and its **North-American check digit** is reported.

**Price: $1.00 per 1,000 decoded VINs ($0.001 per VIN that vPIC decoded)**, plus Apify's default Actor-start event. Invalid VINs, VINs vPIC cannot decode, and failed calls are free.

- **Up to 10,000 VINs per run**, sent in batches of 50 (vPIC's documented maximum), one call at a time.
- **Format check before any call:** exactly 17 characters, no I, O or Q. Spaces are removed and letters upper-cased. Entries that fail become free rows and are **never sent**.
- **Check digit flag:** `checkDigitValid` says whether position 9 matches the North-American formula. A wrong check digit is reported but not refused: vPIC still decodes the VIN, and VINs built for other markets often skip the check digit.
- **Curated or complete:** by default you get about 25 useful fields. With **Include all vPIC fields** on, each row also gets every non-empty vPIC variable (airbags, safety systems, battery, wheelbase …), at the same price.
- **Quick start:** press *Start*. The prefilled input decodes 3 public sample VINs (two from NHTSA's own API examples, one from Wikipedia's check-digit example). It takes under a minute (about 2 seconds of decoding) and costs $0.003.

> **Polite by design.** The Actor sends an honest User-Agent (`vin-decoder-actor/0.1 (https://apify.com/ataism/vin-decoder)`), makes one vPIC call at a time with at least 1 second between calls (5 seconds for runs of over 1,000 VINs during NHTSA's business hours), backs off on HTTP 429 and server errors, and stops when vPIC refuses. Your VINs are sent to NHTSA only, and are never written to the log.

### What it does

1. **Check.** Each entry is cleaned (whitespace removed, upper-cased) and checked: 17 characters from 0–9 and A–Z without I, O and Q. Duplicates are removed (a VIN is decoded and charged once per run). The check digit (position 9) is computed with the North-American formula (49 CFR 565: transliteration, weights 8-7-6-5-4-3-2-10-0-9-8-7-6-5-4-3-2, remainder 11 = X).
2. **Batch.** Valid VINs are grouped in input order into batches of up to 50, and each batch is one `POST https://vpic.nhtsa.dot.gov/api/vehicles/DecodeVINValuesBatch/` (format JSON). With **Model year** set, the year is sent with every VIN.
3. **Decode.** vPIC's answer is matched to each VIN. The row gets the curated fields, vPIC's own codes (`nhtsaErrorCode`, `nhtsaErrorText`) and, if asked, `allFields`.
4. **Charge.** Only VINs that vPIC decoded are charged (rule under *Pricing*). Rows are saved first, then charged.

Calls start at least 1 second apart. NHTSA asks that large batch jobs run at night (US Eastern time) or at weekends. So when a run has more than 1,000 VINs to send, its calls start at least 5 seconds apart on weekdays from 6 AM to 6 PM US Eastern time (NHTSA's business hours), checked before every call. A full run of 10,000 VINs then takes about 17 minutes instead of about 10. On HTTP 429 the Actor waits as long as `Retry-After` asks (up to 2 minutes), then doubles its pause between calls for the rest of the run. Server errors (5xx), timeouts and network errors are tried twice more, after 5 s and 10 s. A 401 or 403 is never retried.

### Input

| Field | What it does |
|---|---|
| **VINs** (`vins`) | VINs, one per line, up to 10,000 per run. Spaces and lower case are fine. Duplicates are decoded once. Partial VINs (with `*`) are not accepted. |
| **Model year (optional hint)** (`modelYear`) | Optional, 1980–2030. Sent with every VIN of the run. Position 10 of a VIN repeats every 30 years (A = 1980 or 2010), and NHTSA recommends sending the year. If it does not match the VIN, vPIC adds code 12. Use it only when all VINs in the run share a year. |
| **Include all vPIC fields** (`includeAllFields`) | Default off. On: each row also gets `allFields`, every non-empty vPIC variable by its vPIC name (values as text). Same price. |

#### Example inputs

**1. A fleet list (curated fields):**

```json
{
  "vins": ["5XYKT3A12CG000000", "3GNDA13D76S000000", "1M8GDM9AXKP042788"]
}
```

**2. Every vPIC field, for a data warehouse:**

```json
{
  "vins": ["1M8GDM9AXKP042788", "3D7KU28C54G000000"],
  "includeAllFields": true
}
```

**3. VINs of one model year, with the year hint:**

```json
{
  "vins": ["5XYKT3A12CG000000"],
  "modelYear": 2012
}
```

### Output

One item per unique entry. Example (prefill VIN from NHTSA's examples, curated fields, as decoded live on 2026-10-26):

```json
{
  "input": "5XYKT3A12CG000000",
  "vin": "5XYKT3A12CG000000",
  "valid": true,
  "checkDigitValid": false,
  "status": "decoded",
  "billable": true,
  "make": "KIA",
  "model": "Sorento",
  "modelYear": 2012,
  "trim": "GL(Middle-low grade)",
  "series": null,
  "bodyClass": "Sport Utility Vehicle [SUV]/Multipurpose Vehicle [MPV]",
  "vehicleType": "MULTIPURPOSE PASSENGER VEHICLE (MPV)",
  "doors": 4,
  "engineCylinders": null,
  "engineDisplacementL": 2.4,
  "engineHP": null,
  "engineModel": "DOHC MPI THETA-II",
  "engineConfiguration": null,
  "fuelTypePrimary": "Gasoline",
  "fuelTypeSecondary": null,
  "electrificationLevel": null,
  "driveType": "4x2",
  "transmissionStyle": null,
  "transmissionSpeeds": null,
  "manufacturer": "KIA GEORGIA, INC",
  "plantCountry": "UNITED STATES (USA)",
  "plantState": "GEORGIA",
  "plantCity": null,
  "gvwr": "Class 1C: 4,001 - 5,000 lb (1,814 - 2,268 kg)",
  "nhtsaErrorCode": "1",
  "nhtsaErrorCodes": [1],
  "nhtsaErrorText": "1 - Check Digit (9th position) does not calculate properly",
  "additionalErrorText": null,
  "suggestedVIN": null,
  "errorCode": null,
  "message": null,
  "modelYearHint": null,
  "rowKey": "5XYKT3A12CG000000"
}
```

Values come from vPIC as published. Empty vPIC values are `null`: NHTSA notes that a missing value means it has no data on that variable, not that the vehicle lacks the feature. Numbers (`modelYear`, `doors`, `engineCylinders`, `engineDisplacementL`, `engineHP`, `transmissionSpeeds`) are numbers; everything else is text. Every row has the same keys, so CSV and Excel exports line up. `input` is your entry exactly as given (at most 64 characters).

The dataset has three views: **Overview**, **Engine, drive and plant**, and **Codes and problems**.

#### Status and error codes

| `status` | `errorCode` | Meaning | Charged |
|---|---|---|---|
| `decoded` | – | vPIC identified the vehicle (rule below) | yes |
| `not-decoded` | `not-decoded` | vPIC answered but did not identify the vehicle, e.g. code 7 (manufacturer not registered with NHTSA) or 8 (no detailed data) | no |
| `invalid` | `invalid-length` | Not 17 characters after removing spaces (partial VINs included) | no, never sent |
| `invalid` | `invalid-characters` | Contains I, O, Q or a character other than 0–9 / A–Z | no, never sent |
| `error` | `source-unavailable` | vPIC answered 5xx three times (maintenance or outage) | no |
| `error` | `rate-limited` | vPIC answered 429 three times, or asked to wait more than 2 minutes | no |
| `error` | `refused` | vPIC answered 401/403 or a bot challenge; never worked around | no |
| `error` | `timeout`, `network`, `dns` | No answer in time, or the connection failed (after 2 retries) | no |
| `error` | `bad-response`, `too-large`, `no-result`, `http` | The answer was not usable, or had no result for this VIN | no |

**The run stops early** (with a `RUN_SUMMARY` saying why) when 3 batches in a row fail (`source-unavailable`), when 2 batches are refused (`source-refused`), when at least 500 sent VINs are free and outnumber the decoded ones (`unpaid-work-cap`), when your **Max total charge** is reached (`max-charge`), or after 50 minutes (`time-limit`). If vPIC refused or was down for the whole run and nothing was decoded, the run ends as **failed**, so you notice.

### Pricing

**$0.001 per decoded VIN (event `vin-decoded`)**, plus Apify's default Actor-start event. No start fee of our own.

A VIN counts as **decoded**, and is charged, only when vPIC returned a **non-empty Make** and **every** code in its ErrorCode list is one of the codes below. When a code says the decode is partial (2, 3, 4, 5, 6 or 14), vPIC must also have returned a **Model**. A garbled VIN that only yields the make from its first 3 characters is free.

| Code | vPIC meaning |
|---|---|
| 0 | VIN decoded clean |
| 1 | Check digit does not calculate (the rest decoded) |
| 2, 3, 4 | vPIC found an error in one position and suggests a corrected VIN (`suggestedVIN`); the decode is partial |
| 5 | Errors in a few positions (partial decode) |
| 6 | Incomplete VIN (partial decode) |
| 9, 10 | Glider or off-road vehicle warning |
| 11, 12 | Model-year code warning, or the year you sent does not match |
| 14 | Some characters could not be decoded (partial decode) |

**Free:** code 7 (manufacturer not registered with NHTSA), code 8 (no detailed data), code 400 (a character not allowed in that position; vPIC checks positions more strictly than the Actor's format check), any other code, an empty Make, a partial decode without a model, every invalid entry, and every failed call. Check `billable` on each row and `RUN_SUMMARY.charged` for the run.

| VINs decoded | Price |
|---:|---:|
| 3 (prefill) | $0.003 |
| 100 | $0.10 |
| 1,000 | $1.00 |
| 10,000 (one full run) | $10.00 |

The run never sends more VINs than your **Max total charge** can pay for, so the limit is respected exactly.

### Use it via API, n8n or Make

**cURL** (synchronous, returns the items):

```bash
curl -X POST "https://api.apify.com/v2/acts/ataism~vin-decoder/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"vins":["5XYKT3A12CG000000","1M8GDM9AXKP042788"]}'
```

**JavaScript** (`npm i apify-client`):

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('ataism/vin-decoder').call({ vins: ['5XYKT3A12CG000000', '1M8GDM9AXKP042788'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((x) => `${x.vin}: ${x.modelYear} ${x.make} ${x.model}`));
```

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient
import os
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("ataism/vin-decoder").call(run_input={"vins": ["5XYKT3A12CG000000", "1M8GDM9AXKP042788"]})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["vin"], item["modelYear"], item["make"], item["model"])
```

**n8n:** add the Apify node, choose **Run an Actor and get dataset**, Actor `ataism/vin-decoder`, and put `{"vins": [...]}` in the input JSON.

**Make:** use the Apify module **Run an Actor** (Actor `ataism/vin-decoder`, *Run synchronously* on), then **Get Dataset Items** with the run's dataset ID.

### Privacy and your responsibilities

In short:

1. **Only your own input is processed:** the VINs you supply. The Actor never searches for or collects VINs anywhere else.
2. **Sent to NHTSA only:** each valid VIN (and the model year, if you set one) goes to NHTSA's vPIC service, and nowhere else. No other data about you goes with it: no cookie, no key, no account name.
3. **Nothing kept beyond the run:** results go to your run's dataset. `RUN_SUMMARY` and `PROGRESS` hold counts and a hash of the input, no VINs. **VINs are never logged.**

- A VIN identifies a vehicle, not a person. But in the EU and UK, a VIN **can be personal data** when it is linked to an owner or keeper (for example in a fleet, insurance or dealer system). **You** supply the VINs and you are the data controller under GDPR / UK GDPR: you need a lawful basis, and you decide what to do with the results. The output holds vehicle facts from NHTSA only; it never contains owner, registration, history or location data.
- Decoded data is vPIC's, as published by NHTSA from manufacturers' filings. It can be incomplete or wrong; NHTSA asks users to report discrepancies to it. This is not a vehicle history, title, theft or recall check.
- You control retention of the dataset through your Apify settings.

### Limits and honest caveats

- **Up to 10,000 VINs per run** (20,000 lines including blanks and duplicates); split bigger lists. A full run of 10,000 takes about 10 minutes (200 vPIC calls of about 2–3 s each), or about 17 minutes during NHTSA's business hours (weekdays 6 AM–6 PM US Eastern), when large runs are paced at 5 s per call.
- **vPIC covers vehicles registered for the US market.** NHTSA's FAQ: about 99% decode confidence from model year 1995 for major brands, about 60–65% for 1980–1994, and no data before 1980. European, Japanese or other VINs of vehicles never sold in the US often come back as code 7 (free).
- **One source.** If vPIC is down (NHTSA schedules maintenance mostly at weekends), rows are free errors; run again later. NHTSA asks large batch users to run at night (US Eastern time) or at weekends; please schedule big lists that way (they also run faster then).
- **Model year hint applies to the whole run.** For mixed years, leave it empty: vPIC reads the year from position 10.
- **No partial VINs.** vPIC accepts `*` wildcards, but this Actor requires full 17-character VINs.
- Values are text as vPIC publishes them, except the six numeric fields.

### Source and terms

Data: **NHTSA Product Information Catalog and Vehicle Listing (vPIC)**, `https://vpic.nhtsa.dot.gov/api/`, run by the US National Highway Traffic Safety Administration. NHTSA's vPIC FAQ says: "NHTSA is a government agency and the services provided on the API are free for use by the public as an offering as a part of our Open Data initiatives". Works of the US federal government are not under copyright in the US (17 U.S.C. § 105). This Actor is not affiliated with or endorsed by NHTSA.

### Open-source components

`apify` 3.7.2 (Apache-2.0). Everything else is Node.js built-ins.

# Actor input Schema

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

Vehicle identification numbers, one per line, up to 10,000 per run. Duplicates are decoded and charged once. Partial VINs (with \*) are not accepted. A VIN can be personal data when it is linked to an owner: only add VINs you have the right to process.

## `modelYear` (type: `integer`):

Optional. Sent with every VIN of this run. NHTSA recommends it: position 10 of a VIN repeats every 30 years (A = 1980 or 2010), and the year helps vPIC pick the right one. When it does not match the VIN, vPIC adds warning code 12. Empty: vPIC works out the year from the VIN.

## `includeAllFields` (type: `boolean`):

Off: the curated fields (make, model, year, trim, body, engine, fuel, drive, transmission, plant, manufacturer, GVWR, vPIC codes). On: each row also gets allFields, an object with every non-empty vPIC variable (up to about 150: airbags, safety systems, battery, wheelbase …). Same price.

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

Internal: "selfTest" runs the maintainer health check and saves no items.

## Actor input object example

```json
{
  "vins": [
    "5XYKT3A12CG000000",
    "3GNDA13D76S000000",
    "1M8GDM9AXKP042788"
  ],
  "includeAllFields": false,
  "mode": "run"
}
```

# Actor output Schema

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

One item per VIN: make, model, year, trim, body, engine, drive, transmission, plant, manufacturer, GVWR, check-digit flag and NHTSA codes (JSON, CSV, Excel or XML).

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

Counts only (no VINs): charged and free rows, vPIC calls, retries, errors by code and why the run stopped.

# 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": [
        "5XYKT3A12CG000000",
        "3GNDA13D76S000000",
        "1M8GDM9AXKP042788"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ataism/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": [
        "5XYKT3A12CG000000",
        "3GNDA13D76S000000",
        "1M8GDM9AXKP042788",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("ataism/vin-decoder").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": [
    "5XYKT3A12CG000000",
    "3GNDA13D76S000000",
    "1M8GDM9AXKP042788"
  ]
}' |
apify call ataism/vin-decoder --silent --output-dataset

```

## MCP server setup

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

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/6Zjvmh2KfKR09WLqd/builds/grhgI5ihkkSnZWal0/openapi.json
