Decode a VIN with the Vehicles.dev API
Purpose
Resolve a 17-character VIN into canonical, normalized vehicle identity — year, make, model, trim, body_style, drivetrain, fuel, transmission, cylinders, doors, and a derived engine string — via the Vehicles.dev GET /v1/vehicles/vin/{vin} endpoint. Read-only: a decode never mutates account state, but each successful (2xx) lookup is a billable metered call (Starter $0.004, drawn from the plan's included calls first). The API is store-first: it returns a matching row from Vehicles.dev's scraped-listings dataset (origin: "store") and falls back to a live NHTSA vPIC decode (origin: "vpic") when the VIN is unknown.
When to Use
- You have a VIN and need structured make/model/year/trim identity from a backend service.
- Enriching inventory, appraisal, insurance, or fleet records keyed by VIN.
- Any batch VIN-normalization job where you'd otherwise scrape or call NHTSA vPIC directly and want a single store-first + vPIC-fallback surface.
- Not for: full build sheets with factory option codes (this is a VIN-pattern decode, not a per-car build record), vehicle history, recalls, or valuation — those are separate Vehicles.dev endpoints.
Workflow
This API is deliberately server-to-server and is NOT browser-callable. It serves no CORS headers, rejects any request carrying a Cookie or Origin header, and requires an Authorization: Bearer API key. There is no browser, cookie, or query-string auth path — a headless/automated browser cannot decode a VIN here (see the Browser note at the end). Call it from a backend with a key stored server-side.
Prerequisite: a Vehicles.dev API key (prefix vdev_), minted once in the dashboard and held in a server-side secret such as VEHICLES_API_KEY. The plaintext secret is shown exactly once at creation and is unrecoverable.
Recommended: raw HTTP GET
-
Normalize the VIN. Must be exactly 17 VIN-safe characters (letters/digits, never
I,O, orQ). The API upper-cases it server-side and echoes the upper-cased form in thevinfield, so case doesn't matter on input. For North American VINs (first character1–5) the decoder also validates the check digit and rejects a bad one. -
Issue the request to
https://api.vehicles.dev/v1/vehicles/vin/{vin}:curl -sS https://api.vehicles.dev/v1/vehicles/vin/5TDDZRBHXNS221317 \ -H "Authorization: Bearer $VEHICLES_API_KEY" \ -H "Accept: application/json"The scheme token is case-sensitive
Bearerfollowed by exactly one space —bearer, a double space, or any other scheme is rejected401 invalid_credential. Do not send aCookieorOriginheader. Timeout budget is 10 s. -
Read the response. On
200, parse the JSON body (see Expected Output). Branch behavior onorigin("store"vs"vpic"), not on field presence — null values are omitted, not emitted as null, so the key set insidevehiclevaries per VIN. Treat every field as optional. Keep thex-request-idresponse header for support tickets. -
Handle errors by branching on the stable
codeslug in theapplication/problem+jsonbody, never on the prosedetail(key order is not stable — parse the JSON). Key codes:400 invalid_vin,404 vin_not_decodable,503 decode_upstream_unavailable/decode_upstream_error(bothretryable: true— safe to retry with backoff),401 authentication_required/invalid_credential,402 insufficient_credits,403 plan_upgrade_required.
Alternative: official CLI
npm install -g vehicles-dev-cli
vehicles login # saves key to ~/.vehicles/credentials.json
vehicles decode 5TDDZRBHXNS221317 # same billable GET, prints the JSON body
Alternative: MCP server (for AI agents)
Wire the vehicles-dev-mcp npm package into any MCP client (Claude Desktop/Code, Cursor). It exposes VIN decode as the decode_vin tool. The key lives in the server's own env (VEHICLES_API_KEY) or ~/.vehicles/credentials.json; the agent never sees it, and each tool call is a direct metered request to api.vehicles.dev.
claude mcp add --transport stdio vehicles-dev -- npx -y vehicles-dev-mcp
Remote MCP is also configured at https://mcp.vehicles.dev/mcp (attachable via the Claude API with your vdev_ key as authorization_token).
Alternative: official SDKs
TypeScript (@vehicles-dev/sdk, decodeVin(vin)) and Python (vehicles_dev, decode_vin(vin)), both installed from immutable GitHub v0.1.1 tags (not yet on npm/PyPI). They wrap the same GET and handle bearer headers, problem-document parsing, and timeouts.
Browser (fallback) — not viable
There is no browser fallback for the decode itself. Confirmed live: opening https://api.vehicles.dev/v1/vehicles/vin/{vin} in a browser returns 401 cookie_credentials_rejected ("Credential rejected.") because the browser attaches a Cookie header the API refuses; a fetch without cookies returns 401 authentication_required. The only browser-adjacent VIN data on the site is the single worked example (5TDDZRBHXNS221317 → 2022 Toyota Highlander Limited) hard-coded in the /docs Quickstart — not a general decoder. To decode any VIN you must call the authenticated API from a backend.
Site-Specific Gotchas
- Not browser-callable, by design. No CORS headers; a
Cookieheader →401 cookie_credentials_rejected; anOriginheader (or a Host that doesn't resolve to a product) →401 invalid_origin/404 route_not_found. Don't waste time trying to drive this endpoint from a headless browser, an XHR/fetch in page context, or a CORS proxy — all confirmed blocked. Call it server-side. - Auth runs before schema validation. A request that is both unauthenticated and malformed returns
401, never400. Fix the credential first, then the parameters. - Bearer scheme is strict. Exactly
Bearer+ one space + thevdev_...key. Lowercasebearer, extra spaces, query-string keys, basic auth, or cookies are all rejected401. Keys are product-scoped — a key minted for another product returns401 invalid_credential. - Null fields are omitted, not null. The
vehicleobject only contains keys with values;trim,doors,cylinders, andengineare frequently absent (vPIC commonly returns no trim or door count, especially for EVs). Never assume a fixed schema — treat every field as optional and check presence. origintells you the data path, not completeness."store"= matched a row in the scraped-listings store (field completeness varies; some fields come from the listing source or prior enrichment, not vPIC)."vpic"= decoded live against NHTSA for this request."store"does not guarantee every field was vPIC-enriched.- VIN character rules. Exactly 17 chars;
I,O,Qnever allowed. North American VINs (first char1–5) must pass their check digit or you get400 invalid_vin. The API upper-cases input and echoes the upper-cased VIN back — input case is irrelevant. - Billing settles only on 2xx. A failed decode costs nothing. Metered fees are prepaid from credits; when included calls are exhausted and credits can't cover a call you get
402 insufficient_credits(no overage bill). VIN Decode is one of the eleven Starter-plan included endpoints (1,000 calls/mo). 503s are retryable, other 4xx are not.retryable: trueappears on every503(decode_upstream_unavailablewhen the 10 s budget elapses or the service is unreachable;decode_upstream_errortypically when live NHTSA vPIC is down for a never-seen VIN) and on429. Retry those with backoff; do not retry400/401/402/403/404.engineis a derived string composed from vPIC displacement + configuration + cylinder count (e.g."3.5L V-Shaped 6cyl"), and is frequently absent onstore-origin rows.- No anti-bot on the marketing site.
vehicles.dev/vehicles.dev/docsreturned HTTP 200 with no bot protection; the successful path used no--verifiedand no--proxies. Stealth/residential proxies are unnecessary — but they're also irrelevant, because the barrier is API-key auth, not IP or fingerprint blocking.
Expected Output
Successful decode (200 · application/json):
{
"origin": "store",
"source": "vehicles.dev",
"vehicle": {
"year": 2022,
"make": "Toyota",
"model": "Highlander",
"trim": "Limited",
"body_style": "SUV",
"drivetrain": "AWD",
"fuel": "Gasoline",
"transmission": "Automatic",
"cylinders": 6,
"doors": 4
},
"vin": "5TDDZRBHXNS221317"
}
Sparse decode (live vPIC fallback, many optional fields omitted):
{
"origin": "vpic",
"source": "vehicles.dev",
"vehicle": {
"year": 2023,
"make": "Tesla",
"model": "Model 3"
},
"vin": "5YJ3E1EA7PF000000"
}
VIN could not be resolved (404 · application/problem+json):
{
"code": "vin_not_decodable",
"detail": "Neither our store nor NHTSA vPIC could resolve a make or model for that VIN.",
"instance": "/v1/vehicles/vin/00000000000000000",
"request_id": "1f9fcd64-c8b4-4d8f-98b1-3b7ed12cae38",
"retryable": false,
"status": 404,
"title": "Not Found",
"type": "https://api.data-platform.dev/problems/vin-not-decodable"
}
Bad VIN (400 · application/problem+json):
{
"code": "invalid_vin",
"detail": "The VIN failed validation.",
"instance": "/v1/vehicles/vin/1HGCM82633A00435",
"request_id": "accbacf9-ddbf-483b-8ee8-612f42d4593c",
"retryable": false,
"status": 400,
"title": "Bad Request",
"type": "https://api.data-platform.dev/problems/invalid-vin"
}
Missing/invalid credential (401 · application/problem+json) — the shape you get from any unauthenticated call, including from a browser (code is cookie_credentials_rejected when a Cookie header is present, authentication_required when no Authorization header is sent):
{
"code": "authentication_required",
"detail": "Bearer authentication is required.",
"instance": "/v1/vehicles/vin/1HGCM82633A004352",
"request_id": "accbacf9-ddbf-483b-8ee8-612f42d4593c",
"retryable": false,
"status": 401,
"title": "Unauthorized",
"type": "https://api.data-platform.dev/problems/authentication-required"
}