Skip to content

Extracting Tax and VAT Lines From Invoices Across Countries

10 min read · updated August 11, 2026

Tax on an invoice is not one concept expressed in different languages. EU VAT, Indian GST and US sales tax differ in who owes the tax, whether it is stated per rate, whether it is included in the printed price, and whether the supplier is registered at all. A field called tax_amount flattens all of that.

Three regimes, not one field

The structural differences that break a shared schema are worth naming before any prompt is written.

  • Whether the tax is broken out per rate. An EU invoice with goods at a standard rate and a reduced-rate item must show the taxable amount for each rate separately. A US invoice typically shows one tax number covering several overlapping jurisdictions.
  • Whether the price is tax-inclusive. European retail and many service invoices print gross unit prices; a B2B invoice usually prints net. Reading a gross unit price as net inflates the net total by the tax rate and the totals then fail to foot.
  • Whether the supplier charges tax at all. Reverse charge, exemption and out-of-scope supplies produce invoices with a tax rate of zero, no tax amount, and a mandatory phrase explaining why. That is a valid invoice, not a failed extraction.

EU VAT: the Article 226 field map

Article 226 of Council Directive 2006/112/EC lists the particulars a full VAT invoice must carry, and it is close to a ready-made schema. The points that matter for tax extraction are: (3) the supplier’s VAT identification number; (4) the customer’s VAT identification number where the customer is liable; (8) the taxable amount per rate, the unit price exclusive of VAT, and any discounts not included in that unit price; (9) the VAT rate applied; and (10) the VAT amount payable, except where a special arrangement excludes it.

The load-bearing word in (8) and (9) is per rate. The right extraction target is a small table, not a scalar:

"tax_breakdown": [
  { "category": "S", "rate_percent": "21.0", "taxable_base": { "minor": 174000, "currency": "EUR" },
    "tax_amount": { "minor": 36540, "currency": "EUR" } },
  { "category": "S", "rate_percent": "9.0",  "taxable_base": { "minor": 42000,  "currency": "EUR" },
    "tax_amount": { "minor": 3780,  "currency": "EUR" } }
],
"tax_total": { "minor": 40320, "currency": "EUR" }

Each row is independently checkable: the tax amount should equal the taxable base times the rate, to within one minor unit of rounding, and the taxable bases should sum to the invoice net. Two arithmetic assertions per invoice, from a published article, with no model judgement involved.

Remember that Article 230 permits the invoice to be denominated in any currency as long as the VAT amount is in the national currency, so tax_amount may legitimately carry a different currency code from taxable_base. That is the case worked through in multi-currency line items.

Reverse charge and zero-rated supplies

On a cross-border B2B supply within the EU, the customer accounts for the tax rather than the supplier. The invoice shows a taxable amount, a rate of zero or a blank, no VAT amount, and — required by Article 226(11a) — the words “Reverse charge”. Article 226(11) requires a reference to the applicable provision where a supply is exempt.

Extract those tokens as a categorisation rather than throwing them away. The mandatory phrases are a small, closed vocabulary — “Reverse charge”, “Self-billing” from 226(10a), “Cash accounting” from 226(7a), the margin-scheme phrases from 226(13) and (14) — and they are far more reliable than inferring intent from a zero. A tax total of zero with none of these phrases present is the interesting case: either the phrase was missed on the page or the invoice is defective, and both deserve a human.

Rates, thresholds, mandatory wording and e-invoicing mandates change country by country and year by year. The article numbers above are stable; the rates are not. Treat any rate table you hard-code as something to revisit, and validate rates against a maintained source rather than a constant in your prompt.

India: one tax printed as two lines

Indian GST is the case that most reliably breaks a European schema. A supply within a state carries central GST and state GST as two separate lines at half the combined rate each; a supply between states carries a single integrated GST at the full rate. So an 18% supply appears either as CGST 9% plus SGST 9%, or as IGST 18%, depending only on where the place of supply is.

Two consequences. A pipeline that sums every tax-looking line double-counts nothing on an intra-state invoice but will report two rates of 9% where the economic rate is 18%, wrecking any rate-based analytics. And a pipeline that stores one tax_rate has to pick one of the two. Model the components:

"tax_breakdown": [
  { "component": "CGST", "rate_percent": "9.0", "tax_amount": { "minor": 90000, "currency": "INR" } },
  { "component": "SGST", "rate_percent": "9.0", "tax_amount": { "minor": 90000, "currency": "INR" } }
],
"effective_rate_percent": "18.0",
"place_of_supply": "27-Maharashtra"

The invoice also carries an HSN code per line for goods (a classification code with a documented number of digits that varies by taxpayer turnover), the supplier and recipient GSTIN, and the place of supply — which is what determines the intra- versus inter-state split in the first place, so extracting it lets you check the split rather than trust it. The Central Board of Indirect Taxes and Customs publishes the mandatory particulars; treat its rules, not a blog post, as the field list.

US sales tax: no registration, no per-rate breakdown

A US invoice will not have a VAT number, because there is no such thing. It will usually show a single sales-tax line that is the sum of state, county, city and special-district rates for the destination address, at a combined rate that is not a round number — 8.375% and similar. Some invoices show the jurisdictions separately; most do not.

Three things to model. Sales tax is generally destination-sourced, so the ship-to address on the invoice is what determines the rate, and extracting it is part of extracting the tax. Exempt purchases carry an exemption-certificate reference and no tax, which looks identical to a missed tax line unless you capture the reference. And whether shipping is taxable varies by state, which is one of the reasons freight belongs in its own field rather than in the goods lines — see separating freight and handling from goods.

Checks that catch the merge

  • Per-row arithmetic. Every breakdown row: base times rate equals amount, within one minor unit — a cross-field amount validation rule in the ordinary sense. A row that fails by a factor near 2 on an Indian invoice means CGST and SGST were merged.
  • Inclusive-price detection. If the sum of line nets equals the gross total rather than the net total, the unit prices were tax-inclusive and the whole extraction is shifted.
  • Zero tax without a phrase. Flag it. This is where reverse-charge invoices and genuinely broken ones separate.
  • Rate plausibility. A rate outside the set of rates that exist in the supplier’s country is an OCR digit error far more often than it is a new tax.