GET /vehicle/critair
Returns the Crit’Air class of a vehicle and the low-emission-zone (ZFE) restrictions that currently apply to that class in France. The class is a pure regulatory calculation over declared characteristics - it is not a lookup: the runtime holds no VIN store, performs no network call and holds no secret, and answers in milliseconds from embedded reference tables (the official Crit’Air grid and a snapshot of modelled ZFE perimeters).
Input is fully declarative: the agent supplies the vehicle’s fuel and
either its euro_norm or its first_registration date - values it typically
gets from a registration document, a VIN decode
or a listing. There is no lookup by licence plate. Use it to answer
questions such as “can this vehicle enter the Lyon ZFE?”, to check eligibility
before a used-car purchase, or for fleet compliance. See the live
/catalog for the authoritative endpoint listing and price.
x402 golden rule: the agent pays for the answer to its question. Any
usable input produces a successful answer -> 200, including non_classe
(a vehicle too old to qualify for a sticker) and including an empty
zfe_restrictions list (a clean sticker that no modelled zone restricts) -
that verdict is the data being sold. Requests the service cannot compute - a
missing fuel, neither a Euro norm nor a date, an unknown fuel or norm, an
unparsable or impossible date (see bounds),
or a vehicle category outside the modelled scope - leave the 200 range and are
not billed.
Parameters
Query-string parameters. fuel is always required; then supply at least one
of euro_norm or first_registration.
| Parameter | Type | Required | Description |
|---|---|---|---|
fuel | string | yes | electric, hydrogen, petrol, diesel, petrol_hybrid or diesel_hybrid (common FR/EN synonyms tolerated). A hybrid is classified by its combustion engine |
euro_norm | string | one of these two | Emission standard: pre_euro or euro2..euro6 (tolerant forms like euro 5, euro-5, 5 accepted; euro1 folds into pre_euro) |
first_registration | string | one of these two | Date of first registration, YYYY-MM-DD; used to derive the Euro norm when euro_norm is absent. Must be a plausible registration date - a future date or one before 1900-01-01 is a 400 (see bounds) |
vehicle_class | string | no (default m1) | Vehicle category, m1 (passenger car) or n1 (light van, ≤ 3.5 t). Scope is M1/N1 only in v1 |
When both euro_norm and first_registration are given, euro_norm
takes precedence (the more precise, explicit path) and derivation.via
reports which path was used. Precedence does not exempt the date from its
bounds: a
first_registration you supply is parsed and bounded identically whether or not
it ends up driving the calculation - an impossible date is refused even when
euro_norm would have won.
GET /vehicle/critair?fuel=petrol&euro_norm=euro6
GET /vehicle/critair?fuel=diesel&first_registration=2009-05-01
GET /vehicle/critair?fuel=electric&euro_norm=euro6
200 response - UnifiedResponse
{
"data": { ... },
"provenance": {
"source": "critair-regulation+zfe-open-data",
"fetched_at": "2026-07-04T12:00:00Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-01" }
}
}
provenance.source:critair-regulation+zfe-open-data- the regulatory grid plus the embedded ZFE reference.freshness.kind:snapshot;as_ofis the oldest date among the two embedded references (regulatory grid and ZFE version) - the service never presents itself as fresher than its stalest input.
Fields of data
| Field | Type | Description |
|---|---|---|
vehicle_class | string | Effective category, m1 or n1 (the one provided, or the m1 default) |
fuel | string | Faithful echo of the declared fuel; a hybrid stays distinct from the engine it is classified by |
euro_norm | string | Echo of the declared Euro norm; present only when the agent supplied it |
first_registration | string | Echo of the declared date (YYYY-MM-DD); present only when the agent supplied it |
critair_class | string | The computed sticker: "0", "1".."5", or "non_classe" |
derivation | object | How the class was reached - makes the calculation auditable (see below) |
zfe_restrictions | array | Zones that restrict this class, sorted by zone name; may be empty (no modelled zone restricts it) |
notes | string | Modelling limits stated in plain text (local/time-based exemptions are out of scope) |
regulation | object | { reference, as_of } - the classification grid reference and its verification date |
Only the input paths the agent actually provided are echoed: euro_norm and
first_registration are each omitted when not supplied, never guessed.
derivation
| Field | Type | Description |
|---|---|---|
via | string | euro_norm, first_registration or fuel - the path actually used. fuel is the electric/hydrogen short-circuit: the fuel alone settles the class, no norm is involved |
euro_norm | string | The effective Euro norm used: declared as-is on the euro_norm path, inferred from the date on the first_registration path (see inference); omitted on the fuel path |
fuel | string | The fuel column used for classification |
zfe_restrictions[]
| Field | Type | Description |
|---|---|---|
zone_id | string | Stable zone identifier (e.g. metropole-lyon) |
zone_name | string | Official zone name |
area | string | Human description of the restricted perimeter |
since | string | Date the restriction on this class took effect (YYYY-MM-DD) |
Example - petrol Euro 6, a clean sticker
A recent petrol car is Crit’Air 1. No modelled ZFE restricts class 1, so
zfe_restrictions is empty - a legitimate 200.
{
"data": {
"vehicle_class": "m1",
"fuel": "petrol",
"euro_norm": "euro6",
"critair_class": "1",
"derivation": { "via": "euro_norm", "euro_norm": "euro6", "fuel": "petrol" },
"zfe_restrictions": [],
"notes": "Restrictions PERMANENTES en vigueur pour les véhicules M1/N1, par classe Crit'Air. Les dérogations locales, horaires et sectorielles (axes, dérogations petits rouleurs, laissez-passer) sont HORS PÉRIMÈTRE et ne sont pas modélisées. Les calendriers de durcissement au-delà des étapes ci-dessous ont été révisés par la loi de 2025 et ne sont retenus que lorsqu'ils sont effectivement entrés en vigueur.",
"regulation": {
"reference": "Arrêté du 21 juin 2016 (nomenclature Crit'Air, art. R. 318-2 du code de la route)",
"as_of": "2026-07-04"
}
},
"provenance": {
"source": "critair-regulation+zfe-open-data",
"fetched_at": "2026-07-04T12:00:00Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-01" }
}
}
Example - diesel derived from a date, restricted in a ZFE
With no euro_norm, the norm is derived from first_registration; a 2009
diesel is Euro 4, hence Crit’Air 3, and is banned in the modelled zones.
derivation.via says the date drove the calculation, and
derivation.euro_norm reports the effective norm.
{
"data": {
"vehicle_class": "m1",
"fuel": "diesel",
"first_registration": "2009-05-01",
"critair_class": "3",
"derivation": { "via": "first_registration", "euro_norm": "euro4", "fuel": "diesel" },
"zfe_restrictions": [
{
"zone_id": "grenoble-alpes",
"zone_name": "ZFE-m de Grenoble-Alpes Métropole",
"area": "13 communes de Grenoble-Alpes Métropole : Grenoble, Saint-Égrève, Saint-Martin-le-Vinoux, La Tronche, Meylan, Gières, Saint-Martin-d'Hères, Eybens, Échirolles, Le Pont-de-Claix, Seyssins, Seyssinet-Pariset et Fontaine",
"since": "2025-01-01"
},
{
"zone_id": "metropole-lyon",
"zone_name": "ZFE-m de la Métropole de Lyon",
"area": "Lyon, Villeurbanne, Caluire-et-Cuire, Bron et Vénissieux (périmètre intra-boulevard périphérique Laurent-Bonnevay)",
"since": "2025-01-01"
},
{
"zone_id": "grand-paris",
"zone_name": "ZFE-m de la Métropole du Grand Paris",
"area": "Communes de la Métropole du Grand Paris situées à l'intérieur de l'autoroute A86 (Paris et petite couronne, hors A86 elle-même)",
"since": "2025-01-01"
}
],
"notes": "...",
"regulation": {
"reference": "Arrêté du 21 juin 2016 (nomenclature Crit'Air, art. R. 318-2 du code de la route)",
"as_of": "2026-07-04"
}
},
"provenance": {
"source": "critair-regulation+zfe-open-data",
"fetched_at": "2026-07-04T12:00:00Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-01" }
}
}
Example - electric is Crit’Air 0
An electric or hydrogen vehicle is class "0" regardless of Euro norm; no zone
restricts it. The fuel alone settles the class, so derivation.via is "fuel"
and no norm is retained - a declared euro_norm is still echoed back, it just
plays no part in the computation.
{
"data": {
"vehicle_class": "m1",
"fuel": "electric",
"euro_norm": "euro6",
"critair_class": "0",
"derivation": { "via": "fuel", "fuel": "electric" },
"zfe_restrictions": [],
"notes": "...",
"regulation": {
"reference": "Arrêté du 21 juin 2016 (nomenclature Crit'Air, art. R. 318-2 du code de la route)",
"as_of": "2026-07-04"
}
},
"provenance": {
"source": "critair-regulation+zfe-open-data",
"fetched_at": "2026-07-04T12:00:00Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-01" }
}
}
Example - too old to qualify is still a 200
A pre-Euro / Euro 1 vehicle is non_classe - it does not qualify for a sticker.
That is the answer, not an error, and it is billed like any successful call.
{
"data": {
"vehicle_class": "m1",
"fuel": "diesel",
"euro_norm": "pre_euro",
"critair_class": "non_classe",
"derivation": { "via": "euro_norm", "euro_norm": "pre_euro", "fuel": "diesel" },
"zfe_restrictions": [
{
"zone_id": "grenoble-alpes",
"zone_name": "ZFE-m de Grenoble-Alpes Métropole",
"area": "13 communes de Grenoble-Alpes Métropole : Grenoble, Saint-Égrève, Saint-Martin-le-Vinoux, La Tronche, Meylan, Gières, Saint-Martin-d'Hères, Eybens, Échirolles, Le Pont-de-Claix, Seyssins, Seyssinet-Pariset et Fontaine",
"since": "2023-07-01"
},
{
"zone_id": "metropole-lyon",
"zone_name": "ZFE-m de la Métropole de Lyon",
"area": "Lyon, Villeurbanne, Caluire-et-Cuire, Bron et Vénissieux (périmètre intra-boulevard périphérique Laurent-Bonnevay)",
"since": "2020-01-01"
},
{
"zone_id": "grand-paris",
"zone_name": "ZFE-m de la Métropole du Grand Paris",
"area": "Communes de la Métropole du Grand Paris situées à l'intérieur de l'autoroute A86 (Paris et petite couronne, hors A86 elle-même)",
"since": "2019-07-01"
}
],
"notes": "...",
"regulation": {
"reference": "Arrêté du 21 juin 2016 (nomenclature Crit'Air, art. R. 318-2 du code de la route)",
"as_of": "2026-07-04"
}
},
"provenance": {
"source": "critair-regulation+zfe-open-data",
"fetched_at": "2026-07-04T12:00:00Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-01" }
}
}
Bounds on first_registration - a bound is not a verdict
A registration date is checked for possibility before anything is computed.
Two inputs are refused with a 400 INVALID_INPUT and a guiding message:
- a date in the future - tolerance of one day past today (UTC), just enough to absorb an agent clocked in a time zone ahead of UTC; beyond that the date is in the future for every observer on the planet;
- a date before
1900-01-01- deliberately a wide, defensible floor: the registered fleet starts well after it, so an earlier date is not an old vehicle, it is an impossible entry.
Do not confuse a bound with a verdict. non_classe is the billed 200 of a
genuinely old real vehicle - first_registration=1996-06-01 on a petrol car is
answered, priced and classified like any other call. 2099-01-01 is not an old
vehicle and not a strict one either: it is an entry no vehicle can have, and
answering it with a confident regulatory verdict would be worse than refusing
it. The floor sits far below any classification threshold of the grid, so it
never converts an answerable question into an error.
{
"error": "'first_registration' cannot be in the future (got '2099-01-01', today is 2026-07-26 UTC); supply the date the vehicle was actually first registered",
"code": "INVALID_INPUT"
}
{
"error": "'first_registration' predates the plausible vehicle fleet (got '1899-12-31', earliest accepted is 1900-01-01); a genuinely old vehicle is answered with critair_class 'non_classe'",
"code": "INVALID_INPUT"
}
The Euro norm derived from a date is an inference
On the first_registration path, derivation.euro_norm is inferred, not
observed: the date is read against the norm that was mandatory for new
registrations at that time. That inference is capped by the Euro 6 mandate at
registration - 2015-09-01 for m1, 2016-09-01 for n1 (the same one-year
offset the rest of the grid already applies to N1). A car first registered in
2026 is therefore reported as euro6, not euro5.
Treat it as an inference in both directions: a vehicle can have been
type-approved under a norm earlier than its registration date, so the explicit
euro_norm path remains the precise one - supply it when the registration
document gives it. What the cap changes is the auditability of the answer,
not the answer itself: within the range where the cap applies, Euro 5 and Euro 6
share the same sticker (petrol -> 1, diesel -> 2), so no critair_class
moves - only derivation.euro_norm stops claiming a floor as if it were the
vehicle’s norm.
Scope and modelling honesty
The Crit’Air class is a deterministic function of category, fuel and Euro norm, so the result is exact for the input given - but the input is declared, not verified: the service does not check that the vehicle really has those characteristics. Feed it the wrong fuel or norm and you get the wrong class.
zfe_restrictions models permanent, in-force restrictions for M1/N1
vehicles by class, from an embedded snapshot. It deliberately does not model
local, time-based or sector-based exemptions (protected axes, low-mileage
permits, day passes), and tightening schedules revised by the 2025 law are only
included once actually in force. This is stated to the caller in notes.
Scope is bounded further: M1/N1 only (no heavy vehicles, buses or two-
wheelers in v1) and no lookup by licence plate.
Because the core is pure calculation over embedded references, there is no
store 5xx and no staleness 5xx: the snapshot is always servable, and
provenance.freshness.as_of is deliberately the oldest of the two references.
Errors
Only requests the service cannot compute leave the 200 range, and they are not billed. Messages are in English and guiding - they list the accepted values.
| Status | code | Case |
|---|---|---|
| 400 | MISSING_PARAMETER | fuel is absent or empty |
| 400 | INVALID_INPUT | Neither euro_norm nor first_registration given; unknown fuel or Euro norm; unparsable date; or a vehicle_class outside M1/N1 |
| 400 | INVALID_INPUT | A first_registration in the future (one-day tolerance) or before 1900-01-01 - an impossible date, refused even when euro_norm takes precedence (bounds) |
| 404 | NOT_FOUND | Unknown route |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "error": "missing required parameter 'fuel'", "code": "MISSING_PARAMETER" }
{ "error": "provide either 'euro_norm' (pre_euro or euro2..euro6) or 'first_registration' (YYYY-MM-DD)", "code": "INVALID_INPUT" }
Attribution
- The Crit’Air classification grid derives from French regulation - the Arrêté du 21 juin 2016 (art. R. 318-2 of the code de la route).
- The ZFE reference (perimeters and per-class restriction dates) is built from French open data under the Licence Ouverte / Etalab.
See also
GET /vehicle/vin/decode- decode a VIN into manufacturer, model year and deterministic attributes to feed this endpoint.GET /vehicle/recalls- candidate recall campaigns for a vehicle, consolidated offline from public sources.GET /vehicle/report- a composite report for one VIN (decode + recalls + Crit’Air) in a single settlement.- For agents - discovery surfaces, the live
/catalogand how settlement works.