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.

ParameterTypeRequiredDescription
fuelstringyeselectric, hydrogen, petrol, diesel, petrol_hybrid or diesel_hybrid (common FR/EN synonyms tolerated). A hybrid is classified by its combustion engine
euro_normstringone of these twoEmission standard: pre_euro or euro2..euro6 (tolerant forms like euro 5, euro-5, 5 accepted; euro1 folds into pre_euro)
first_registrationstringone of these twoDate 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_classstringno (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_of is 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

FieldTypeDescription
vehicle_classstringEffective category, m1 or n1 (the one provided, or the m1 default)
fuelstringFaithful echo of the declared fuel; a hybrid stays distinct from the engine it is classified by
euro_normstringEcho of the declared Euro norm; present only when the agent supplied it
first_registrationstringEcho of the declared date (YYYY-MM-DD); present only when the agent supplied it
critair_classstringThe computed sticker: "0", "1".."5", or "non_classe"
derivationobjectHow the class was reached - makes the calculation auditable (see below)
zfe_restrictionsarrayZones that restrict this class, sorted by zone name; may be empty (no modelled zone restricts it)
notesstringModelling limits stated in plain text (local/time-based exemptions are out of scope)
regulationobject{ 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

FieldTypeDescription
viastringeuro_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_normstringThe 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
fuelstringThe fuel column used for classification

zfe_restrictions[]

FieldTypeDescription
zone_idstringStable zone identifier (e.g. metropole-lyon)
zone_namestringOfficial zone name
areastringHuman description of the restricted perimeter
sincestringDate 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.

StatuscodeCase
400MISSING_PARAMETERfuel is absent or empty
400INVALID_INPUTNeither euro_norm nor first_registration given; unknown fuel or Euro norm; unparsable date; or a vehicle_class outside M1/N1
400INVALID_INPUTA first_registration in the future (one-day tolerance) or before 1900-01-01 - an impossible date, refused even when euro_norm takes precedence (bounds)
404NOT_FOUNDUnknown route
500INTERNALInternal 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 /catalog and how settlement works.