vinapi.app

API documentation

Authenticate with your API key, POST a VIN, and receive structured vehicle data as JSON. Structural fields are computed deterministically from the VIN standard; attribute fields come from data providers and are null when unknown — never guessed.

OpenAPI 3.1 spec →

Authentication

Create a key in your dashboard and send it as a bearer token (or the x-api-key header). Keep it secret — treat it like a password.

Header
Authorization: Bearer vin_live_...

Decode a VIN

POST /api/v1/decode

Body: { "vin": "<17 chars>" }

cURL
curl https://www.vinapi.app/api/v1/decode \
  -H "Authorization: Bearer vin_live_..." \
  -H "Content-Type: application/json" \
  -d '{"vin":"1HGCM82633A004352"}'
JavaScript
const res = await fetch("https://www.vinapi.app/api/v1/decode", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.VIN_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ vin: "1HGCM82633A004352" }),
});
const { data } = await res.json();
console.log(data.manufacturer, data.vehicle.modelYear, data.vehicle.bodyClass);
Python
import os, requests

res = requests.post(
    "https://www.vinapi.app/api/v1/decode",
    headers={"Authorization": f"Bearer {os.environ['VIN_API_KEY']}"},
    json={"vin": "1HGCM82633A004352"},
)
print(res.json()["data"])

Response

200 OK
{
  "data": {
    "vin": "1HGCM82633A004352",
    "squishVin": "1HGCM8263",
    "valid": true,
    "standard": "iso-3779",
    "checkDigit": { "expected": "3", "actual": "3", "valid": true, "applicable": true },
    "wmi": "1HG",
    "manufacturer": "Honda",
    "region": "North America",
    "country": "United States",
    "vehicleClass": null,
    "vehicle": {
      "make": "HONDA",
      "model": "Accord",
      "modelYear": 2003,
      "yearAmbiguous": false,
      "yearCandidates": [],
      "series": null,
      "trim": "EX-V6",
      "bodyClass": "Coupe",
      "vehicleType": "PASSENGER CAR",
      "doors": 2
    },
    "engine": { "cylinders": 6, "displacementL": 2.998832712, "fuelType": "Gasoline", "powerKW": null },
    "plant": "A",
    "serialNumber": "004352",
    "errors": [],
    "sources": { "structural": "internal", "attributes": "vpic" }
  }
}
FieldMeaning
squishVinPositions 1-8 + 10 — the vehicle pattern used for caching. Contains no serial number.
validStructural validity (17 chars, allowed charset). NOT gated on the check digit.
standardiso-3779 (modern 17-char), pre-1981, or non-standard.
checkDigitComputed vs. actual (position 9). valid:false is a data-quality signal — many valid EU/Asian VINs fail it, so it is never used to reject a VIN.
vehicle / engineAttribute fields from a data provider. Any unknown value is null — never guessed.
sources.attributesWhere attributes came from: cache, vpic, commercial, or null when unavailable (structural still returns).

Batch decode

POST /api/v1/decode/batch

Up to 50 VINs per call. Metered per VIN. A malformed VIN doesn't fail the batch — it returns with valid:false and its errors.

cURL
curl https://www.vinapi.app/api/v1/decode/batch \
  -H "Authorization: Bearer vin_live_..." \
  -H "Content-Type: application/json" \
  -d '{"vins":["1HGCM82633A004352","WVWZZZ1JZ3W386752"]}'

# → { "data": [ {…}, {…} ], "count": 2 }

Rate limits & quota

Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Per-minute and monthly limits depend on your plan; the monthly quota resets on the 1st (UTC). Only successful (2xx) decodes count against your quota.

Errors

Errors return { "error": { "code", "message" } }.

StatusCodeMeaning
400invalid_body / invalid_vin / invalid_batchBody isn't JSON, the VIN isn't 17 valid chars, or the batch is empty / over 50.
401missing_api_key / invalid_api_keyNo key provided, or the key is wrong/revoked.
402subscription_inactiveThe account's subscription isn't active.
429rate_limited / quota_exceededPer-minute limit or monthly quota reached (see Retry-After).
5xxdecode_failedTemporary decode error — retry with backoff.