VIN Decoder API & VIN OCR — Decode VINs, Read VINs from Photos, Recalls, Safety Ratings, Fuel Economy
🚗 Get your API key: VIN Recognition/Decoder API on RapidAPI.
💬 Support on Telegram: @pintoflow — message us for the fastest response (questions, bugs, custom plans or new features).
A VIN decoder API and VIN OCR API for developers. Decode any 17-character Vehicle Identification Number into make, model, year, trim, engine and 100+ specs, extract a VIN from an image (door-jamb sticker, dashboard plate, registration document), and get NHTSA recalls, owner complaints, 5-star crash ratings, EPA fuel economy and vehicle history counts — all from one REST API with ready-to-run examples in Node.js, Python, PHP and cURL.
- Use cases
- Examples in this repo
- Quick start
- Response format
- Endpoints
- Error codes
- Do's and don'ts
- Data coverage and accuracy
- FAQ
- Support
- Car dealership & marketplace listings — auto-fill make, model, year, trim and engine from a VIN.
- Insurance & fleet apps — scan the VIN sticker with the phone camera instead of typing 17 characters.
- Used-car checks — show open recalls, owner complaints, crash-test ratings and history record counts.
- Parts & service shops — identify the exact engine/transmission before ordering parts.
- Data cleaning — validate VIN check digits and batch-decode up to 50 VINs per call.
| Language | File | What it does |
|---|---|---|
| cURL | examples/curl/examples.sh |
Every endpoint in one script |
| Node.js | examples/nodejs/decode-vin.js |
Validate → decode a VIN with retry on 502/503/504 |
| Node.js | examples/nodejs/extract-vin-from-image.js |
Read a VIN from a photo (file upload or URL) |
| Node.js | examples/nodejs/express-proxy.js |
Backend proxy so your RapidAPI key is never exposed |
| Python | examples/python/decode_vin.py |
Decode a VIN and print a summary |
| Python | examples/python/batch_decode_csv.py |
Batch-decode a CSV of VINs into an enriched CSV |
| Python | examples/python/vehicle_report.py |
Full report: recalls, complaints, ratings, MPG |
| Python | examples/python/extract_vin_from_image.py |
VIN OCR from a photo with alternatives |
| PHP | examples/php/decode_vin.php |
Decode a VIN with PHP cURL |
Set your key once and run any example:
export RAPIDAPI_KEY="your-key-from-rapidapi"Turn a Vehicle Identification Number (typed or photographed) into vehicle data:
- Decode a VIN into make, model, model year, trim, body, engine, transmission, plant and 100+ specs.
- Read a VIN from a photo of a door-jamb sticker, dashboard plate, windscreen tag or registration document.
- Recalls, owner complaints, NHTSA crash-test ratings and EPA fuel economy for the vehicle.
- Validate VINs (length, illegal characters, check digit) without spending time on a full decode.
- Batch decode up to 50 VINs in one call.
- One-call vehicle report that combines all of the above.
All requests go through RapidAPI. Send your key in the usual headers:
curl --request GET \
--url 'https://vin-recognition-decoder.p.rapidapi.com/decode?vin=1HGCM82633A004352&compact=true' \
--header 'X-RapidAPI-Key: <YOUR_KEY>' \
--header 'X-RapidAPI-Host: vin-recognition-decoder.p.rapidapi.com'{
"success": true,
"data": {
"vin": "1HGCM82633A004352",
"make": "Honda",
"model": "Accord",
"modelYear": 2003,
"trim": "EX-V6",
"bodyClass": "Coupe",
"engine": { "cylinders": 6, "displacementL": 3, "horsepower": 240, "configuration": "V-Shaped" },
"transmission": { "style": "Automatic", "speeds": 5 },
"plant": { "city": "Marysville", "state": "Ohio", "country": "United States (USA)" },
"decodeQuality": "complete"
},
"meta": { "requestId": "c1f0…", "tookMs": 412 }
}Every response has the same envelope, so you can handle all endpoints with the same code.
Success (HTTP 200)
{ "success": true, "data": { … }, "meta": { "requestId": "…", "tookMs": 123, "warnings": ["…"] } }meta.warnings is only present when there is something you should know (for example a check-digit mismatch). Always read it.
Error (HTTP 4xx / 5xx)
{
"success": false,
"error": { "code": "INVALID_VIN", "message": "VIN must be exactly 17 characters, got 16.", "details": { … } },
"meta": { "requestId": "…" }
}error.codeis stable. Build your logic on it.error.messageis human-readable and may change wording.error.detailsis optional extra context, such as a corrected VIN suggestion.- Quote
meta.requestIdwhen you contact support.
VIN parameters are case-insensitive. Spaces, dashes and dots are ignored, so 1hg-cm826 33a004352 works. /decode, /decode-basic, /validate, /report and /history-check also accept the VIN as a path segment, for example /decode/1HGCM82633A004352.
| Parameter | Required | Description |
|---|---|---|
vin |
yes | 17-character VIN |
compact |
no | true returns only the summary, without the long specs object |
modelYear |
no | Helps when position 10 is ambiguous (rare) |
strict |
no | true rejects VINs whose check digit is wrong (422 instead of a warning) |
Returns the summary fields shown above plus:
specs: every non-empty field NHTSA has, such as airbags, ADAS features, GVWR, wheelbase, battery and seat belts.decodeQuality: one of the values below.complete: decoded cleanly.partial: some positions could not be decoded. ReaddecodeNotes.not_found: only the data encoded in the VIN itself is available.
possibleModelYears: model-year codes repeat every 30 years, so this lists every candidate.origin: region, country and WMI.
Aliases kept for older integrations: /get-vin, /get-vin2.
Uses no external data source, so it is the fastest and most reliable call. It returns region, country, manufacturer, WMI/VDS/VIS sections, model-year candidates, plant code and serial number. It works for VINs from any country. Alias: /simple-vin.
Returns valid, structurallyValid, checkDigit { required, actual, expected, valid }, errors[], warnings[], and a suggestion when the VIN contains I, O or Q, which are never used in VINs. It always returns HTTP 200, even for an invalid VIN, because the validation result is the answer.
{ "vins": ["1HGCM82633A004352", "5YJ3E1EA7KF317000"] }You can send up to 50 VINs. Each item has its own success flag and either data or error, so one bad VIN does not fail the whole batch. Add ?full=true to include specs.
Send the image in one of these ways:
| Method | How |
|---|---|
| File upload | multipart/form-data, field name file (or image) |
| Base64 | JSON { "image_base64": "<base64 or data:image/...;base64,...>" } |
| Public URL | JSON { "image_url": "https://…/photo.jpg" } |
| Query parameter | Description |
|---|---|
decode |
Defaults to true, which also decodes the VIN that was found. Use false for a faster OCR-only result. |
include_text |
true also returns the raw OCR text |
Supported formats are JPEG, PNG, WEBP, BMP, GIF and TIFF, up to 8 MB.
curl -X POST 'https://vin-recognition-decoder.p.rapidapi.com/extract-vin' \
-H 'X-RapidAPI-Key: <YOUR_KEY>' -H 'X-RapidAPI-Host: vin-recognition-decoder.p.rapidapi.com' \
-F 'file=@door-sticker.jpg'The response includes:
vin: the best candidate.checkDigitValid.correctedFromOcr:truewhen look-alike characters were fixed, for example an O read as 0.alternatives[]: other candidates found in the image.vehicle: the decoded summary.
Alias: /extract-vin34.
?vin=… returns the following sections:
vehicle: full decode.recalls.complaints: a summary.safetyRatings.fuelEconomy.history: counts of history records.
The sections are fetched in parallel. If one source is down, that section is null and the reason appears in meta.sectionErrors, while the rest of the report is still returned.
Takes either vin, or make + model + year. It returns NHTSA safety recalls with the following fields:
campaignNumbercomponentsummaryconsequenceremedyreportReceivedDateparkItparkOutsideoverTheAirUpdate
The newest recalls come first.
Takes either vin, or make + model + year. Optional parameters:
| Parameter | Description |
|---|---|
limit |
Default 20, maximum 100 |
offset |
Defaults to 0 |
component |
Filter by component, for example AIR BAGS |
The response has a summary with the total, crashes, fires, injuries, deaths and the top 10 components, plus a page of complaints with the newest first.
Takes either vin, or make + model + year. It returns NHTSA 5-star ratings (overall, frontal, side, pole and rollover), the safety technology, and links to crash-test photos for each body style.
Takes either vin, or make + model + year. It returns EPA data for each variant:
- City, highway and combined MPG.
- For electric vehicles: kWh per 100 miles and range.
- CO₂ in grams per mile.
- Annual fuel cost and EPA scores.
When the request uses a VIN, the variant whose engine matches the decoded engine is flagged with bestMatch: true.
?vin=… returns how many history records exist for the VIN (damages, odometerReadings, events and photos) plus hasDamageRecords. It only gives counts. It does not return the records themselves.
?code=1HG takes the first 3 characters of a VIN and returns the manufacturer, region and country.
/makes?type=car&search=hon:typeis optional. Examples arecar,truck,motorcycle,busandmultipurpose passenger vehicle (mpv)./models?make=honda&year=2020&type=car:yearandtypeare optional.
Liveness check.
| HTTP | error.code |
Meaning / what to do |
|---|---|---|
| 400 | MISSING_VIN |
Add ?vin= |
| 400 | INVALID_VIN |
Wrong length or characters. See details.errors and details.suggestion. |
| 422 | CHECK_DIGIT_MISMATCH |
Only returned with strict=true. The VIN is probably mistyped. |
| 422 | VEHICLE_NOT_IDENTIFIED |
Make, model or year could not be decoded, so model-level data is unavailable. Pass make, model and year instead. |
| 400 | MISSING_VEHICLE |
Pass vin, or all of make, model and year. |
| 400 | INVALID_YEAR, INVALID_LIMIT, INVALID_OFFSET, INVALID_WMI, MISSING_MAKE |
Fix the parameter |
| 400 | MISSING_VINS |
The batch body must be {"vins": [...]}. |
| 413 | TOO_MANY_VINS |
Send no more than 50 VINs per batch. |
| 400 | INVALID_JSON |
The body is not valid JSON. |
| 400 | MISSING_IMAGE, INVALID_UPLOAD, INVALID_BASE64, INVALID_IMAGE_URL, IMAGE_DOWNLOAD_FAILED, EMPTY_IMAGE |
Fix the image input. |
| 413 | IMAGE_TOO_LARGE, PAYLOAD_TOO_LARGE |
Images must be 8 MB or smaller. |
| 415 | UNSUPPORTED_IMAGE_TYPE |
Send JPEG, PNG, WEBP, BMP, GIF or TIFF. |
| 422 | VIN_NOT_FOUND_IN_IMAGE |
No VIN was readable. Retake the photo (see the tips below). |
| 404 | WMI_NOT_FOUND, ROUTE_NOT_FOUND |
Unknown WMI or endpoint |
| 503 | BUSY |
Too many images are being processed. Retry after the Retry-After header. |
| 502 | UPSTREAM_UNAVAILABLE |
A data source is temporarily down. Retry later. |
| 504 | UPSTREAM_TIMEOUT, OCR_TIMEOUT |
Retry. For OCR, send a smaller and sharper image. |
| 500 | INTERNAL_ERROR |
Retry. If it persists, contact support with the requestId. |
Retry only 502, 503 and 504, with backoff (for example 2 s, then 5 s, then 15 s). Retrying a 4xx gives the same answer every time and still counts against your quota.
- Validate first with
/validateor/decode-basicwhen the VINs come from user input. These calls are instant and catch typos before you spend a full decode. - Use
compact=trueon/decodewhen you only need the main fields. The response is about half the size. - Use
/decode/batchfor lists of VINs instead of many single calls. - Use
/reportwhen you need several data sets for one VIN. It is one request instead of five. - Check
meta.warningsanddecodeQualitybefore trusting a result blindly. - Cache results on your side. A VIN's specs never change. Recalls and complaints change slowly, so refreshing them daily is plenty.
- For photos, follow these tips:
- Crop to the VIN area.
- Shoot straight on, in good light, without glare.
- Make sure the VIN text is at least about 30 px high.
- Door-jamb stickers and registration documents read best. Stamped chassis numbers are the hardest.
- Show
alternativesto your user whencheckDigitValidisfalseon an OCR result. Let them confirm the VIN. - Handle errors by
error.code, not by HTTP status alone or by the message text.
- Don't send pre-1981 or non-standard IDs (fewer than 17 characters, or chassis or frame numbers). They are rejected with
INVALID_VIN. - Don't "fix" I, O or Q yourself. They never appear in VINs.
/validatereturns asuggestionfor you. - Don't expect full detail for every country. See the coverage section below.
- Don't treat
/recallsas proof that a specific car still needs a repair. Recalls are listed per make, model and year. Whether a particular vehicle has been repaired is only known by the manufacturer, or by the VIN lookup at nhtsa.gov/recalls. - Don't treat
/history-checkas a full history report. It tells you how many records exist, not what they say. - Don't send huge images or PDFs. Resize photos to about 1500–2500 px on the long side. That is faster and usually more accurate.
- Don't retry 4xx errors in a loop. You will burn your quota on the same answer.
- Don't call the API directly from a public web page or mobile app with your RapidAPI key embedded. Proxy it through your backend so your key can't be stolen.
- Don't use this data to make legal, insurance or safety-critical decisions on its own. Confirm against official documents.
| Data | Source | Coverage |
|---|---|---|
Specs (/decode) |
NHTSA vPIC | Best for vehicles sold in the USA and Canada. For European and Asian-market VINs you usually get make, manufacturer, plant country and model year, but often not the model or engine. |
Offline decode (/decode-basic, /validate, /wmi) |
ISO 3779 and the WMI registry | Worldwide |
| Recalls, complaints, crash ratings | NHTSA | US-market vehicles |
| Fuel economy | US EPA (fueleconomy.gov) | US-market vehicles, model year 1984 and later |
| History counts | Third-party vehicle-history index | Varies by vehicle; strongest for vehicles sold in the US and Europe |
Model year:
- In North America, position 10 of the VIN plus position 7 gives a single year.
- Elsewhere, position 10 can mean two years 30 years apart, for example
E= 2014 or 1984. The API returns the most recent plausible year and lists every candidate inpossibleModelYears. - Some manufacturers outside North America do not encode the year at all.
Check digit: position 9 is mandatory in North America and China. Elsewhere it is often just a filler, so a mismatch there is only a warning.
Why is model null for my European VIN?
The US government database only has full data for vehicles sold in North America. You still get make, manufacturer, country and year. Use /decode-basic for the structural data.
Why does /recalls?vin=… return 422 VEHICLE_NOT_IDENTIFIED?
The VIN could not be resolved to a model. Call /recalls?make=…&model=…&year=… instead.
Why do I get several fuel-economy variants?
EPA lists each engine and transmission separately. When you query by VIN, the variant matching the decoded engine is flagged with bestMatch: true.
Is the image stored? No. Images are processed in memory and discarded as soon as the response is sent.
💬 Need help? Contact us on Telegram: @pintoflow for the fastest response. Please include the meta.requestId from the response when reporting an issue.
Subscribe / get your key: https://rapidapi.com/belchiorarkad-FqvHs2EDOtP/api/vin-recognition-decoder
VIN decoder API, VIN lookup API, VIN OCR, VIN scanner, extract VIN from image, vehicle identification number decoder, NHTSA vPIC API, car recall API, vehicle safety ratings API, EPA fuel economy API, WMI lookup, vehicle history check, Node.js VIN decoder, Python VIN decoder.
The example code in this repository is MIT licensed. Use of the API is subject to the RapidAPI terms.