# 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. Published 2026-08-10 · Updated 2026-08-15 · HTML version: https://invoket.com/blog/what-will-registering-this-car-in-france-cost --- 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`](/docs/api/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: | Item | What it prices | |----------------|--------------------------------------------------------------------------------| | `co2_malus` | WLTP CO2 in g/km, priced gram by gram against the dated scale | | `weight_malus` | Mass in running order, at the marginal rate bracket by bracket after allowances | | `regional_tax` | Fiscal 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. ```http GET /vehicle/tax?country=FR&co2_wltp=150&weight_kg=1900&fuel=diesel&fiscal_power=7®ion=11®istration_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: ```json "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**. ```json { "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`](/docs/api/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": | `status` | Meaning | |------------------|--------------------------------------------------------------------------------| | `due` | Computed — including a real **0 EUR** below the threshold | | `exempt` | An automatic exemption applied, **named by its article** — never silent | | `not_applicable` | No schedule existed at that date (the weight malus before 2022) — an absence of tax | | `undetermined` | A 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: ```json { "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`](/docs/api/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](/blog/what-co2-was-this-car-type-approved-at) 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](/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 [`GET /vehicle/co2`](/docs/api/vehicle-co2) supplies the CO2 and mass this costing is priced on, and states the precision it matched them at. [`GET /vehicle/vin/decode`](/docs/api/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`](/docs/api/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`](/docs/api/vehicle-report). For the full field reference, the resolution keys and the error codes, see the [`GET /vehicle/tax` documentation](/docs/api/vehicle-tax); for how agents discover and call Invoket endpoints, see [For agents](/docs/for-agents).