Identity & safety · VIN Decode

VIN Decoder API

A single GET call turns a 17-character VIN into canonical vehicle identity: year, make, model, trim, body style, drivetrain, fuel, transmission, cylinders, doors and a derived engine string. It is the call nearly every other workflow starts with, because the rest of the API keys off a resolved vehicle, and one of the lowest-priced in the catalog. Built for teams who need a VIN normalized into values they can group, filter and join on — not a raw decoder dump they have to clean up themselves.

GET/v1/vehicles/vin/{vin}10 s route budget

Get an API keyFull parameter reference

What comes back

The response is a small, flat envelope: origin (either "store" or "vpic"), a constant source provenance marker, the upper-cased vin that was actually decoded, and a vehicle object. Inside vehicle the possible keys are year, make, model, trim, body_style, drivetrain, fuel, transmission, cylinders, doors and engine — for example a 2022 Toyota Highlander Limited comes back as an SUV, AWD, Gasoline, Automatic, 6 cylinders, 4 doors. Two things matter for your client code. First, null fields are omitted entirely rather than emitted as null, so the key set varies per VIN and every field should be typed optional. Second, values are normalized to a controlled vocabulary — drivetrain resolves to AWD, FWD, RWD, 2WD or 4WD, and vPIC axle notation that does not map is dropped rather than passed through. origin tells you which path served the request, not whether every store field came from vPIC; log it if you care about latency distribution.

Request
curl -sS https://api.vehicles.dev/v1/vehicles/vin/5TDDZRBHXNS221317 \
  -H "Authorization: Bearer $VEHICLES_API_KEY" \
  -H "Accept: application/json"
200 · 2022 Toyota Highlander Limited
{
  "origin": "store",
  "source": "carscrape",
  "vehicle": {
    "year": 2022,
    "make": "Toyota",
    "model": "Highlander",
    "trim": "Limited",
    "body_style": "SUV",
    "drivetrain": "AWD",
    "fuel": "Gasoline",
    "transmission": "Automatic",
    "cylinders": 6,
    "doors": 4
  },
  "vin": "5TDDZRBHXNS221317"
}

The complete parameter table, the response-field dictionary and the per-endpoint error codes live in the reference: full parameter reference for /v1/vehicles/vin/{vin}.

What it is actually built on

The decode path is verified-store-first. We look the VIN up in our own scraped-listings Postgres store — 1,721,693 US vehicle listings covering 1,719,030 distinct vehicles — but return origin: "store" only after a clean vPIC result has verified the row's year, make and model. Optional store fields can still come from the listing source or prior enrichment, and completeness varies; origin identifies the serving path, not field-by-field provenance. When the identity is unverified or the VIN is unknown to us, we perform a live NHTSA vPIC DecodeVinValuesBatch call and return origin: "vpic". That two-tier design gives you store latency without trusting a scraped identity blindly, plus a live vPIC fallback behind the same path and auth header. On top of that we apply the normalization layer — controlled drivetrain values, a derived engine string composed from vPIC displacement, engine configuration and cylinder count (for example "3.5L V-Shaped 6cyl"), and omission of empty keys. The route runs against a 10-second budget; if the decode service is unreachable or that budget elapses you get a retryable 503 rather than a hung request.

Data source
Store-first. We look the VIN up in our scraped-listings Postgres store and return a matching row with origin: "store". Store fields can come from the listing source or prior enrichment, and completeness varies. When the VIN is unknown to us we fall back to a live NHTSA vPIC DecodeVinValuesBatch call and return origin: "vpic".
Route budget
10 s
Billed per
successful lookup, successful responses only
MCP tool
decode_vin

Limits: what this endpoint will not do

This is an identity decoder, not a history report. It returns nothing about title status, accidents, odometer rollback, liens, or previous owners, and it never returns personal data of any kind — if that is your requirement, this endpoint will not meet it. Coverage is US-market-oriented because the fallback is NHTSA vPIC. Field completeness varies: vPIC commonly returns no trim and no door count, especially for EVs, and the derived engine string is frequently absent on store-origin rows. Treat every key inside vehicle as optional, because nulls are omitted rather than returned. If you need raw manufacturer vocabulary instead of our normalized values — "TOYOTA", "4WD/4-Wheel Drive/4x4" — use the specifications endpoint; this one deliberately throws away unmapped source values to keep the vocabulary closed. On input, the public route requires exactly 17 letters or digits and rejects I, O and Q. The decoder also validates the check digit for North American VINs whose first character is 1–5; malformed input returns 400, while a structurally valid VIN that neither our store nor vPIC can resolve to a make or model returns 404 vin_not_decodable. Both 503 codes are marked retryable and are the ones worth wiring backoff for, since a live vPIC outage surfaces here for VINs we have never seen. The endpoint is available now and successful responses use the published catalog rate. Standard plans include no SLA.

What teams build with it

Dealer inventory and DMS enrichment at intake

Inventory feed vendors and DMS integrators receive VIN-only rows from rooftops and need consistent merchandising fields. Decode at intake to fill year/make/model/trim/body style, then chain the specifications endpoint when the merchandising template needs seats, horsepower or GVWR, and the photos endpoint for a hero image. Because decode is normalized, the resulting rows group cleanly by drivetrain and body style — which is what makes faceted search on the storefront work without a hand-maintained synonym table.

Insurance and warranty quote prefill

Insurtech and service-contract flows typically ask for a VIN and then need to stop asking questions. One decode call fills the vehicle section of the quote form, and the origin field lets you decide UX: "store" responses are served from our dataset with no external round trip, while "vpic" responses depend on a live NHTSA call and are slower. If a VIN cannot be resolved at all you get a 404 vin_not_decodable, which is your cue to fall back to manual year/make/model entry rather than blocking the funnel.

Marketplace and classifieds listing forms

Peer-to-peer marketplaces and consignment platforms can autofill a seller's listing from the VIN plate, then immediately call market value with the decoded make, model and year plus the seller's mileage to show an asking-price anchor before they publish. Decode first is deliberate: the valuation model is sensitive to how make and model are cased and spelled, and decoded output gives you Title-Case model names to pass straight through.

Fleet and lender portfolio normalization

Lenders, remarketers and fleet software hold VIN columns collected over years from inconsistent sources. Batch-decoding the portfolio produces one vocabulary for make, model and drivetrain, which is the prerequisite for joining against the depreciation endpoint (its curves are keyed by make and model) or bucketing exposure by body style. Log origin per row so you can see what fraction of your book already exists in our store versus what needed a live decode.

Integrating it properly

The reference ships the bare curl. This is the shape worth deploying: a typed client that branches on the RFC 9457 code slug rather than the human-readable title, and retries only the codes this endpoint marks retryable.

TypeScript
const API = "https://api.vehicles.dev";

// Only these codes are marked retryable for this endpoint. Everything else is terminal.
const RETRYABLE = new Set(["decode_upstream_unavailable", "decode_upstream_error"]);

export async function decodeVin(vin: string, attempt = 0): Promise<DecodeVinResponse> {
  const response = await fetch(
    `${API}/v1/vehicles/vin/${encodeURIComponent(vin)}`,
    {
      headers: {
        Accept: "application/json",
        Authorization: `Bearer ${process.env.VEHICLES_API_KEY ?? ""}`
      },
      // The route budget is 10 s; allow it plus headroom.
      signal: AbortSignal.timeout(15_000)
    }
  );

  if (response.ok) return (await response.json()) as DecodeVinResponse;

  // Failures are application/problem+json. Branch on `code`, never on the human-readable title.
  const problem = (await response.json()) as { code: string; detail?: string };
  if (RETRYABLE.has(problem.code) && attempt < 3) {
    await new Promise((resolve) => setTimeout(resolve, 250 * 2 ** attempt));
    return decodeVin(vin, attempt + 1);
  }
  throw new Error(`${response.status} ${problem.code}: ${problem.detail ?? ""}`);
}

How to check it is behaving

The route enforces a 10-second upstream budget, so a slow dependency returns a problem document instead of holding your request open. Exactly 2 codes are marked retryable here — decode_upstream_unavailable and decode_upstream_error — and everything else documented for this endpoint is terminal, so backoff on anything else just burns your rate limit. Billing is success-only: failed calls release their reservation, so you can measure error rates and hold-out accuracy against your own data without paying for the failures. Usage is visible per endpoint in the dashboard.

What this one endpoint costs

Priced per successful lookup. The platform fee buys throughput and endpoint access; the unit price covers the data work. Worked at 50,000 successful calls a month so you can see where the plans cross over — the full price matrix is in the reference.

VIN Decoder API pricing by plan
PlanPlatform feeRate limitPer successful lookup50,000 / month
Starter$0 / mo5 rps$0.004$200$200 usage + platform fee
Pro$299 / mo10 rps$0.0025$424$125 usage + platform fee
Scale$599 / mo50 rps$0.0015$674$75 usage + platform fee

Eligible for the Starter plan's included monthly calls; past those it draws on your credit balance. Data fees are prepaid from credits on every plan: when included calls and credits cannot cover a call you get 402 insufficient_credits rather than an overage bill. Published rates are active and apply only to successful responses. Standard plans include no SLA.

Questions engineers actually ask

NHTSA vPIC is free. Why pay for this?

If all you need is raw vPIC fields, you can absorb NHTSA's latency and availability, and you are happy maintaining your own normalization, then call vPIC directly — it costs nothing and we say so plainly. What you buy here is: cached responses for identities vPIC has already verified in our dataset, a normalized controlled vocabulary instead of raw axle notation, an explicit 10-second budget with retryable 503s instead of an open-ended upstream, and one auth surface shared with valuation, listings, recalls and photos.

Do I get charged when a VIN fails to decode?

No. VIN decode is billed success-only. A 400 invalid_vin, a 404 vin_not_decodable, and both 503 upstream codes release the billing reservation rather than settling it, so failures cost nothing. Rate-limit rejections (429) are also free. You are billed per successful lookup, and only per successful lookup.

What does 50,000 decodes a month actually cost?

On Pro, VIN decode is $0.0025 per successful lookup ($2.50 per 1,000), so 50,000 decodes is $125 on top of the $299 platform fee — $424 total. On Scale it is $0.0015 ($1.50 per 1,000), so the same volume is $75 plus the $599 fee, $674 total. Scale only wins on decode volume alone above 300,000 calls a month, so pick Scale for its 50 rps limit rather than for decode pricing at lower volume.

Can I use VIN decode on the free Starter plan?

Yes. VIN decode is one of the eleven synchronous vehicle data endpoints that share Starter's 1,000 included calls per month, and it is capped at 5 requests per second. Past the shared allowance the rate is $0.004 per lookup, funded from your one-time $10 signup credit — roughly 2,500 further decodes. Starter accrues no overage: when included calls and credits cannot cover a call you get 402 insufficient_credits rather than a bill.

Why did the same VIN return different fields on two different days?

Because the key set follows the data, not a fixed schema. Null values are omitted, so a VIN served from our store may carry trim while a live vPIC decode of a similar vehicle does not, and engine is often absent on store-origin rows. Check origin to see which path served you, and treat every field under vehicle as optional in your types.

Is it case-sensitive, and does it validate the check digit?

The VIN is case-insensitive — the API upper-cases it before the lookup and echoes the upper-cased form back in vin. The public route accepts exactly 17 letters or digits, with I, O and Q excluded. The backing decoder also verifies the check digit for North American VINs whose first character is 1–5. Invalid input returns 400 before billing settles.

Where to go next

Most integrations chain two or three of these. The ones that pair with vin decoder api most often:

The reference entry for this endpoint — VIN Decode in the API documentation — carries the parameter constraints, defaults, response fields and the full error table with retryable flags. Agents can reach the same endpoint as the decode_vin tool on the vehicles-dev-mcp MCP server, and the machine-readable contract is published at https://api.vehicles.dev/openapi.json.

Browse the rest of the catalog on the Vehicle data APIs hub.