Which recalls may apply to this vehicle? NHTSA and RappelConso

Open recall data keys on make, model and year — never on one VIN. One offline call returns candidate campaigns from NHTSA and RappelConso, pay per call.

By Matthias Begot · · Updated

An agent doing due diligence on a vehicle — vetting a used-car listing, auditing a fleet — needs to know which recall campaigns could concern it. That data is public, but it is scattered across national registers with different keys and formats. One offline call, GET /vehicle/recalls, consolidates the US NHTSA recall dataset and France’s RappelConso, looked up by make/model/year or directly by VIN, and returns candidate campaigns — each with a match block stating why it was retained. No account, no API key, one x402 settlement per answer.

The problem: public data, fragmented surfaces

Vehicle recalls are open data by design — regulators want them found. NHTSA publishes its defect and recall records as public-domain datasets; France’s DGCCRF publishes RappelConso on data.gouv.fr under the Licence Ouverte. But for an agent, “open” is not the same as “callable”: each register has its own schema, its own keys, its own refresh cadence, and none of them will onboard an autonomous agent onto a B2B account.

There is also a subtlety that trips up naive integrations: open recall data is keyed on make, model and year range — not on the individual VIN. A campaign says “Accords built 2001–2007”, not “this chassis is affected”. Any service that returns a per-VIN recall verdict from these sources is overselling them. The honest shape of the answer is a list of candidates.

The call: one question, two forms

GET /vehicle/recalls accepts one of two mutually exclusive forms:

  • By makemake, optionally narrowed by model and year:

    GET /vehicle/recalls?make=Honda&model=Accord&year=2003
  • By VIN — the VIN is decoded to a make and model year (the same decode as /vehicle/vin/decode), the search runs on that resolution, and the resolution is echoed back in data.vin so the agent sees exactly what was searched:

    GET /vehicle/recalls?vin=1HGCM82633A004352

The response lists campaigns sorted by publication date, each carrying its originating source (NHTSA, RappelConso), the campaign identifier, the defect summary, the remedy when the source states one, a link back to the source record — and a match block (make, optional model, optional year range) that states why the campaign was retained. The agent never has to guess how a result relates to its query.

The lookup is served from a local store built from these open datasets; the runtime makes no network call and holds no secret, which is why it answers in milliseconds and is cheap enough to run per listing or per fleet unit.

Candidates, not a verdict

The scope is stated in the response itself, and it is worth internalizing:

  • Every result is a candidate to confirm with the manufacturer. Matching is by make/model/year range because that is how the public sources are keyed. The endpoint answers “which campaigns may apply?” — never “this specific VIN is recalled”.
  • An empty list is a successful answer. “No known recall for this vehicle in the covered sources” is exactly the data being sold — a 200, billed, per the x402 golden rule. The 4xx range is reserved for requests the service cannot run at all: no search key, both forms at once, a malformed VIN or year, an unknown VIN manufacturer, or a make absent from every covered source — none of which are billed.
  • Coverage is announced, never implied. coverage.sources lists each expected source with its dated snapshot. The EU Safety Gate is on the roadmap but not yet ingested: it appears as missing: true with a reason, which alone forces coverage.complete: false. The response never pretends to a coverage it does not have.
  • Freshness is the oldest source served. provenance.freshness.as_of deliberately carries the stalest snapshot date among the served sources — the service never presents itself as fresher than its weakest input.

Where it sits in the x402 loop

The call follows the same loop as every Invoket endpoint — no signup precedes it:

  1. Discover the endpoint via the live catalog and call it; receive the 402 challenge.
  2. Pay — sign the chosen rail and replay the request.
  3. Read — iterate recalls[], keeping the match block and source next to each campaign.
  4. Branch — an empty list clears the vehicle in the covered sources; a non-empty list becomes a checklist of campaigns to confirm with the manufacturer before purchase or dispatch.

The Quickstart walks the full discover → 402 → pay → replay cycle with runnable snippets. Price and accepted rails are not pinned here — they are served live by the catalog; see the endpoint reference for the current figure.

Decode, then recalls, then the full report

For a single listing, the natural sequence starts with GET /vehicle/vin/decode — grounding the make and model year in the VIN itself — and follows with the recall lookup on that resolution. When the agent wants the whole picture in one settlement, GET /vehicle/report bundles the decode, the candidate recalls and the Crit’Air classification into a single composite response.

Used for what it is — a consolidated, honestly-scoped view of open recall data — /vehicle/recalls turns a scattered set of national registers into one question an agent can pay to ask. For the full field reference, coverage semantics and error codes, see the GET /vehicle/recalls documentation; for how agents discover and call Invoket endpoints, see For agents.