GET /vehicle/lookup

Turns the designation an agent holds - “Renault Clio V”, “VW Golf 2020”, a make and a commercial name copied from a classified ad - into the make / model / year key that /vehicle/recalls and /vehicle/co2 require, spelled the way the referential spells it. Those two routes do not need a VIN, but they do need the referential’s own spelling: without it, “Renault Clio V” returns an empty recall list that looks exactly like “no recall” - the most expensive silent failure of the vehicle family. Answers come from local stores already ingested, with no network call and no secret at request time.

Start here when the agent holds a name. If it holds a VIN, skip this route: /vehicle/vin/decode, /vehicle/report and /vehicle/prepurchase take the VIN directly. See the live /catalog for the authoritative listing and price.

ambiguous is the normal answer here

Read this before the examples: on this route, ambiguous is the nominal case, not a degraded one. A model spans several model years, and the European aggregates index each year as its own key - so “Skoda Octavia” is six candidates, “Alpine A110” five, and an exact usually needs a year in the text. That is the opposite of /company/lookup, where most names resolve to a single company; each page tells the truth of its own domain.

verdictMeaningcandidates
exactOne candidate answersexactly one element
ambiguousSeveral do - all served, up to 25, with total exact. The usual casetwo or more elements
noneThe referentials publish nothing for this text, at the snapshot date[]

candidates is always present - empty on none, a one-element list on exact. The service never picks a candidate for you: the list is the answer, and each candidate carries its model years and the endpoints that accept it, which is what lets you choose.

none is a paid answer

A none is served as a 200 and billed - never a 404. “No referential publishes this designation” is exactly what stops an agent from inventing a make and a model and paying downstream calls that cannot succeed. Only a malformed request - q missing, empty or longer than 200 characters - leaves the 200 range, and it is not billed.

What a resolution does not attest

Every answer carries the same fixed sentence in data.limits, served verbatim:

A resolution names what the referential publishes for the text you sent, as of the date carried by the provenance; it does not certify that the entity resolved is the one you had in mind. Candidates come from the recall campaigns and the European registration aggregates already ingested: a make/model absent from both is not a statement about the vehicle, only about these referentials. Generation designations (‘V’, ‘5’, ‘Mk5’) are not interpreted, as no referential publishes them; a four-digit number in the text is read as a model year. A candidate without a year is accepted by /vehicle/recalls, where the year is optional, but not by /vehicle/co2, which requires one.

Three consequences, each worth a line.

Generations are not interpreted. “Renault Clio V” is exactly what an agent will write, and no referential publishes generations - only model years. So “Clio V”, “Clio 5” and “Clio” resolve to the same Clio keys, and the RENAULT CLIO 2018 candidate an agent gets for “Clio V” is a fourth-generation car. If the generation matters, the year is the only lever the referentials offer.

A short model name that appears in your text is a candidate. A model is retained when its published label appears, as a whole, in the text - extra words (trim, engine, generation) do not prevent the match. Without a make, that cuts both ways: Clio V alone returns 12 candidates, including the Mercedes-Benz V (the V-Class) and the Orion V bus, because V is a published model name. Name the make in the text or pass the make filter: “Renault Clio V” returns Renault keys only.

A none is a statement about these referentials, not about the vehicle. The Microlino is a real vehicle; it is a quadricycle, outside the passenger-car registration aggregates and absent from the recall campaigns - so it comes back none. That is the answer, not a verdict on the car.

The key: three natures, one of them partial

Each candidate’s id is the key downstream accepts as is - copy it into the next call without reworking it. It is composite here, because that is what the vehicle referentials index, and id_kind names how much of it is there:

id_kindidAccepted by
make_model_year{ make, model, year }/vehicle/co2 and /vehicle/recalls
make_model{ make, model }/vehicle/recalls only - no year
make{ make }/vehicle/recalls only - the make-only fallback

A candidate without a year is accepted by /vehicle/recalls but not by /vehicle/co2, which needs a model year to find a registration aggregate. Each candidate says so itself in accepted_by, which is computed against the store, never deduced from a convention. Read it before paying for a co2 call that cannot succeed.

The make-only fallback is a partial match

When the text names a known make but no model any referential publishes, the answer is a single id_kind: "make" candidate - “Koenigsegg Jesko” returns { "make": "KOENIGSEGG" }, because no ingested referential publishes a Jesko. Because it is the only candidate, the verdict is exact: the verdict counts candidates, it does not grade them. What tells you the match is partial is the candidate itself - id_kind: "make" and accepted_by without /vehicle/co2. Read id_kind before the verdict. A make-wide recall search is still a useful next call; a CO2 lookup is not.

A make that no referential knows does not fall back to anything: it is a none.

Parameters

ParameterTypeRequiredDescription
qstringyesThe designation as you hold it - make, model, commercial name, with or without a model year. Up to 200 characters
makestringnoNarrows to one make, in any spelling the curated aliases know (vw, volkswagen, mercedes-benz)
GET /vehicle/lookup?q=golf&make=vw
  • The make filter is firm. It only reduces the candidate set, never changes the shape of the answer - and it narrows even to nothing: q=golf&make=zorglub is a none, not a wider list.
  • A year in the text is an indication, not a filter. A four-digit number in q is read as a model year: it narrows when it matches (Renault Clio 2021 is exact), and it steps aside when it would leave nothing - Clio 2015 does not come back empty on a referential that publishes the Clio, it returns the year-less RENAULT CLIO key.
  • No pagination. The list is capped at 25; total counts every candidate before the cap and truncated says whether the list was cut. A second page would be a second paid call for the same question - narrow with make or a year instead. A truncated list is never exact.

200 response - UnifiedResponse

{
  "data": { ... },
  "provenance": {
    "source": "nhtsa+rappelconso+eea-co2-monitoring",
    "fetched_at": "2026-10-15T15:32:03.517946196Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-07-28T00:00:00Z" }
  }
}
  • provenance.source: the referentials queried - NHTSA recall campaigns, RappelConso, and the EEA CO2 monitoring aggregates.
  • freshness.as_of: the oldest snapshot among them. A none is only worth something if it is dated.

Fields of data

FieldTypeDescription
querystringThe text you sent, echoed verbatim
verdictstringexact, ambiguous or none
candidatesobject[]Always served. Ordered by make, then model, then model year descending - a stable reading order, never a relevance ranking
totalnumberCandidates found before the 25-item cap; the verdict is derived from it
truncatedboolWhether the served list is cut at the cap
limitsstringThe fixed sentence quoted above

Each candidate:

FieldTypeDescription
idobject{ make, model?, year? } - the indexed values, to pass downstream as is
id_kindstringmake_model_year, make_model or make
labelstringThe designation as the source publishes it - never recased, never re-accented, never completed
yearsobject{ from, to } - the model years attached to this make/model in the referentials. Omitted when none is published
sourcesstring[]Which referentials publish this candidate: nhtsa, rappelconso, eea-co2-monitoring
accepted_bystring[]Which endpoints accept this key, computed against the store

There is no similarity score anywhere in the response, and the order is mechanical: a rank would read as a confidence the service does not have.

Labels are served as published, quirks included. The European aggregates are reported by member states, and some rows carry the make again in the commercial name (RENAULT CLIO as a model, next to CLIO) or a misspelled make (VOLKSAWGEN, VW). They are separate keys in the referential, so they are separate candidates here - never cleaned up, because a cleaned key would no longer be accepted downstream.

Example - exact

GET /vehicle/lookup?q=Renault%20Clio%202021:

{
  "data": {
    "query": "Renault Clio 2021",
    "verdict": "exact",
    "candidates": [
      {
        "id": { "make": "RENAULT", "model": "CLIO", "year": 2021 },
        "id_kind": "make_model_year",
        "label": "RENAULT CLIO 2021",
        "years": { "from": 2021, "to": 2021 },
        "sources": ["eea-co2-monitoring", "rappelconso"],
        "accepted_by": ["/vehicle/co2", "/vehicle/recalls"]
      }
    ],
    "total": 1,
    "truncated": false,
    "limits": "A resolution names what the referential publishes for the text you sent, as of the date carried by the provenance; it does not certify that the entity resolved is the one you had in mind. Candidates come from the recall campaigns and the European registration aggregates already ingested: a make/model absent from both is not a statement about the vehicle, only about these referentials. Generation designations ('V', '5', 'Mk5') are not interpreted, as no referential publishes them; a four-digit number in the text is read as a model year. A candidate without a year is accepted by /vehicle/recalls, where the year is optional, but not by /vehicle/co2, which requires one."
  },
  "provenance": {
    "source": "nhtsa+rappelconso+eea-co2-monitoring",
    "fetched_at": "2026-10-15T15:32:03.517946196Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-07-28T00:00:00Z" }
  }
}

The id goes straight into /vehicle/co2?make=RENAULT&model=CLIO&year=2021 or /vehicle/recalls?make=RENAULT&model=CLIO&year=2021.

Example - ambiguous, the usual case

GET /vehicle/lookup?q=Renault%20Clio%20V - the form an agent actually writes (limits and provenance as above):

{
  "data": {
    "query": "Renault Clio V",
    "verdict": "ambiguous",
    "candidates": [
      { "id": { "make": "RENAULT", "model": "CLIO", "year": 2022 }, "id_kind": "make_model_year", "label": "RENAULT CLIO 2022", "years": { "from": 2018, "to": 2022 }, "sources": ["eea-co2-monitoring", "rappelconso"], "accepted_by": ["/vehicle/co2", "/vehicle/recalls"] },
      { "id": { "make": "RENAULT", "model": "CLIO", "year": 2021 }, "id_kind": "make_model_year", "label": "RENAULT CLIO 2021", "years": { "from": 2018, "to": 2022 }, "sources": ["eea-co2-monitoring", "rappelconso"], "accepted_by": ["/vehicle/co2", "/vehicle/recalls"] },
      { "id": { "make": "RENAULT", "model": "CLIO", "year": 2020 }, "id_kind": "make_model_year", "label": "RENAULT CLIO 2020", "years": { "from": 2018, "to": 2022 }, "sources": ["eea-co2-monitoring", "rappelconso"], "accepted_by": ["/vehicle/co2", "/vehicle/recalls"] },
      { "id": { "make": "RENAULT", "model": "CLIO", "year": 2019 }, "id_kind": "make_model_year", "label": "RENAULT CLIO 2019", "years": { "from": 2018, "to": 2022 }, "sources": ["eea-co2-monitoring", "rappelconso"], "accepted_by": ["/vehicle/co2", "/vehicle/recalls"] },
      { "id": { "make": "RENAULT", "model": "CLIO", "year": 2018 }, "id_kind": "make_model_year", "label": "RENAULT CLIO 2018", "years": { "from": 2018, "to": 2022 }, "sources": ["eea-co2-monitoring", "rappelconso"], "accepted_by": ["/vehicle/co2", "/vehicle/recalls"] },
      { "id": { "make": "RENAULT", "model": "CLIO V" }, "id_kind": "make_model", "label": "RENAULT CLIO V", "sources": ["rappelconso"], "accepted_by": ["/vehicle/recalls"] },
      { "id": { "make": "RENAULT", "model": "RENAULT", "year": 2019 }, "id_kind": "make_model_year", "label": "RENAULT RENAULT 2019", "years": { "from": 2019, "to": 2019 }, "sources": ["eea-co2-monitoring"], "accepted_by": ["/vehicle/co2", "/vehicle/recalls"] },
      { "id": { "make": "RENAULT", "model": "RENAULT CLIO", "year": 2019 }, "id_kind": "make_model_year", "label": "RENAULT RENAULT CLIO 2019", "years": { "from": 2019, "to": 2019 }, "sources": ["eea-co2-monitoring"], "accepted_by": ["/vehicle/co2", "/vehicle/recalls"] }
    ],
    "total": 8,
    "truncated": false,
    "limits": "A resolution names what the referential publishes for the text you sent, …"
  },
  "provenance": { "…": "…" }
}

Read it line by line, because every line teaches something:

  • the five CLIO keys are the nominal ambiguity - one per model year the aggregates publish. Adding the year to the text (Renault Clio 2021) makes it exact;
  • the V was not read as a generation: RENAULT CLIO 2018 is a fourth-generation Clio, and it is served because CLIO appears in the text;
  • CLIO V is a separate key because RappelConso publishes that literal label - it has no year, so it is accepted by /vehicle/recalls and not by /vehicle/co2, and years is omitted;
  • the last two are aggregate rows where the member state reported the make inside the commercial name. They are real keys of the registry, served as published.

Example - make-only fallback

GET /vehicle/lookup?q=Koenigsegg%20Jesko (limits and provenance as above):

{
  "data": {
    "query": "Koenigsegg Jesko",
    "verdict": "exact",
    "candidates": [
      {
        "id": { "make": "KOENIGSEGG" },
        "id_kind": "make",
        "label": "KOENIGSEGG",
        "sources": ["eea-co2-monitoring", "nhtsa"],
        "accepted_by": ["/vehicle/recalls"]
      }
    ],
    "total": 1,
    "truncated": false,
    "limits": "A resolution names what the referential publishes for the text you sent, …"
  },
  "provenance": { "…": "…" }
}

exact - one candidate - but partial: the make is known, the Jesko is not published, and the candidate says so twice (id_kind: "make", no /vehicle/co2 in accepted_by).

Example - none

GET /vehicle/lookup?q=Microlino:

{
  "data": {
    "query": "Microlino",
    "verdict": "none",
    "candidates": [],
    "total": 0,
    "truncated": false,
    "limits": "A resolution names what the referential publishes for the text you sent, as of the date carried by the provenance; it does not certify that the entity resolved is the one you had in mind. Candidates come from the recall campaigns and the European registration aggregates already ingested: a make/model absent from both is not a statement about the vehicle, only about these referentials. Generation designations ('V', '5', 'Mk5') are not interpreted, as no referential publishes them; a four-digit number in the text is read as a model year. A candidate without a year is accepted by /vehicle/recalls, where the year is optional, but not by /vehicle/co2, which requires one."
  },
  "provenance": {
    "source": "nhtsa+rappelconso+eea-co2-monitoring",
    "fetched_at": "2026-10-15T15:32:03.676846569Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-07-28T00:00:00Z" }
  }
}

A 200, billed, dated: neither the recall campaigns nor the passenger-car aggregates publish this designation as of 2026-07-28. The agent now knows not to pay a recalls or co2 call on a key it would have had to invent.

Sources

Candidates come from two kinds of referential, both already ingested:

  • recall campaigns - NHTSA (US) and RappelConso (France): make/model pairs, with the model-year ranges of their campaigns;
  • EEA CO2 monitoring aggregates (Regulation (EU) 2019/631): make, commercial name and model year of the cars registered in the EU.

NHTSA’s vPIC is used as a model-to-make bridge only - it lets “Golf” reach Volkswagen when the make is not in the text. It is not a source of candidates: vPIC does not publish enumerable make/model pairs, and its labels are not the keys the downstream routes accept. It therefore never appears in sources or in the provenance.

Errors

Only requests the service cannot answer leave the 200 range - none of them are billed. There is no 404 on this route: a designation no referential publishes is a none.

StatuscodeCase
400MISSING_PARAMETERq absent, empty or blank
400INVALID_INPUTq longer than 200 characters
500INTERNALInternal error (detail logged, not exposed)
{ "code": "MISSING_PARAMETER", "error": "missing or empty required parameter: q" }
{ "code": "INVALID_INPUT", "error": "'q' must be at most 200 characters" }

Attribution

Candidates are derived from public sources, each under its own terms:

  • NHTSA vehicle recalls and the vPIC decoder, 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 CO2 monitoring dataset published under Regulation (EU) 2019/631 (European Environment Agency / European Commission), reused under its open licence.

See also