Transport Canada recall lookup by VIN, by API
Per-VIN recall checks with citable authority — Transport Canada + NHTSA fused — and entity-scoped sweeps for fleets.
curl "https://api.cardog.app/v2/recalls/vin/2T3R1RFV7MW180266" \
-H "x-api-key: $CARDOG_API_KEY"Example
7 keysA VIN in, the campaigns affecting that vehicle out — Transport Canada and NHTSA fused into one answer, each campaign carrying its issuing authority and official campaign number so the result is citable, not just displayable. 5 credits per VIN checked.
The VIN is bridged to its model-year node in the graph, and the campaigns keyed to that node come back — Decode a Canadian VIN by API covers the identity the check bridges through.
What SOR/2024-274 requires
SOR/2024-274 amended Canada's Motor Vehicle Safety Regulations: s.15.05 obliges vehicle manufacturers to operate a public, VIN-searchable recall lookup, so that anyone holding a VIN can learn whether that vehicle has outstanding recalls. If you build for dealers, insurers, lenders, fleets, or a registry, that per-VIN check is the one your surface has to get right — and getting it right means getting the negative answer right, which is the part most integrations break on.
resolved: false is not "no recalls"
The check has two distinct empty states, and conflating them is the classic compliance bug:
Example
7 keysresolved: true, total: 0— the VIN is bridged to the graph and the corpus holds no campaigns for it. That is a clean answer you can act on.resolved: false— the VIN is not bridged yet.total: 0here is vacuous: the platform is telling you it cannot support a recall claim for this VIN, not that the vehicle is clear.
Branch on resolved before you branch on total. A compliance surface
that renders resolved: false as "No recalls ✓" is wrong in exactly the
way s.15.05 exists to prevent — this is the same null-semantics law that
runs through the whole API: absence of evidence is stated, never dressed up
as evidence of absence.
Both authorities, kept distinct
total: 2 on the RAV4 above, second record elided: the same occupant
classification defect exists as both Transport Canada campaign 2023684
and NHTSA campaign 23V865000. The corpus fuses the authorities into one
answer but keeps their records distinct — each stays citable against its
own issuer.
Citing a result
Every campaign is attributable, because an uncited recall answer is worth nothing in a dispute:
authority/authorityLabel— who issued the campaign ("tc"/ Transport Canada,"nhtsa"/ NHTSA — the set is open).campaignNumber— the authority's own identifier, resolvable in their systems.asOf— corpus freshness at the moment of the check. Persist it with the check: "no recalls as of {asOf}, per {authority}" is a defensible record; "no recalls" is not.
Sweeping at entity scope
Fleets and portfolios do not check VIN-by-VIN first — they watch the entities they hold. Any make, model, or model-year ref sweeps its campaigns:
# Everything affecting a model year you hold in volume
curl "https://api.cardog.app/v2/recalls/entity/model-year:toyota%2Frav4%2F2021" \
-H "x-api-key: $CARDOG_API_KEY"
# The whole make, for portfolio triage
curl "https://api.cardog.app/v2/recalls/entity/make:toyota" \
-H "x-api-key: $CARDOG_API_KEY"The pattern that scales: sweep the entity refs you hold on a schedule, and
when a new campaign lands, run the per-VIN check across the affected slice
of your fleet. GET /v2/recalls/feed (newest campaigns first) and
GET /v2/recalls/stats (corpus counters and latest recall date) drive the
watch loop. The full route surface for the group is in the
recalls reference.
The affects grain
A campaign's scope is campaign × model-year: each entry in affects
binds one model-year ref with its unitsAffected where the authority
published one. Two consequences worth knowing. First, affects is scoped
to how you asked — an entity query lists only the years in your scope, the
campaign detail (GET /v2/recalls/{recall-ref}) lists them all. Second,
the grain is the authority's, not the vehicle's: a campaign names model
years, and the per-VIN check is that grain bridged through the VIN's
identity. Where a manufacturer scopes below the model year (a VIN range, a
plant window), the authoritative per-VIN answer is the manufacturer's own
s.15.05 lookup — the campaign record here tells you exactly which one to
ask, and cite.
Pricing
A VIN check is 5 credits. Non-VIN registry reads in the group — entity sweeps, the feed, stats, campaign detail — are 1 credit each. See Credits & limits.
What is not here
Recall completion status is not in this API. Whether a specific vehicle has had a specific campaign performed lives with the manufacturer, and we do not redistribute it. If your product needs "was this fixed," you need an OEM relationship, not a data vendor.
If you need one lookup, use Transport Canada's public recall database. It is free and it is the primary source. What we add is VIN-level matching, NHTSA fusion, entity-scoped sweeps, and an API you can put behind a product. If you're doing this once by hand, see How to look up vehicle recalls by VIN.
Push notification is not built. Recall watches — tell me when a
campaign lands on a VIN I care about — are on the roadmap, not in the API.
Today you poll GET /v2/recalls/feed.
- 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"