Send a VIN, get back every NHTSA safety recall campaign for that vehicle's year, make and model — with the component, a plain-language summary, the safety consequence, the manufacturer's remedy, the campaign number and the report date. The endpoint does the VIN-to-vehicle resolution for you, which is the part most teams get wrong when they wire NHTSA data themselves. Read the matching granularity section below before you build: campaigns are matched at year/make/model level, not per individual VIN.
The response returns the resolved vehicle alongside the campaigns: year, make, model, a count, source, the upper-cased vin, and a recalls array. Each entry carries six fields — component (NHTSA's taxonomy string, e.g. "STRUCTURE:BODY:BUMPERS" or "EQUIPMENT:OTHER:LABELS"), summary (the campaign text naming the affected vehicles and defect), consequence (the safety risk, e.g. a detached bumper cover becoming a road hazard), remedy (what the manufacturer or dealer will do, and that it is free of charge), campaign_number (e.g. "23V720000") and report_date. count always equals recalls.length. The year, make and model fields are not decoration: they tell you which vehicle the recall query actually ran against, which is how you audit a surprising result. Individual campaign fields can be null when NHTSA omits them, so treat all six as optional.
{
"count": 5,
"make": "Toyota",
"model": "Highlander",
"recalls": [
{
"component": "EQUIPMENT:OTHER:LABELS",
"summary": "Gulf States Toyota, Inc. (GST) is recalling certain 2022 4Runner, Tacoma, Highlander, and Highlander Hybrid vehicles. The load carrying capacity modification labels may not be permanent and can fade, becoming illegible.",
"consequence": "An illegible label may allow the vehicle to be overloaded, increasing the risk of a crash.",
"remedy": "GST will notify owners by mail, including a replacement load carrying capacity modification label for their vehicle and detailed replacement instructions, free of charge.",
"campaign_number": "22V310000",
"report_date": "06/05/2022"
},
{
"component": "STRUCTURE:BODY:BUMPERS",
"summary": "Toyota Motor Engineering & Manufacturing (Toyota) is recalling certain 2020-2023 Highlander & Highlander Hybrid vehicles. During normal vehicle operation, minor impact to the front lower bumper cover may result in the cover coming loose or detaching.",
"consequence": "A detached front bumper cover can become a road hazard, increasing the risk of a crash.",
"remedy": "Dealers will repair or replace the upper and lower front bumper covers as necessary, free of charge.",
"campaign_number": "23V720000",
"report_date": "26/10/2023"
}
],
"source": "carscrape",
"vin": "5TDDZRBHXNS221317",
"year": 2022
}
The endpoint runs in two stages. First it resolves the VIN to a year, make and model — using our store only when vPIC has verified that core identity, and falling back to a live vPIC decode for unverified or unknown rows. Then it queries NHTSA's recallsByVehicle API for that year/make/model and projects each campaign onto the six documented fields. Both stages are external dependencies wrapped in one 15-second route budget, so an unreachable upstream or an elapsed budget surfaces as 503 recalls_upstream_unavailable, and an unexpected upstream status — most often the NHTSA recalls API or the fallback vPIC decode being down — as 503 recalls_upstream_error. Both are retryable. What you are paying for is the resolution step and the operational wrapper, not the recall records themselves: NHTSA publishes those, and we say so plainly. If you already hold clean year/make/model triples and are willing to own the vocabulary matching, retries and timeouts, you can query NHTSA directly for free. The value here is that a VIN is all you need, the resolution reuses the same verified identity store that powers VIN decode, and the failure modes come back as typed, retryable problem documents on the same auth surface as the rest of the API.
Data source
Two stages. We resolve year/make/model store-first, falling back to a live vPIC decode when the VIN is unknown to us, then query the NHTSA recallsByVehicle API and project each campaign onto six fields.
Route budget
15 s
Billed per
successful lookup, successful responses only
MCP tool
get_recalls
Limits: what this endpoint will not do
The most important limit: recalls are matched at year/make/model level, not per VIN. A campaign may name sibling models or a narrower build range — real summaries read like "certain 2022 4Runner, Tacoma, Highlander, and Highlander Hybrid vehicles" — so a returned campaign does not prove this specific VIN is affected. Always surface the campaign_number and direct users to confirm with the manufacturer. Resolution also depends on our normalized make and model matching NHTSA's vocabulary, so an unusual or newly launched nameplate can resolve to a vehicle and still return nothing. report_date is passed through verbatim and its formatting is inconsistent across campaigns — both 06/05/2022 and 26/10/2023 appear in live data — so do not assume a single date format, and prefer sorting on campaign_number if you need deterministic ordering. Any of the six per-recall fields can be null. Scope is NHTSA safety recall campaigns only: no technical service bulletins, no consumer complaints, no open investigations, no crash-test ratings, and no per-VIN open/closed remedy status. The public route rejects anything other than exactly 17 VIN-safe letters or digits, and the backing validator also rejects a bad North American check digit. A well-formed VIN that cannot be resolved returns 404 recalls_not_resolvable. And count 0 with HTTP 200 is a successful, chargeable result — it means the vehicle resolved and NHTSA lists no campaigns for it. Recalls shares the free Starter plan's 1,000 monthly calls; after that shared allowance, successful lookups cost $0.01 from the one-time $10 signup credit — about 1,000 additional lookups before both sources are exhausted and the API returns 402 insufficient_credits. The endpoint is available now and successful responses use the published catalog rate. Standard plans include no SLA.
What teams build with it
Pre-sale safety checks for used-car retail
Independent dealers, online used-car retailers and consignment platforms increasingly want an open-recall check in the reconditioning workflow and a disclosure block on the vehicle detail page. One call per VIN at intake gives you the campaign list; store campaign_number per unit so your service department has the identifier to quote to the franchise dealer performing the free remedy. Because matching is at year/make/model level, present results as campaigns issued for this year, make and model, and point the buyer to the manufacturer for VIN-level confirmation.
Fleet and rental compliance dashboards
Fleet management and rental software can sweep a VIN list on a schedule and surface units whose year/make/model appears in a new campaign, ordered by component severity or report_date. The count field makes the sweep cheap to reason about: count 0 with HTTP 200 is a normal answer meaning the vehicle resolved and NHTSA lists nothing. Pair with VIN decode at onboarding so your fleet table already carries normalized make and model for grouping.
Service-department outreach and shop software
Repair-shop and service-scheduling products can turn a customer's VIN into a recall talking point at check-in: the summary and consequence strings are written to be read by a human, and remedy states that the work is free of charge, which is the single most common customer question. Because the API returns a campaign list rather than an open/closed status per VIN, frame it as campaigns that may apply, confirm with the manufacturer, in the customer-facing copy.
Vehicle report and diligence products
If you are assembling a buyer-facing report, recalls are the safety section: decode identity from the VIN decoder, price it with market value, and add campaigns here. Keeping this call explicit also lets your product preserve the campaign number, consequence, remedy, and report date without blending safety records into a valuation response.
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(["recalls_upstream_unavailable", "recalls_upstream_error"]);
export async function getRecalls(vin: string, attempt = 0): Promise<GetRecallsResponse> {
const response = await fetch(
`${API}/v1/vehicles/recalls/${encodeURIComponent(vin)}`,
{
headers: {
Accept: "application/json",
Authorization: `Bearer ${process.env.VEHICLES_API_KEY ?? ""}`
},
// The route budget is 15 s; allow it plus headroom.
signal: AbortSignal.timeout(20_000)
}
);
if (response.ok) return (await response.json()) as GetRecallsResponse;
// 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 getRecalls(vin, attempt + 1);
}
throw new Error(`${response.status} ${problem.code}: ${problem.detail ?? ""}`);
}
How to check it is behaving
The route enforces a 15-second upstream budget, so a slow dependency returns a problem document instead of holding your request open. Exactly 2 codes are marked retryable here — recalls_upstream_unavailable and recalls_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 25,000 successful calls a month so you can see where the plans cross over — the full price matrix is in the reference.
Vehicle Recall API pricing by plan
Plan
Platform fee
Rate limit
Per successful lookup
25,000 / month
Starter
$0 / mo
5 rps
$0.01
$250$250 usage + platform fee
Pro
$299 / mo
10 rps
$0.01
$549$250 usage + platform fee
Scale
$599 / mo
50 rps
$0.007
$774$175 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
If a campaign comes back, is this exact VIN affected?
Not necessarily. The lookup resolves your VIN to a year, make and model and then asks NHTSA which campaigns exist for that vehicle. Campaigns frequently cover a subset of production or a group of sibling models, and the summary text usually says so. Use the returned campaign_number as the handle for VIN-level confirmation with the manufacturer or a franchise dealer, and word your UI as "campaigns issued for this vehicle" rather than "your car is affected".
Am I billed for a lookup that returns zero recalls?
Yes. count 0 with HTTP 200 means the endpoint did its job — the VIN resolved and NHTSA lists no campaigns — so it is a successful, billable lookup. What is not billed: 404 recalls_not_resolvable, both 503 upstream codes, and 429 rate-limit rejections, all of which release the billing reservation.
How are invalid and unresolvable VINs different?
Malformed VINs return 400: the public route requires exactly 17 VIN-safe letters or digits, and the backing service also validates North American check digits. A VIN that passes those checks but cannot produce a year, make and model from either our store or vPIC returns 404 recalls_not_resolvable.
NHTSA publishes recall data for free. What am I paying for?
The VIN-to-vehicle resolution, the operational wrapper and the single integration surface. NHTSA's recalls API is keyed by year, make and model, so using it directly means you own VIN decoding, vocabulary matching against NHTSA's naming, retries and timeouts across two upstreams. If you already have clean year/make/model data and are happy owning that plumbing, call NHTSA directly — it is free and we would rather tell you than have you churn.
What does it cost, and why more than VIN decode?
Recalls is included in Starter's shared 1,000-call monthly allowance, then $0.01 per successful lookup from available credit. Pro is $0.01 per successful lookup ($10.00 per 1,000) and Scale is $0.007 ($7.00 per 1,000). 25,000 lookups on Pro is $250 plus the $299 platform fee. It prices above VIN decode because each call can fan out to two upstream services — resolution then the recalls query — rather than one store read.
How should I schedule recall sweeps over a large VIN list?
Rate limits are per plan — 10 requests per second on Pro, 50 on Scale — and 429s are free, so a token-bucket client with backoff is enough. Sweep on a cadence rather than continuously: campaigns are issued at NHTSA's pace, and results only change when a new campaign lands for a year/make/model you hold. Deduplicate your VIN list to distinct year/make/model triples first if you only need campaign-level awareness; that alone usually cuts the call volume by an order of magnitude.
Where to go next
Most integrations chain two or three of these. The ones that pair with vehicle recall api most often:
VIN Decoder APINormalized year, make, model, trim, body and powertrain from a VIN. Verified-store-first, with a live NHTSA vPIC fallback for everything else.
Vehicle Specs APIThe 20-field NHTSA vPIC factory sheet for a VIN — seats, horsepower, displacement, GVWR class, plant country — read live on every request and never cached.
Vehicle Valuation APIAn ML-predicted current asking price in whole USD for a year/make/model, with the model's 3.7% median holdout error reported on every response.