# What WLTP CO2 was this car type-approved at? > Type-approval WLTP CO2 and technical specs for any European car, from the EU 2019/631 monitoring dataset — every figure stating the precision it was matched at. Published 2026-08-10 · Updated 2026-08-15 · HTML version: https://invoket.com/blog/what-co2-was-this-car-type-approved-at --- An agent that needs a European vehicle's CO2 figure — to cost a registration tax, to compare two listings, to fill an import declaration — cannot get it from a language model's memory. The number depends on the *test procedure* and on the *exact version* of the car, and a commercial name like "Clio 2021" covers hundreds of versions that do not emit the same thing. [`GET /vehicle/co2`](/docs/api/vehicle-co2) answers the question from the official EU CO2 monitoring dataset, offline, and — the part that matters — it tells you **how precisely the figure was matched**. One x402 settlement per answer, no account. ## The problem: a figure that is wrong in two different ways Ask a model "what is the CO2 of a 2019 Golf" and you will get a number. It will be wrong twice over: - **Wrong procedure.** The same car carries two official figures. The old NEDC value and the WLTP value, measured under the procedure that replaced it, are not the same number and do not answer the same question. Only one of them feeds a modern tax scale. - **Wrong granularity.** A make/model/year is not a vehicle. Under one commercial name a manufacturer sells petrol, diesel, hybrid — and increasingly a battery electric variant, type-approved at 0 g/km. A single "the CO2 of a 208" is a fiction. Both failures are silent. The agent gets a plausible integer, uses it to price something, and nothing in the pipeline flags that it was invented. What an agent actually needs is a figure with a stated provenance and a stated precision. ## One dataset: every new registration in the EU The source is the **EU CO2 monitoring dataset** published under [Regulation (EU) 2019/631](https://www.eea.europa.eu/en/datahub/datahubitem-view/fa8b1229-3db6-495d-b18e-9c9b3267c02b). Member states report, for **each new passenger car registered** on their territory, the manufacturer, the type-approval number with its type, variant and version, the make and commercial name, the specific CO2 emissions under both the NEDC and WLTP protocols, masses, wheelbase, engine capacity and power, fuel type and mode. The European Environment Agency runs quality checks and publishes a provisional then a final database, one release per reporting year. That makes it an unusually good base for an agent: it is official, it is per-registration rather than per-brochure, and it is a **flat file** — which is why Invoket serves it from a local store with no network call and no secret at request time. It also sets the hard boundary of the endpoint: the dataset describes **registrations**, not individual cars. It carries no VIN. ## Two keys, and the precision each one buys The endpoint takes two mutually exclusive key forms — the precise one and the approximate one — and every response opens with a `match_level` naming which one you got. | `match_level` | How it was keyed | What you get | |--------------------------|----------------------------------------------------------|-----------------------------------------------------------------| | `type_approval_exact` | Type-approval number narrowed by `variant` and `version` | Unique published values for one approved version | | `type_approval_partial` | Type-approval number alone | Versions of that approval, registration-weighted median | | `model_year` | `make` + `model` + `year` | A whole model-year population — **the only level with p10/p90** | ```http GET /vehicle/co2?type_approval=e2*2007/46*0624*10 GET /vehicle/co2?make=Peugeot&model=208&year=2022 ``` The three parts of the approximate key travel together: two of the three is a `400` naming the one that is missing, and a year outside the ingested window is a `404` that **names the years actually served** rather than returning a silent miss. ```json { "code": "INVALID_INPUT", "error": "the approximate key needs all of 'make', 'model' and 'year' (missing: year)" } { "code": "YEAR_OUT_OF_WINDOW", "error": "year 2030 is outside the ingested window (covered years: 2018-2022)" } ``` ## Why the dispersion *is* the answer Here is a real `model_year` answer for a Peugeot 208 registered in 2022: ```json { "data": { "match_level": "model_year", "vehicle": { "make": "PEUGEOT", "commercial_name": "208", "year": 2022 }, "typical_specs": { "engine_capacity_cm3": 1199, "engine_power_kw": 74, "mass_kg": 1165 }, "co2": { "wltp": { "p50": 115, "p10": 0, "p90": 122 }, "unit": "g/km", "sample_size": 184519, "low_sample": false }, "coverage": { "years_covered": [2018, 2019, 2020, 2021, 2022], "versions_aggregated": 225, "min_sample": 5 } }, "provenance": { "source": "eea-co2-monitoring", "freshness": { "kind": "snapshot", "as_of": "2026-07-28T00:00:00Z" } } } ``` Read `p10: 0`. Two hundred and twenty-five versions sit behind that median, and the bottom decile of them is the **battery electric e-208**, type-approved at zero, sold under the same commercial name. The median of 115 g/km is a true statement about the population and a false statement about any car you are actually looking at. This is the whole design argument for `match_level`. An endpoint that returned `115` and stopped would look more confident and be less useful. An agent that reads `p10` and `p90` can branch: when the spread is wide, the model-year answer is not good enough for a tax computation, and the agent must obtain the type-approval number instead of rounding a median. `typical_specs` carries the same warning in its own limit: each figure there is an **independent** weighted median over the aggregated versions, so together they describe no single existing car. They are a portrait of a population, not a datasheet. ## NEDC and WLTP are two different questions The same endpoint on a pre-switchover year shows why the procedure has to be explicit: ```json "co2": { "wltp": { "p50": 138, "p10": 128, "p90": 169 }, "nedc": 113, "unit": "g/km", "sample_size": 535576 } ``` That is a 2019 Volkswagen Golf, over half a million registrations. The gap between the correlated NEDC value and the WLTP median is 25 g/km — enough to move a vehicle several brackets down a CO2-indexed tax scale. `nedc` is served **only where the source publishes it**; on later registrations the field is simply absent, and absent is not zero. The same rule runs through the whole response. **A field the source does not carry is omitted, never served `null`** — a battery electric has no `engine_capacity_cm3`, and that absence is itself the fact. Conversely `0` is a real value: an EV type-approved at `0 g/km` returns zeros, and an agent must never read a missing block as a zero. ## What the endpoint refuses to do Every answer carries its reading limits inline, because they are instructions rather than a disclaimer: - **Type-approval values, not real-world consumption.** These are laboratory WLTP figures. The European Commission's own on-board monitoring (OBFCM) data, [published in July 2024](https://climate.ec.europa.eu/news-other-reads/news/publication-real-world-co2-emissions-and-fuel-consumption-cars-and-vans-collected-2022-2024-07-26_en), found real-world emissions of petrol and diesel cars run **around 20 % above** their WLTP type-approval values — and plug-in hybrids more than three times above. Type-approval values are the right input for a regulatory or fiscal computation and the wrong input for a running-cost estimate. - **A statistical aggregate of registrations**, not a manufacturer datasheet. - **No VIN → variant/version link.** A European VIN does not carry the type-approval variant outside manufacturer databases. That is why a VIN-keyed answer is always `model_year`, and the limit is stated rather than worked around. - **No regulated pollutants** — NOx and particulates are out of scope. It is also not a valuation, a price quote or a purchase recommendation. ## Where it sits in the x402 loop No signup precedes the call: 1. **Discover** the endpoint in the live catalog, call it, receive the `402` challenge. 2. **Pay** — sign the chosen rail and replay the request. 3. **Read** `match_level` *first*, then the figures. 4. **Branch** — feed a tax computation, flag a listing, or go back for a type-approval number when the dispersion is too wide to commit to. Per the x402 golden rule, an agent pays for the answer to its question: a key the dataset matches is a billed **200**, including a genuine `0 g/km` for an electric vehicle. Requests the service cannot answer — no key, both key forms at once, an incomplete approximate key, a year outside the window, a vehicle absent from the dataset — leave the 200 range and are not billed. [Quickstart](/docs/quickstart) walks the full discover → `402` → pay → replay cycle; price and accepted rails are served live by the [catalog](https://api.invoket.com/catalog), never pinned in an article. ## Chain it CO2 and mass are exactly the two figures a French registration tax is priced on, so [`GET /vehicle/tax`](/docs/api/vehicle-tax) is the natural next call. For a used listing, [`GET /vehicle/vin/decode`](/docs/api/vehicle-vin-decode) establishes the make, model and model year that key the CO2 lookup, while [`GET /vehicle/critair`](/docs/api/vehicle-critair) answers the low-emission-zone question from declared characteristics. For all of it against one VIN in a single settlement, use the composite [`GET /vehicle/report`](/docs/api/vehicle-report) — where the CO2 block is always `model_year`, for the reason above. For the full field reference, the `coverage` semantics and the error codes, see the [`GET /vehicle/co2` documentation](/docs/api/vehicle-co2); for how agents discover and call Invoket endpoints, see [For agents](/docs/for-agents).