What will a carte grise cost? French registration tax, by region

CO2 malus, weight malus and regional tax for a French registration, priced against the schedule in force on the date you supply and auditable against the law.

By Matthias Begot · · Updated

An agent costing a car purchase, an import or a fleet renewal in France needs one number: what the carte grise will cost. That number is not a constant and it is not something a language model can recall — it is the output of three separate taxes, each read from a schedule that changes by finance law, and the applicable schedule is the one in force on the registration date. GET /vehicle/tax computes it at that date, shows the bracket behind every euro, and names the articles of the code des impositions sur les biens et services it applied. One x402 settlement, no account.

The schedule moves, and it does not wait for January

The tempting shortcut — ask a model, get a malus — fails on the calendar. The French CO2 malus threshold has moved three times in three finance laws, and the last move did not even land on a 1 January.

  • Law n° 2025-127 of 14 February 2025 (loi de finances for 2025) set a new CO2 scale that entered into force on 1 March 2025, at a trigger threshold of 113 g/km — a mid-year switchover, because the law itself was promulgated in February.
  • The same law programmed the threshold down 5 g each year: 108 g/km from 1 January 2026, 103 g/km in 2027.
  • The weight malus followed its own track: its trigger threshold dropped from 1 600 kg to 1 500 kg in 2026.

So “the malus” is meaningless without a date, and a model trained before February 2025 will confidently price a 2026 registration on a 2024 scale. The endpoint takes registration_date and selects the schedule to the day, including across that 1 March 2025 boundary. Omit it and you get today’s.

Three taxes, one call

A French registration certificate bills three things, and an agent that prices only the malus is short by the other two:

ItemWhat it prices
co2_malusWLTP CO2 in g/km, priced gram by gram against the dated scale
weight_malusMass in running order, at the marginal rate bracket by bracket after allowances
regional_taxFiscal power × the tariff voted by the region, plus the flat national fee

The regional part is the one most tools get wrong, because it is not national policy: each regional council votes its own tariff per fiscal horsepower, once a year. Two identical cars registered on the same day in two regions do not cost the same. And the flat national fee is levied on top of the proportional part.

GET /vehicle/tax?country=FR&co2_wltp=150&weight_kg=1900&fuel=diesel&fiscal_power=7&region=11&registration_date=2026-05-01

Every euro shows its bracket

The weight malus is not a rate times a mass; it is a marginal scale. The response derives it in the open rather than asserting a total:

"weight_malus": {
  "status": "due",
  "amount_eur": 5500,
  "mass_kg": 1900,
  "taxable_mass_kg": 1900,
  "threshold_kg": 1500,
  "brackets": [
    { "from_kg": 1500, "to_kg": 1699, "taxable_kg": 200, "eur_per_kg": 10, "amount_eur": 2000 },
    { "from_kg": 1700, "to_kg": 1799, "taxable_kg": 100, "eur_per_kg": 15, "amount_eur": 1500 },
    { "from_kg": 1800, "to_kg": 1899, "taxable_kg": 100, "eur_per_kg": 20, "amount_eur": 2000 }
  ]
}

Three bracket lines, three multiplications, one sum. An agent can recompute the 5 500 EUR from the fields it was given — or show them to a human who wants to know why. The CO2 item states its threshold_g and whether the amount was capped; the regional item splits proportional_eur from fixed_fee_eur.

Auditable against the law, not against a blog post

The part that makes the figure defensible is schedule.legal_basis: every applied article, with the code it belongs to, its number, the date it entered into force, its subject — and its LEGI identifier.

{
  "code": "code des impositions sur les biens et services",
  "article": "L. 421-62",
  "legi_id": "LEGIARTI000051214838",
  "in_force_since": "2025-03-01",
  "subject": "barèmes CO2 WLTP"
}

A real answer carries the ten or so articles behind the three items. That LEGI identifier is a key into another endpoint: GET /legal/article returns the text of that article as in force, so an agent that must justify a figure can fetch the law it was computed from instead of citing a tax calculator.

Read status before the amount

The three items share one closed enumeration, and the distinction between its values is the difference between “you owe nothing” and “we do not know”:

statusMeaning
dueComputed — including a real 0 EUR below the threshold
exemptAn automatic exemption applied, named by its article — never silent
not_applicableNo schedule existed at that date (the weight malus before 2022) — an absence of tax
undeterminedA required figure was missing: no amount, a reason instead

Exemptions are cited, not implied. An electric car comes back with all three items exempt, each carrying the article that exempts it — and the regional item still bills a non-zero amount, because the flat national fee survives the regional exemption. That is the law, and an endpoint that rounded it to zero would be quietly wrong. A plug-in hybrid gets an allowance instead: a mass deduction, with the kilograms it removed and an explicit exempts: false.

The total disappears rather than being partial

Drop co2_wltp from the request and the CO2 malus cannot be computed. What the endpoint does not do is add up the other two and call it a total:

{
  "co2_malus": {
    "status": "undetermined",
    "threshold_g": 108,
    "reason": "'co2_wltp' is neither declared nor resolved: the CO2 malus cannot be computed"
  },
  "weight_malus": { "status": "due", "amount_eur": 5500 },
  "regional_tax": { "status": "due", "amount_eur": 493.65 }
}

total_eur is absent from the JSON. Not null, not a partial sum — absent. Every field that could be computed still is, so the agent keeps what it can use; but a partial sum presented as a total is a wrong number, and a wrong number in a costing pipeline propagates silently. The same discipline applies to coverage: France only. Any other country is a 400 that says so, because approximating a neighbouring schedule would be worse than refusing.

Declared, or resolved from the vehicle

The technical figures — CO2, mass, fuel — can be supplied directly or resolved from a vehicle key. Pass a type-approval number, or a make/model/year, and they come from the EU CO2 monitoring dataset behind GET /vehicle/co2. inputs_used then labels each figure declared or resolved, so the result stays reconstructable, and a declared value always wins over a resolved one.

The administrative figures do not work that way. Fiscal power and region are French records absent from the EU registry, so they are always declarative.

Resolution also propagates its own precision. A make/model/year key matches a whole model-year population, and the answer says so: provenance.source becomes fr-registration-tax+eea-co2-monitoring, and limits gains a line stating the CO2 came from a model-year aggregate spanning N versions — so the malus of one specific version may differ. As the CO2 article shows, a single commercial name can span a battery electric variant at 0 g/km and a petrol version well over the malus threshold. That uncertainty is propagated, never hidden behind a clean-looking euro amount.

What it refuses to do

Six reading limits come back with every answer. The load-bearing ones:

  • New vehicles registered in France. The reduction coefficient for vehicles already registered abroad — imported used cars — is not modelled.
  • WLTP schedules only. NEDC and administrative-power scales for vehicles outside WLTP are out.
  • Exemptions that depend on the taxpayer, not the vehicle (disability card, dependent children, a legal person operating a vehicle with at least eight seats) are published in the schedules but never applied automatically — the service does not know who is buying.
  • The regional tariff is voted yearly and applies at the date the certificate is issued, not at first registration; only current tariffs are published, so there is no regional history to query.
  • Out of scope: annual taxes on business use, transport-vehicle taxes, purchase incentives and the non-tax delivery fee.

This is a costing of the published schedules. It is not tax advice, and it is not a quote from the administration.

Where it sits in the x402 loop

  1. Discover the endpoint in the live catalog, call it, receive the 402.
  2. Pay — sign the chosen rail and replay the request.
  3. Read status on each item before reading any amount.
  4. Branch — commit the total, ask a human when an item is undetermined, or go back for a precise type-approval number when the CO2 was a model-year aggregate.

Per the x402 golden rule, the agent pays for the answer to its question. A computable request is a billed 200, including one where an item is exempt, not_applicable or undetermined — those are answers. Only requests the service cannot answer — a country other than France, a date before the first published schedule, an unknown region, a missing fuel — leave the 200 range and are not billed. Quickstart walks the full discover → 402 → pay → replay cycle; price and accepted rails are served live by the catalog, never pinned in an article.

Chain it

GET /vehicle/co2 supplies the CO2 and mass this costing is priced on, and states the precision it matched them at. GET /vehicle/vin/decode turns a VIN from a listing into the make, model and year that key that lookup. For a running-cost picture rather than a purchase cost, GET /vehicle/critair answers the low-emission-zone question from the same fuel vocabulary. And for all of it against one VIN in a single settlement, use the composite GET /vehicle/report.

For the full field reference, the resolution keys and the error codes, see the GET /vehicle/tax documentation; for how agents discover and call Invoket endpoints, see For agents.