Decode a Canadian VIN by API
One VIN in, one identity card out — entity refs, VIN grains, and links, for 99.77% of Canadian VINs.
curl "https://api.cardog.app/v2/vin/2T3R1RFV7MW180266" \
-H "x-api-key: $CARDOG_API_KEY"One VIN in, one identity card out: the display names to render, the entity refs to store, the two VIN grains, and links to this vehicle's market instrument, recalls, and live listings. Two credits per VIN, batch or single.
The reason this is a separate product from the free American decoder is coverage. NHTSA's vPIC is built from US regulatory submissions, so a Canadian-market vehicle decodes to a make and a model and then stops — no trim, sometimes no engine, sometimes nothing at all for a model line that never went on sale in the United States. We decode 99.77% of Canadian VINs to trim level, and where a pattern isn't in any public registry we carry our own. Every fact comes back keyed to a ref, so the decode output joins directly against specs, listings, market quotes, and recalls without a name-matching step.
The response:
Example
10 keys
refsare the point. The display names are for your UI; the refs are for your database. Writemake:toyotainto the column, not "Toyota" — every other endpoint takes the ref, and it will still mean Toyota in five years.nullin any ref field means not derivable for this VIN — never "we don't know the ref."
What comes back
valid— the decoder's verdict, and only the decoder's.falsemeans no identity: every field below it is null and every ref is null. The API never fills an unresolved VIN from adjacent data.year,make,model,trim— display names. Render these; join on the refs.trimis the catalogue trim this VIN's own pattern resolves to — never a seller's listing text, which ships separately inobservedTrim, tagged and dated.refs— the join keys, one per domain. Every other endpoint takes them. The grammar they obey is the ref grammar.nanoandsquish— the two VIN grains, covered below.links— server-relative paths to this VIN's adjacent resources. Follow them instead of building URLs by hand.
The capture above is trimmed. The live card also carries provenance —
authorityTier for the identity facts, trimAuthorityTier on the trim —
and links grows as the decode resolves: per-ref entity links, plus
specs, quote, and safety once the model year is known. The full
spec sheet is one link away at links.specs; on the MCP surface, the
identify_vehicle tool skims it into a specHighlights block. The
complete response shape is in the VIN reference.
null means not-derivable, never unknown
A null in any identity field is a fact about the VIN, not a gap in our lookup: it means this VIN's pattern does not derive that fact. It is never "we don't know the ref", never a fuzzy match withheld, and it will not turn into a guess on retry. The refs also cohere as a set — a model ref only ships under the make ref beside it, and a pairing that cannot hold together goes null rather than travel as a plausible-looking join key. That is what makes a decode trustworthy in a pipeline: every non-null ref can be written straight into a column with no validation pass.
Batch
Same contract, up to 1,000 VINs per call:
curl -X POST "https://api.cardog.app/v2/vin/batch" \
-H "x-api-key: $CARDOG_API_KEY" \
-H "content-type: application/json" \
-d '{"vins":["2T3R1RFV7MW180266","1HGCM82633A123456"]}'The batch is all-accepted, per-item-resolved: one malformed VIN fails its
own row with the standard error envelope and never the batch. Results come
back in request order, with a meta block counting requested,
decoded, and failed. Metered per VIN — N VINs is N decode units.
The TypeScript SDK is the same surface, typed:
import { CardogClient } from "@cardog/api";
const client = new CardogClient({ apiKey: process.env.CARDOG_API_KEY });
const identity = await client.v2.vin.getByVin("2T3R1RFV7MW180266");
// Store the refs, render the names.
await db.vehicles.update(id, {
makeRef: identity.refs.make, // "make:toyota"
modelYearRef: identity.refs.modelYear, // "model-year:toyota/rav4/2021"
squish: identity.squish, // "squish:2T3R1RFVM"
});Why Canadian VINs are hard, specifically
vPIC is the decoder everyone starts with, and inside its boundary it is good: free, public, and correct for US-market vehicles, because it is built from the submissions US regulation compels. The boundary is the border. A trim configuration sold only in Canada has no US submission behind it, so the pattern is simply absent — the decode returns a make, usually a model, and stops. A model line that never went on sale in the United States can return nothing at all.
Our coverage comes from treating VIN patterns as a registry to maintain, not a dataset to download: where a Canadian pattern exists in a public registry we use it, and where it doesn't we derive and carry our own pattern extensions. That is what carries decode coverage to 99.77% of Canadian VINs at trim level.
VIN structure is the same on both sides of the border. For WMI, check digits, and year codes — the half of decoding that is arithmetic, not data — see Understanding VINs.
The grains: squish and nano
Two keys in the identity card are pure projections of the VIN itself:
squish— WMI + VDS + year, plant-agnostic. The market grain: two VINs with the same squish are the same configuration, which is why a squish is a valid instrument for market quotes.nano— squish plus the plant code. The build grain: the dedup and comparables key.
Because both are projections, they are derivable offline from any VIN with
@cardog/entities —
zero API calls, zero network:
import { squishFromVin, nanoFromVin } from "@cardog/entities";
const vin = "2T3R1RFV7MW180266";
squishFromVin(vin); // "2T3R1RFVM" — the market grain (WMI + VDS + year)
nanoFromVin(vin); // "2T3R1RFVMW" — the build grain (+ plant)The key you compute offline is the key the API returns — the identity card
ships it in ref form (squish:2T3R1RFVM), from the same grammar. You can
index a table on the squish grain before you ever call us.
From identity to everything else
The identity card is the entry point to the rest of the graph. Every card
carries a links block — rel → server-relative path — so the adjacent
resources are one traversal away:
instrument— the market instrument this VIN trades as, and from it the live quoterecalls— Transport Canada and NHTSA campaigns for this VIN; see the recall lookuplistings— live listings of this vehicle
Follow the links instead of building URLs by hand.
Errors
Every non-2xx response is one envelope. A malformed VIN:
Example
4 keyscode is machine-dispatchable, hint says what to do next, and
docs_url is fetchable markdown — the full correction recipe, no HTML
parsing. In a batch, the same envelope appears on the failing row's
error field while the rest of the batch decodes. The complete code list
is in Errors.
- Everything in one fetch: https://cardog.app/docs.md
- The contract: https://api.cardog.app/v2/openapi.json
- Get a key: https://cardog.app/account/api
- MCP:
claude mcp add --transport http cardog "https://mcp.cardog.io/mcp?api_key=$CARDOG_API_KEY"