GET /vehicle/recalls

Returns the recall campaigns that could concern a vehicle, consolidated from the public recall datasets we currently serve - the US NHTSA recall dataset and France’s RappelConso - and served from a local store, so a lookup resolves in milliseconds without an account or API key. The runtime performs no network call and holds no secret.

The EU Safety Gate (formerly RAPEX) is an expected source on the roadmap, not yet ingested: it is listed in coverage.sources as missing with a reason so the response never over-promises. Only sources actually loaded are named in provenance.source.

Look up either by make (optionally narrowed by model and year) or by VIN (the VIN is decoded to a make and model year, then searched, and the resolution is echoed back). Each returned campaign carries a match block that states why it was retained. See the live /catalog for the authoritative endpoint listing and price.

Candidate campaigns, not a VIN verdict. Open recall data keys on make / model / year range, not on the individual VIN, so this endpoint answers “which campaigns may apply to this vehicle?” - never “this specific VIN is recalled”. Treat every result as a candidate to confirm with the manufacturer, not a confirmed defect for the car in hand.

x402 golden rule: the agent pays for the answer to its question. A searchable, covered request is a successful answer -> 200, including an empty list - “no known recall in the covered sources” is itself the data being sold. Requests the service cannot answer - no search key, both forms at once, a malformed VIN or year, an unknown VIN manufacturer, or a make absent from every covered source - leave the 200 range and are not billed.

Parameters

Provide one of two mutually exclusive forms. Supplying vin together with any of make / model / year is a 400.

ParameterTypeFormDescription
makestringmakeRequired in this form. Manufacturer marque; resolved through the alias table before the search - see make resolution
modelstringmakeOptional. Narrows the search to a model, as far as the source metadata allows - see filter application
yearstringmakeOptional. A 4-digit model year (YYYY), applied as far as the source metadata allows - see filter application
vinstringvinA 17-character VIN; decoded to make and model year (see /vehicle/vin/decode), then searched. The resolution is echoed in data.vin
GET /vehicle/recalls?make=Honda&model=Accord&year=2003
GET /vehicle/recalls?vin=1HGCM82633A004352

Make resolution

make goes through an alias table before the search runs, so the usual short forms reach the right marque: VW finds Volkswagen, Mercedes finds Mercedes-Benz, Chevy finds Chevrolet, and Alfa and Land-Rover resolve the same way.

A corporate group is not a marque. Stellantis, FCA and PSA are refused with a 404 UNKNOWN_MAKE on purpose - no recall campaign is filed against a holding company, and quietly picking one of its brands would be a guess. Query the brand itself (Fiat, Jeep, Peugeot), or use the vin= form and let the VIN name the manufacturer.

Because the requested make is resolved rather than copied, data.make is the canonical marque, not an echo of what you typed - see below.

200 response - UnifiedResponse

{
  "data": { ... },
  "provenance": {
    "source": "nhtsa+rappelconso",
    "fetched_at": "2026-07-01T12:00:00Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-07-01" }
  }
}
  • provenance.source: a +-joined slug of the recall sources actually served - currently nhtsa+rappelconso. A source that is expected but not yet ingested (Safety Gate) is not named here; it appears in coverage.sources flagged missing.
  • freshness.kind: snapshot; as_of is the oldest source date among the sources served - the service never presents itself as fresher than its stalest input.

Fields of data

FieldTypeDescription
makestringThe canonical make searched, in the register’s own casing (HONDA, MERCEDES-BENZ) - not a copy of what you sent. In the VIN form it is the make as the decoder named it (Honda)
modelstringEcho of the requested model, verbatim; present only in the make form when supplied
yearnumberEffective model year - the one provided, or the one resolved from the VIN; omitted when unknown
vinobjectPresent only in the VIN form: how the VIN was decoded (see below)
recallsarrayCandidate campaigns, sorted by publication date descending; may be empty, and capped at 200
totalnumberHow many campaigns matched before the cap - the number to read for a count, not recalls.length
truncatedbooltrue when total exceeded 200 and only the 200 most recent campaigns were served
coverageobjectThe sources consulted, their freshness, and whether the search ran on all of them

total counts what the search matched, which is not the same as what a filter narrowed: read coverage.filters before treating it as a model- or year-specific figure.

vin (VIN form only)

FieldTypeDescription
vinstringEcho of the normalized VIN
wmistringResolved World Manufacturer Identifier
manufacturerstringGlobal manufacturer decoded from the WMI, as the WMI tables name it (AMERICAN HONDA MOTOR CO., INC.)
resolved_makestringThe marque that manufacturer maps to, and the one actually searched (Honda)
model_yearnumberModel year decoded from position 10; omitted when unresolved

recalls[]

FieldTypeDescription
sourcestringThe originating source of the served item (NHTSA, RappelConso)
campaign_idstringThe source’s campaign identifier
componentstringAffected component; omitted when the source does not state one
summarystringThe campaign’s defect summary
remedystringThe remedy; omitted when the source does not state one
published_atstringPublication date; omitted when unknown
urlstringLink to the source campaign; omitted when unavailable
matchobjectWhy this campaign was retained: make, optional model, optional years { from?, to? }

coverage

FieldTypeDescription
sourcesarrayEach expected source. A served source is { name, as_of, fresh }; an expected-but-not-yet-ingested source is { name, missing: true, reason }
completebooltrue only when every expected source was loaded and fresh - false while any source is stale or missing
reasonstringPresent when complete: false, naming the stale or missing sources
filtersobjectHow far the requested model / year filters could actually be applied; omitted when neither filter is requested (see below)

A source entry carries missing: true and a reason when the source is on the roadmap but not yet ingested (currently the EU Safety Gate). Because an expected source is missing, complete is false and reason names it. A field a served source does not provide is omitted, never guessed. A stale served source still serves its last good snapshot, flagged fresh: false - it is never silently dropped and never turned into a 5xx.

Filter application

model and year are applied at the best of each source’s metadata, and the response says how far that went.

  • On NHTSA, model and model-year ranges are structured fields: both filters are exact.
  • Many RappelConso campaigns carry neither an exploitable model nor a year range - they are published at brand level. Those campaigns stay candidates for the make rather than being discarded, because dropping them would throw away real recalls that the source simply did not qualify.

coverage.filters reports this per filter, and is omitted entirely when neither model nor year is requested:

FieldTypeDescription
appliedstring"full" when every matched campaign could be judged by this filter, "partial" when some could not
unfilterablenumberHow many of the matched campaigns carry no usable value for this filter in the source
reasonstringPlain-language explanation; present only when applied is "partial"

The year entry also appears in the VIN form, where the year being applied is the one decoded from the VIN rather than one you passed.

Read coverage.filters before treating total as a filtered count. Because brand-level campaigns are kept, a model that does not exist can return the same total as a real one - but the response now says so: applied: "partial" with unfilterable equal to total means the model filter discriminated nothing at all, and every campaign you got back was retained on the make alone. An agent that ignores this block will read a brand-wide list as a model-specific one.

Example - make + model + year with campaigns

{
  "data": {
    "make": "HONDA",
    "model": "Accord",
    "year": 2003,
    "total": 17,
    "truncated": false,
    "recalls": [
      {
        "source": "NHTSA",
        "campaign_id": "15V320000",
        "component": "Air Bags",
        "summary": "Passenger frontal air bag inflator may rupture on deployment.",
        "remedy": "Dealer will replace the inflator, free of charge.",
        "published_at": "2015-05-27",
        "url": "https://www.nhtsa.gov/recalls?nhtsaId=15V320000",
        "match": { "make": "HONDA", "model": "ACCORD", "years": { "from": 2001, "to": 2007 } }
      },
      {
        "source": "NHTSA",
        "campaign_id": "10V364000",
        "summary": "Interior power window master switch may fail and overheat.",
        "published_at": "2010-08-05",
        "match": { "make": "HONDA", "model": "ACCORD", "years": { "from": 2003, "to": 2004 } }
      }
    ],
    "coverage": {
      "sources": [
        { "name": "NHTSA", "as_of": "2026-07-01", "fresh": true },
        { "name": "RappelConso", "as_of": "2026-07-01", "fresh": true },
        { "name": "Safety Gate", "missing": true, "reason": "Safety Gate source is not currently ingested" }
      ],
      "complete": false,
      "reason": "Safety Gate source is not currently ingested",
      "filters": {
        "model": {
          "applied": "partial",
          "unfilterable": 6,
          "reason": "6 of 17 matched campaigns carry no model in the source: they match at brand level and the 'model' filter did not discriminate them"
        },
        "year": {
          "applied": "partial",
          "unfilterable": 6,
          "reason": "6 of 17 matched campaigns carry no model year range in the source: they match at brand level and the 'year' filter did not discriminate them"
        }
      }
    }
  },
  "provenance": {
    "source": "nhtsa+rappelconso",
    "fetched_at": "2026-07-01T12:00:00Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-07-01" }
  }
}

Eleven of those seventeen campaigns were judged on model and year; the six that carry neither in the source are kept as brand-level candidates and counted in unfilterable.

Example - a filter that only applied partially

Renault is covered almost entirely by RappelConso campaigns published at brand level, so asking for a model narrows nothing: every matched campaign is unfilterable, and the response says so instead of implying a model-specific result. The match blocks agree - they carry the make alone.

{
  "data": {
    "make": "RENAULT",
    "model": "Clio",
    "year": 2021,
    "total": 41,
    "truncated": false,
    "recalls": [
      {
        "source": "RappelConso",
        "campaign_id": "sr/03092/25",
        "summary": "Rear barn door protrudes when the doors are open, with a head injury risk during loading.",
        "published_at": "2026-02-04",
        "url": "https://rappel.conso.gouv.fr/fiche-rappel/47747/rapex",
        "match": { "make": "RENAULT" }
      }
    ],
    "coverage": {
      "sources": [
        { "name": "NHTSA", "as_of": "2026-07-01", "fresh": true },
        { "name": "RappelConso", "as_of": "2026-07-01", "fresh": true },
        { "name": "Safety Gate", "missing": true, "reason": "Safety Gate source is not currently ingested" }
      ],
      "complete": false,
      "reason": "Safety Gate source is not currently ingested",
      "filters": {
        "model": {
          "applied": "partial",
          "unfilterable": 41,
          "reason": "41 of 41 matched campaigns carry no model in the source: they match at brand level and the 'model' filter did not discriminate them"
        },
        "year": {
          "applied": "partial",
          "unfilterable": 41,
          "reason": "41 of 41 matched campaigns carry no model year range in the source: they match at brand level and the 'year' filter did not discriminate them"
        }
      }
    }
  },
  "provenance": {
    "source": "nhtsa+rappelconso",
    "fetched_at": "2026-07-01T12:00:00Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-07-01" }
  }
}

Against a make whose campaigns are fully qualified in the source - a US-only manufacturer carried by NHTSA, say - the same request returns the clean form, with no reason to give:

"filters": {
  "model": { "applied": "full", "unfilterable": 0 },
  "year":  { "applied": "full", "unfilterable": 0 }
}

Example - VIN form, resolution echoed

The VIN is decoded to a make and model year, those drive the search, and the resolution is returned in data.vin for transparency. The model is not used to filter (a decoded model is not reliable enough), so every campaign for the make and year is returned. The decoded year is a filter like any other, so coverage.filters.year is present and reports how far it applied - there is no model entry, since no model filter was run. In this form data.make is the marque as the VIN decoder named it, repeated in data.vin.resolved_make, next to the raw manufacturer string the WMI tables carry.

{
  "data": {
    "make": "Honda",
    "year": 2003,
    "vin": {
      "vin": "1HGCM82633A004352",
      "wmi": "1HG",
      "manufacturer": "AMERICAN HONDA MOTOR CO., INC.",
      "resolved_make": "Honda",
      "model_year": 2003
    },
    "total": 39,
    "truncated": false,
    "recalls": [ ... ],
    "coverage": {
      "sources": [
        { "name": "NHTSA", "as_of": "2026-07-01", "fresh": true },
        { "name": "RappelConso", "as_of": "2026-07-01", "fresh": true },
        { "name": "Safety Gate", "missing": true, "reason": "Safety Gate source is not currently ingested" }
      ],
      "complete": false,
      "reason": "Safety Gate source is not currently ingested",
      "filters": {
        "year": {
          "applied": "partial",
          "unfilterable": 18,
          "reason": "18 of 39 matched campaigns carry no model year range in the source: they match at brand level and the 'year' filter did not discriminate them"
        }
      }
    }
  },
  "provenance": {
    "source": "nhtsa+rappelconso",
    "fetched_at": "2026-07-01T12:00:00Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-07-01" }
  }
}

Example - a known make with no campaigns is still a 200

An empty list is the answer, not an error: the make is covered, and there is simply nothing known for it in the sources.

{
  "data": {
    "make": "HONDA",
    "year": 1999,
    "total": 0,
    "truncated": false,
    "recalls": [],
    "coverage": {
      "sources": [
        { "name": "NHTSA", "as_of": "2026-07-01", "fresh": true },
        { "name": "RappelConso", "as_of": "2026-07-01", "fresh": true },
        { "name": "Safety Gate", "missing": true, "reason": "Safety Gate source is not currently ingested" }
      ],
      "complete": false,
      "reason": "Safety Gate source is not currently ingested",
      "filters": {
        "year": { "applied": "full", "unfilterable": 0 }
      }
    }
  },
  "provenance": {
    "source": "nhtsa+rappelconso",
    "fetched_at": "2026-07-01T12:00:00Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-07-01" }
  }
}

Coverage honesty

The service answers from local snapshots of the sources it currently serves - NHTSA and RappelConso. It does not query them at request time. coverage.sources lists each served source with its dated as_of and a fresh flag; if any served source is stale, complete is false and reason names it while its last good snapshot is still served. provenance.freshness.as_of is deliberately the oldest served source date.

The EU Safety Gate is an expected source that is not yet ingested: it is carried in coverage.sources as { "missing": true, "reason": … }, which alone makes complete false. It is never named in provenance.source and never contributes a served campaign until it is actually loaded.

The same honesty applies to the filters you asked for. Rather than silently dropping campaigns the source never qualified by model or year, the service keeps them as brand-level candidates and declares the shortfall in coverage.filters. A narrowed request therefore returns at least the campaigns that a broader one would - and tells you how much of the narrowing actually happened.

Scope is bounded to these open datasets: there is no lookup by licence plate, no confirmation that an individual VIN is affected, and no per-country coverage beyond the sources listed. Matching is by make / model / year range, which is why results are framed as candidate campaigns to confirm with the manufacturer.

Errors

Only requests the service cannot answer leave the 200 range. A missing or malformed search key, an unknown VIN manufacturer, and a make absent from every covered source are not billed.

StatuscodeCase
400INVALID_INPUTNeither make nor vin was given; both forms were combined; or year is not a valid YYYY
400INVALID_VINvin is not 17 characters, or contains a character outside the VIN alphabet
404UNKNOWN_WMIThe VIN is well formed but its manufacturer identifier is absent from the tables
404UNKNOWN_MAKEThe make - after alias resolution - is not present in any covered recall source, or is a corporate group rather than a marque; the search cannot be run
404NOT_FOUNDUnknown route
500INTERNALInternal error (detail logged, not exposed)
{ "error": "provide either 'make' (optionally with 'model'/'year') or 'vin'", "code": "INVALID_INPUT" }
{ "error": "'vin' cannot be combined with 'make', 'model' or 'year'; choose one form", "code": "INVALID_INPUT" }

The UNKNOWN_MAKE message echoes the make as normalized, and points at the two ways out - a marque instead of a group, or the VIN form:

{ "error": "make 'LADA' is not present in any covered recall source; use a brand name rather than a corporate group acronym (e.g. 'Fiat' or 'Jeep', not 'FCA'/'Stellantis'), or query by 'vin='", "code": "UNKNOWN_MAKE" }

The same 404 is what make=Stellantis gets: the group is refused deliberately, not missing by accident.

Attribution

Recall data is consolidated from the public sources we currently serve, each under its own terms:

  • NHTSA vehicle recalls, published by the US National Highway Traffic Safety Administration in the public domain.
  • RappelConso, the French government recall service, under the Licence Ouverte / Etalab.

The EU Safety Gate (formerly RAPEX), published by the European Commission, is an expected source on the roadmap and is not yet served; its attribution will be added once it is ingested.

See also

  • GET /vehicle/vin/decode - decode a VIN into manufacturer, model year and deterministic attributes.
  • GET /vehicle/report - a composite report for one VIN (decode + recalls + Crit’Air) in a single settlement.
  • For agents - discovery surfaces, the live /catalog and how settlement works.