Multi-rate VAT invoices
One stay can mix VAT rates — a room at 10% and breakfast or a service fee at 20%. TaxLens attributes each line to its own VAT rate, buckets them into per-rate subtotals, and reconciles each one independently so a mis-attributed line is caught instead of silently averaging out.
Why one invoice carries several rates
Accommodation often sits at a reduced VAT rate while ancillary supplies don't. A German folio might charge 7% on the room but 19% on a parking line; an Italian one, 10% on the room and 22% on a minibar charge. The invoice has to show each rate separately — a single blended rate would be wrong and would fail validation.
Per-line attribution with line_item_index
Each tax component is tied to the line it belongs to via line_item_index:
line_item_index: null— the room base (the primary accommodation line).line_item_index: 0, 1, …— each entry inline_items[](a fee, breakfast, a service charge) gets its own index and its own VAT component.
The projection routes each line to its own VAT component, then buckets everything by (category, rate) into one EN 16931 VAT subtotal per distinct rate. Pass line items on the calculation request — see Scenario fields and Tax categories.
{
"jurisdiction_code": "DE",
"nights": 2,
"nightly_rate": 200, // room → 7%
"currency": "EUR",
"line_items": [
{ "item_type": "amenity_fee", "amount": 40, "description": "Parking" } // → 19%
]
}Each subtotal reconciles on its own
Validation reconciles per (category, rate), not just on the grand total. This enforces two EN 16931 rules at once:
- BR-CO-10 — the sum of all line net amounts equals the document's total net.
- BR-S-8 — for each standard-rated subtotal, the taxable base is the sum of exactly the lines at that rate, and the tax equals base × rate.
(category, rate) bucket independently catches the mistake, because the misplaced line breaks its bucket's base-times-rate identity.In the UBL output
The serializer loops the VAT rows into multiple cac:TaxSubtotal elements, and each cac:InvoiceLine carries its own cac:ClassifiedTaxCategory with the right cbc:Percent — so the two-rate split is explicit in the wire format.
<cac:TaxTotal>
<cbc:TaxAmount currencyID="EUR">35.60</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="EUR">400.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="EUR">28.00</cbc:TaxAmount>
<cac:TaxCategory><cbc:ID>S</cbc:ID><cbc:Percent>7</cbc:Percent>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme></cac:TaxCategory>
</cac:TaxSubtotal>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="EUR">40.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="EUR">7.60</cbc:TaxAmount>
<cac:TaxCategory><cbc:ID>S</cbc:ID><cbc:Percent>19</cbc:Percent>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme></cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>Next
The cross-border B2B reverse charge (category AE on the commission line) is another way one invoice carries multiple categories — see Reverse charge. For the standards behind these subtotals, revisit EN 16931, UBL & PEPPOL.