Calculation and cross-field rules — when the totals do not add up
BR-CO does not check individual fields but how they relate: that the totals reconcile, that two entries do not contradict each other, that a value matches the values it is derived from.
23 rules · 19 explained · Rule set v2026-08-31
Rounding is the usual cause
The total rules — BR-CO-10 through BR-CO-17 — rarely fail because somebody calculated wrongly. They fail because of when the rounding happened. Keep line amounts internally at four decimal places, round them to two only when writing the XML, and build the total from the unrounded values, and you produce an invoice whose lines do not add up to its own total.
The norm requires the other order: round first, then sum. That is one line in the mapping and the difference between an accepted and a rejected invoice.
How exact the arithmetic has to be is not the same across the family. BR-CO-10, BR-CO-13, BR-CO-14, BR-CO-15 and BR-CO-16 compare to the cent — one cent out and the rule reports. BR-CO-17 compares with “greater than minus one” and “less than plus one”, which leaves a whole currency unit of slack. Reproduced against the running validator on 17 September 2026: one cent too much in a category tax amount reports BR-CO-14 alone, a full euro reports BR-CO-14, BR-CO-17 and BR-S-09. Which rules fire together therefore tells you how far out you are.
Entries that exclude each other
The other half of the family checks pairs where exactly one may be set: the tax point as a date (BT-7) or as a code (BT-8), never both. Systems that fill both fields “to be safe” breach these for exactly that reason.
No amount of recalculating helps here; a decision in the mapping does. Which of the two is the source, and the other stays empty.
Four rules in the family require something nothing enforces. BR-CO-05 through BR-CO-08 ask that an allowance or charge reason in plain text and its code mean the same thing — and whether a code and a sentence mean the same thing is not something Schematron can decide. The compiled test reads true() in both syntaxes. The requirement is in the norm and is never reported: a wrongly chosen code passes the validator and is caught only by the person who reads the invoice.
All calculation and cross-field rules
With the official rule text. Rules with a written explanation carry their cause and fix directly beneath.
BR-CO-03
Value added tax point date (BT-7) and Value added tax point date code (BT-8) are mutually exclusive.
- What the rule requires
- The invoice may state the VAT point either as a date (BT-7) or as a code (BT-8), never as both. The two fields answer the same question: when the VAT becomes chargeable.
- Why it fires
- Mappings fill BT-7 from the delivery date and then set BT-8 to a default as well, because both fields exist in the target schema and nothing marks them as either/or.
- How to fix it
- Pick one. With a concrete date, send BT-7 (cbc:TaxPointDate in UBL, ram:TaxPointDate in CII) and omit BT-8; otherwise send only the code in BT-8 (cac:InvoicePeriod/cbc:DescriptionCode in UBL, ram:DueDateTypeCode in CII).
- What the validator checks
UBL(exists(cbc:TaxPointDate) and not(cac:InvoicePeriod/cbc:DescriptionCode)) or (not(cbc:TaxPointDate) and exists(cac:InvoicePeriod/cbc:DescriptionCode)) or (not(cbc:TaxPointDate) and not(cac:InvoicePeriod/cbc:DescriptionCode))CII((//ram:TaxPointDate) and not(//ram:DueDateTypeCode)) or (not (//ram:TaxPointDate) and (//ram:DueDateTypeCode)) or (not (//ram:TaxPointDate) and not (//ram:DueDateTypeCode))
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cbc:TaxPointDate>2026-08-26</cbc:TaxPointDate> <cac:InvoicePeriod> <cbc:DescriptionCode>3</cbc:DescriptionCode> </cac:InvoicePeriod>Fixed <cbc:TaxPointDate>2026-08-26</cbc:TaxPointDate>BR-CO-04
Each Invoice line (BG-25) shall be categorized with an Invoiced item VAT category code (BT-151).
- What the rule requires
- Every invoice line (BG-25) must carry a VAT category code (BT-151) — S for the standard rate, E for exempt, Z for zero-rated, AE for reverse charge, and so on.
- Why it fires
- The code sits in the header, where the tax breakdown is built anyway, and is forgotten at line level — particularly on invoices with a single rate, where repeating it looks redundant.
- How to fix it
- Set BT-151 on every line: cac:InvoiceLine/cac:Item/cac:ClassifiedTaxCategory/cbc:ID in UBL, ram:ApplicableTradeTax/ram:CategoryCode at line level in CII. Every line must name it, even where the rate is the same throughout.
- What the validator checks
UBL(cac:Item/cac:ClassifiedTaxCategory[cac:TaxScheme/(normalize-space(upper-case(cbc:ID))='VAT')]/cbc:ID)CII(ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax[upper-case(ram:TypeCode) = 'VAT']/ram:CategoryCode)
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:InvoiceLine> <cbc:ID>1</cbc:ID> <cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount> <cac:Item> <cbc:Name>Beratungsleistung</cbc:Name> </cac:Item> </cac:InvoiceLine>Fixed <cac:InvoiceLine> <cbc:ID>1</cbc:ID> <cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount> <cac:Item> <cbc:Name>Beratungsleistung</cbc:Name> <cac:ClassifiedTaxCategory> <cbc:ID>S</cbc:ID> <cbc:Percent>19</cbc:Percent> <cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme> </cac:ClassifiedTaxCategory> </cac:Item> </cac:InvoiceLine>BR-CO-05
Document level allowance reason code (BT-98) and Document level allowance reason (BT-97) shall indicate the same type of allowance.never fires
BR-CO-06
Document level charge reason code (BT-105) and Document level charge reason (BT-104) shall indicate the same type of charge.never fires
BR-CO-07
Invoice line allowance reason code (BT-140) and Invoice line allowance reason (BT-139) shall indicate the same type of allowance reason.never fires
BR-CO-08
Invoice line charge reason code (BT-145) and Invoice line charge reason (BT-144) shall indicate the same type of charge reason.never fires
BR-CO-09
The Seller VAT identifier (BT-31), the Seller tax representative VAT identifier (BT-63) and the Buyer VAT identifier (BT-48) shall have a prefix in accordance with ISO code ISO 3166-1 alpha-2 by which the country of issue may be identified. Nevertheless, Greece may use the prefix ‘EL’.
UBL 252 · CII 252 permitted values · UBL only: SS · CII only: AN
- What the rule requires
- The VAT identifiers of the seller (BT-31), the tax representative (BT-63) and the buyer (BT-48) must begin with the ISO 3166-1 alpha-2 country code. Greece is permitted to use “EL” instead.
- Why it fires
- Master data usually holds the number without the prefix, because the country lives in a field of its own. The mapping then copies the digits alone — “811234567” where “DE811234567” is required.
- How to fix it
- Prepend the country code and strip any spaces: cac:PartyTaxScheme/cbc:CompanyID with a cac:TaxScheme/cbc:ID of VAT in UBL, ram:SpecifiedTaxRegistration/ram:ID with schemeID="VA" in CII.
- What the validator checks
UBL( contains( ' 1A AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG EH EL ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF TG TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS XI YE YT ZA ZM ZW ',substring(cbc:CompanyID,1,2) ) )CIIcontains(' 1A AD AE AF AG AI AL AM AN AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BL BJ BM BN BO BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG EH EL ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR ST SV SX SY SZ TC TD TF TG TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS XI YE YT ZA ZM ZW ', concat(' ', substring(.,1,2), ' '))
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:PartyTaxScheme> <cbc:CompanyID>811234567</cbc:CompanyID> <cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme> </cac:PartyTaxScheme>Fixed <cac:PartyTaxScheme> <cbc:CompanyID>DE811234567</cbc:CompanyID> <cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme> </cac:PartyTaxScheme>BR-CO-10
Sum of Invoice line net amount (BT-106) = Σ Invoice line net amount (BT-131).
- What the rule requires
- The sum of invoice line net amounts (BT-106) must equal the sum of the individual line net amounts (BT-131) exactly.
- Why it fires
- This appears when line amounts are rounded individually and then added, while the total is computed from unrounded values. The two routes disagree.
- How to fix it
- Round each line amount to two decimal places and sum exactly those rounded values, not the raw ones.
- What the validator checks
UBL(xs:decimal(cbc:LineExtensionAmount) = xs:decimal(round(sum(//(cac:InvoiceLine|cac:CreditNoteLine)/xs:decimal(cbc:LineExtensionAmount)) * 10 * 10) div 100))CIIxs:decimal(ram:LineTotalAmount) = round(xs:decimal(sum(../../ram:IncludedSupplyChainTradeLineItem/ram:SpecifiedLineTradeSettlement/ram:SpecifiedTradeSettlementLineMonetarySummation/ram:LineTotalAmount)) * xs:decimal(100)) div xs:decimal(100)
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:LegalMonetaryTotal> <cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount> </cac:LegalMonetaryTotal> <cac:InvoiceLine> <cbc:ID>1</cbc:ID> <cbc:LineExtensionAmount currencyID="EUR">333.34</cbc:LineExtensionAmount> </cac:InvoiceLine> <cac:InvoiceLine> <cbc:ID>2</cbc:ID> <cbc:LineExtensionAmount currencyID="EUR">666.67</cbc:LineExtensionAmount> </cac:InvoiceLine>Fixed <cac:LegalMonetaryTotal> <cbc:LineExtensionAmount currencyID="EUR">1000.01</cbc:LineExtensionAmount> </cac:LegalMonetaryTotal> <cac:InvoiceLine> <cbc:ID>1</cbc:ID> <cbc:LineExtensionAmount currencyID="EUR">333.34</cbc:LineExtensionAmount> </cac:InvoiceLine> <cac:InvoiceLine> <cbc:ID>2</cbc:ID> <cbc:LineExtensionAmount currencyID="EUR">666.67</cbc:LineExtensionAmount> </cac:InvoiceLine>BR-CO-11
Sum of allowances on document level (BT-107) = Σ Document level allowance amount (BT-92).
- What the rule requires
- The sum of allowances at document level (BT-107) must equal the sum of the individual allowance amounts (BT-92) exactly.
- Why it fires
- The total is taken from your own invoice object while the individual allowances are written through a filter — an allowance of zero skipped, say, or a discount also folded into the line.
- How to fix it
- Build BT-107 from exactly the allowances the document contains: cbc:AllowanceTotalAmount inside cac:LegalMonetaryTotal as the sum of every cac:AllowanceCharge whose cbc:ChargeIndicator is false in UBL, ram:AllowanceTotalAmount in CII.
- What the validator checks
UBLxs:decimal(cbc:AllowanceTotalAmount) = (round(sum(../cac:AllowanceCharge[cbc:ChargeIndicator=false()]/xs:decimal(cbc:Amount)) * 10 * 10) div 100) or (not(cbc:AllowanceTotalAmount) and not(../cac:AllowanceCharge[cbc:ChargeIndicator=false()]))CII(not(/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge[ram:ChargeIndicator/udt:Indicator=false()])and not (ram:AllowanceTotalAmount)) or ram:AllowanceTotalAmount = (round(sum(/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge[ram:ChargeIndicator/udt:Indicator=false()]/ram:ActualAmount[1])* 10 * 10 ) div 100)
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:AllowanceCharge> <cbc:ChargeIndicator>false</cbc:ChargeIndicator> <cbc:Amount currencyID="EUR">30.00</cbc:Amount> </cac:AllowanceCharge> <cac:AllowanceCharge> <cbc:ChargeIndicator>false</cbc:ChargeIndicator> <cbc:Amount currencyID="EUR">30.00</cbc:Amount> </cac:AllowanceCharge> <cac:LegalMonetaryTotal> <cbc:AllowanceTotalAmount currencyID="EUR">50.00</cbc:AllowanceTotalAmount> </cac:LegalMonetaryTotal>Fixed <cac:AllowanceCharge> <cbc:ChargeIndicator>false</cbc:ChargeIndicator> <cbc:Amount currencyID="EUR">30.00</cbc:Amount> </cac:AllowanceCharge> <cac:AllowanceCharge> <cbc:ChargeIndicator>false</cbc:ChargeIndicator> <cbc:Amount currencyID="EUR">30.00</cbc:Amount> </cac:AllowanceCharge> <cac:LegalMonetaryTotal> <cbc:AllowanceTotalAmount currencyID="EUR">60.00</cbc:AllowanceTotalAmount> </cac:LegalMonetaryTotal>BR-CO-12
Sum of charges (BT-108) does not match
Sum of charges on document level (BT-108) = Σ Document level charge amount (BT-99).
- What the rule requires
- The sum of charges at document level (BT-108) must equal the sum of the individual charge amounts (BT-99) exactly — the counterpart to BR-CO-11.
- Why it fires
- As with BR-CO-11, on the charge side: BT-108 comes from your own invoice object while the individual charges are written through a filter. One common variant is a charge that ends up with cbc:ChargeIndicator false by mistake — it then counts as an allowance and drops out of the sum that still includes it.
- How to fix it
- Sum BT-108 from every cac:AllowanceCharge whose cbc:ChargeIndicator is true and write the result to cbc:ChargeTotalAmount in UBL, ram:ChargeTotalAmount in CII. A charge belongs either in the line or at document level, never in both.
- What the validator checks
UBLxs:decimal(cbc:ChargeTotalAmount) = (round(sum(../cac:AllowanceCharge[cbc:ChargeIndicator=true()]/xs:decimal(cbc:Amount)) * 10 * 10) div 100) or (not(cbc:ChargeTotalAmount) and not(../cac:AllowanceCharge[cbc:ChargeIndicator=true()]))CII(not(/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge[ram:ChargeIndicator/udt:Indicator=true()])and not (ram:ChargeTotalAmount)) or ram:ChargeTotalAmount = (round(sum(/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeAllowanceCharge[ram:ChargeIndicator/udt:Indicator=true()]/ram:ActualAmount[1])* 10 * 10 ) div 100)
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:AllowanceCharge> <cbc:ChargeIndicator>true</cbc:ChargeIndicator> <cbc:Amount currencyID="EUR">20.00</cbc:Amount> </cac:AllowanceCharge> <cac:LegalMonetaryTotal> <cbc:ChargeTotalAmount currencyID="EUR">35.00</cbc:ChargeTotalAmount> </cac:LegalMonetaryTotal>Fixed <cac:AllowanceCharge> <cbc:ChargeIndicator>true</cbc:ChargeIndicator> <cbc:Amount currencyID="EUR">20.00</cbc:Amount> </cac:AllowanceCharge> <cac:LegalMonetaryTotal> <cbc:ChargeTotalAmount currencyID="EUR">20.00</cbc:ChargeTotalAmount> </cac:LegalMonetaryTotal>BR-CO-13
Invoice total amount without VAT (BT-109) = Σ Invoice line net amount (BT-131) - Sum of allowances on document level (BT-107) + Sum of charges on document level (BT-108).
- What the rule requires
- The invoice total without VAT (BT-109) must equal the sum of all line net amounts (BT-131) minus document-level allowances (BT-107) plus document-level charges (BT-108).
- Why it fires
- Usually document-level allowances or charges are shown but forgotten when computing BT-109 — or the discount is already baked into the line prices and then subtracted a second time.
- How to fix it
- Compute BT-109 strictly as Σ BT-131 − BT-107 + BT-108 from exactly the values written in the document. A discount belongs either in the line or at document level — never in both.
- What the validator checks
UBL((cbc:ChargeTotalAmount) and (cbc:AllowanceTotalAmount) and (xs:decimal(cbc:TaxExclusiveAmount) = round((xs:decimal(cbc:LineExtensionAmount) + xs:decimal(cbc:ChargeTotalAmount) - xs:decimal(cbc:AllowanceTotalAmount)) * 10 * 10) div 100 )) or (not(cbc:ChargeTotalAmount) and (cbc:AllowanceTotalAmount) and (xs:decimal(cbc:TaxExclusiveAmount) = round((xs:decimal(cbc:LineExtensionAmount) - xs:decimal(cbc:AllowanceTotalAmount)) * 10 * 10 ) div 100)) or ((cbc:ChargeTotalAmount) and not(cbc:AllowanceTotalAmount) and (xs:decimal(cbc:TaxExclusiveAmount) = round((xs:decimal(cbc:LineExtensionAmount) + xs:decimal(cbc:ChargeTotalAmount)) * 10 * 10 ) div 100)) or (not(cbc:ChargeTotalAmount) and not(cbc:AllowanceTotalAmount) and (xs:decimal(cbc:TaxExclusiveAmount) = xs:decimal(cbc:LineExtensionAmount)))CII(xs:decimal(ram:TaxBasisTotalAmount[1]) = round((xs:decimal(ram:LineTotalAmount[1]) - xs:decimal(ram:AllowanceTotalAmount[1]) + xs:decimal(ram:ChargeTotalAmount[1])) * 10 * 10) div 100) or ((xs:decimal(ram:TaxBasisTotalAmount[1]) = round((xs:decimal(ram:LineTotalAmount[1]) - xs:decimal(ram:AllowanceTotalAmount[1])) * 10 * 10) div 100) and not(ram:ChargeTotalAmount)) or ((xs:decimal(ram:TaxBasisTotalAmount[1]) = round((xs:decimal(ram:LineTotalAmount[1]) + xs:decimal(ram:ChargeTotalAmount[1])) * 10 * 10) div 100) and not(ram:AllowanceTotalAmount)) or ((xs:decimal(ram:TaxBasisTotalAmount[1]) = round((xs:decimal(ram:LineTotalAmount[1])) * 10 * 10) div 100) and not(ram:ChargeTotalAmount) and not(ram:AllowanceTotalAmount))
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:LegalMonetaryTotal> <cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount> <cbc:TaxExclusiveAmount currencyID="EUR">1000.00</cbc:TaxExclusiveAmount> <cbc:AllowanceTotalAmount currencyID="EUR">50.00</cbc:AllowanceTotalAmount> <cbc:ChargeTotalAmount currencyID="EUR">20.00</cbc:ChargeTotalAmount> </cac:LegalMonetaryTotal>Fixed <cac:LegalMonetaryTotal> <cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount> <cbc:TaxExclusiveAmount currencyID="EUR">970.00</cbc:TaxExclusiveAmount> <cbc:AllowanceTotalAmount currencyID="EUR">50.00</cbc:AllowanceTotalAmount> <cbc:ChargeTotalAmount currencyID="EUR">20.00</cbc:ChargeTotalAmount> </cac:LegalMonetaryTotal>BR-CO-14
Invoice total VAT amount (BT-110) = Σ VAT category tax amount (BT-117).
- What the rule requires
- The invoice total VAT amount (BT-110) must equal the sum of the tax amounts of all VAT categories (BT-117).
- Why it fires
- Classic when BT-110 is computed from individually rounded per-line taxes while the category amounts come from the taxable bases — the two routes drift apart by rounding differences.
- How to fix it
- Compute BT-117 per category from its taxable base first, then form BT-110 as the exact sum of those category amounts — never as a separate, independent calculation.
- What the validator checks
UBL(xs:decimal(child::cbc:TaxAmount)= round((sum(cac:TaxSubtotal/xs:decimal(cbc:TaxAmount)) * 10 * 10)) div 100) or not(cac:TaxSubtotal)CII. = (round(sum(/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax/ram:CalculatedAmount)*10*10)div 100)
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:TaxTotal> <cbc:TaxAmount currencyID="EUR">190.00</cbc:TaxAmount> <cac:TaxSubtotal> <cbc:TaxableAmount currencyID="EUR">800.00</cbc:TaxableAmount> <cbc:TaxAmount currencyID="EUR">152.00</cbc:TaxAmount> </cac:TaxSubtotal> <cac:TaxSubtotal> <cbc:TaxableAmount currencyID="EUR">200.00</cbc:TaxableAmount> <cbc:TaxAmount currencyID="EUR">14.00</cbc:TaxAmount> </cac:TaxSubtotal> </cac:TaxTotal>Fixed <cac:TaxTotal> <cbc:TaxAmount currencyID="EUR">166.00</cbc:TaxAmount> <cac:TaxSubtotal> <cbc:TaxableAmount currencyID="EUR">800.00</cbc:TaxableAmount> <cbc:TaxAmount currencyID="EUR">152.00</cbc:TaxAmount> </cac:TaxSubtotal> <cac:TaxSubtotal> <cbc:TaxableAmount currencyID="EUR">200.00</cbc:TaxableAmount> <cbc:TaxAmount currencyID="EUR">14.00</cbc:TaxAmount> </cac:TaxSubtotal> </cac:TaxTotal>BR-CO-15
Invoice total amount with VAT (BT-112) = Invoice total amount without VAT (BT-109) + Invoice total VAT amount (BT-110).
- What the rule requires
- The invoice total with VAT (BT-112) must equal the total without VAT (BT-109) plus the total VAT amount (BT-110), exactly.
- Why it fires
- BT-112 is often copied from the internal system while BT-109 and BT-110 are recalculated — a single cent of rounding difference between the two worlds triggers the error.
- How to fix it
- Always derive BT-112 as BT-109 + BT-110 from the two document values instead of writing an independently calculated gross amount. Use decimal arithmetic, never float.
- What the validator checks
UBLevery $Currency in cbc:DocumentCurrencyCode satisfies (count(cac:TaxTotal/xs:decimal(cbc:TaxAmount[@currencyID=$Currency])) eq 1) and (cac:LegalMonetaryTotal/xs:decimal(cbc:TaxInclusiveAmount) = round( (cac:LegalMonetaryTotal/xs:decimal(cbc:TaxExclusiveAmount) + cac:TaxTotal/xs:decimal(cbc:TaxAmount[@currencyID=$Currency])) * 10 * 10) div 100)CIIevery $Currency in /rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceCurrencyCode satisfies ( ( count(/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TaxTotalAmount[@currencyID = $Currency]) = 1 and xs:decimal((/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:GrandTotalAmount)[1]) = round( ( xs:decimal((/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TaxBasisTotalAmount)[1]) + xs:decimal((/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TaxTotalAmount[@currencyID = $Currency])[1]) ) * 100 ) div 100 ) or ( xs:decimal((/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:GrandTotalAmount)[1]) = xs:decimal((/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TaxBasisTotalAmount)[1]) ) )
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:LegalMonetaryTotal> <cbc:TaxExclusiveAmount currencyID="EUR">1000.00</cbc:TaxExclusiveAmount> <cbc:TaxInclusiveAmount currencyID="EUR">1189.99</cbc:TaxInclusiveAmount> </cac:LegalMonetaryTotal>Fixed <cac:LegalMonetaryTotal> <cbc:TaxExclusiveAmount currencyID="EUR">1000.00</cbc:TaxExclusiveAmount> <cbc:TaxInclusiveAmount currencyID="EUR">1190.00</cbc:TaxInclusiveAmount> </cac:LegalMonetaryTotal>BR-CO-16
Amount due (BT-115) does not add up
Amount due for payment (BT-115) = Invoice total amount with VAT (BT-112) -Paid amount (BT-113) +Rounding amount (BT-114).
- What the rule requires
- The amount due for payment (BT-115) must add up: invoice total with VAT (BT-112) minus any amount already paid (BT-113) plus the rounding amount (BT-114).
- Why it fires
- The expression tests four cases, depending on whether a prepaid amount (BT-113) and a rounding amount (BT-114) are present. With neither, what remains is simply BT-115 = BT-112 — miss that and the payable amount was computed independently instead of derived. Only with a prepaid amount is anything rounded, and there the classic floating-point drift is the usual cause: one cent, invisible until validation.
- How to fix it
- Use decimal arithmetic throughout rather than float or double, and round only when writing the output to two decimal places. Also check whether a prepaid amount (BT-113) is set that your calculation ignores.
- What the validator checks
UBL(exists(cbc:PrepaidAmount) and not(exists(cbc:PayableRoundingAmount)) and (xs:decimal(cbc:PayableAmount) = (round((xs:decimal(cbc:TaxInclusiveAmount) - xs:decimal(cbc:PrepaidAmount)) * 10 * 10) div 100))) or (not(exists(cbc:PrepaidAmount)) and not(exists(cbc:PayableRoundingAmount)) and xs:decimal(cbc:PayableAmount) = xs:decimal(cbc:TaxInclusiveAmount)) or (exists(cbc:PrepaidAmount) and exists(cbc:PayableRoundingAmount) and ((round((xs:decimal(cbc:PayableAmount) - xs:decimal(cbc:PayableRoundingAmount)) * 10 * 10) div 100) = (round((xs:decimal(cbc:TaxInclusiveAmount) - xs:decimal(cbc:PrepaidAmount)) * 10 * 10) div 100))) or (not(exists(cbc:PrepaidAmount)) and exists(cbc:PayableRoundingAmount) and ((round((xs:decimal(cbc:PayableAmount) - xs:decimal(cbc:PayableRoundingAmount)) * 10 * 10) div 100) = xs:decimal(cbc:TaxInclusiveAmount)))CII(xs:decimal(ram:DuePayableAmount[1]) = xs:decimal(ram:GrandTotalAmount[1]) - xs:decimal(ram:TotalPrepaidAmount[1]) + xs:decimal(ram:RoundingAmount[1])) or ((xs:decimal(ram:DuePayableAmount[1]) = xs:decimal(ram:GrandTotalAmount[1]) + xs:decimal(ram:RoundingAmount[1])) and not(ram:TotalPrepaidAmount)) or ((xs:decimal(ram:DuePayableAmount[1]) = xs:decimal(ram:GrandTotalAmount[1]) - xs:decimal(ram:TotalPrepaidAmount[1])) and not(ram:RoundingAmount)) or ((xs:decimal(ram:DuePayableAmount[1]) = xs:decimal(ram:GrandTotalAmount[1])) and not(ram:TotalPrepaidAmount) and not(ram:RoundingAmount))
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:LegalMonetaryTotal> <cbc:TaxInclusiveAmount currencyID="EUR">1190.00</cbc:TaxInclusiveAmount> <cbc:PrepaidAmount currencyID="EUR">200.00</cbc:PrepaidAmount> <cbc:PayableAmount currencyID="EUR">1190.00</cbc:PayableAmount> </cac:LegalMonetaryTotal>Fixed <cac:LegalMonetaryTotal> <cbc:TaxInclusiveAmount currencyID="EUR">1190.00</cbc:TaxInclusiveAmount> <cbc:PrepaidAmount currencyID="EUR">200.00</cbc:PrepaidAmount> <cbc:PayableAmount currencyID="EUR">990.00</cbc:PayableAmount> </cac:LegalMonetaryTotal>BR-CO-17
Tax amount (BT-117) wrong for the rate
VAT category tax amount (BT-117) = VAT category taxable amount (BT-116) x (VAT category rate (BT-119) / 100), rounded to two decimals.±1 tolerance
- What the rule requires
- Per VAT category, the tax amount (BT-117) must equal the taxable amount (BT-116) times the rate (BT-119), within one whole currency unit. The rule text does not say so; the expression the validator evaluates does, comparing with "− 1 <" and "+ 1 >" rather than to the cent.
- Why it fires
- Because the tolerance is a whole euro, a rounding drift of a few cents is precisely not the cause — that reports BR-CO-14 and BR-CO-15, which check exactly. BR-CO-17 fires when something larger went wrong: the wrong rate applied to the base, one category computed at another's rate, or a line that landed in the wrong category of the breakdown. At a rate of 0 a different branch of the expression applies: there it only requires the tax amount to round to zero. For exempt, intra-community and reverse-charge categories BR-CO-17 is therefore a zero check rather than a multiplication.
- How to fix it
- Group lines by VAT category, sum their net amounts into the taxable base BT-116, and compute the tax once from it: BT-116 × BT-119 / 100. Check the rate against the category first — at this magnitude it is almost always the rate that is wrong, not the rounding. For standard-rated categories BR-S-09 reports alongside this rule.
- What the validator checks
UBL(round(cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/xs:decimal(cbc:Percent)) = 0 and (round(xs:decimal(cbc:TaxAmount)) = 0)) or (round(cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/xs:decimal(cbc:Percent)) != 0 and ((abs(xs:decimal(cbc:TaxAmount)) - 1 < round(abs(xs:decimal(cbc:TaxableAmount)) * (cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/xs:decimal(cbc:Percent) div 100) * 10 * 10) div 100 ) and (abs(xs:decimal(cbc:TaxAmount)) + 1 > round(abs(xs:decimal(cbc:TaxableAmount)) * (cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/xs:decimal(cbc:Percent) div 100) * 10 * 10) div 100 ))) or (not(exists(cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/xs:decimal(cbc:Percent))) and (round(xs:decimal(cbc:TaxAmount)) = 0))CII(round(.[normalize-space(upper-case(ram:TypeCode)) = 'VAT']/xs:decimal(ram:RateApplicablePercent)) = 0 and (round(xs:decimal(ram:CalculatedAmount)) = 0)) or (round(.[normalize-space(upper-case(ram:TypeCode)) = 'VAT']/xs:decimal(ram:RateApplicablePercent)) != 0 and ((abs(xs:decimal(ram:CalculatedAmount)) - 1 <= round(abs(xs:decimal(ram:BasisAmount)) * (.[normalize-space(upper-case(ram:TypeCode)) = 'VAT']/xs:decimal(ram:RateApplicablePercent) div 100) * 10 * 10) div 100 ) and (abs(xs:decimal(ram:CalculatedAmount)) + 1 >= round(abs(xs:decimal(ram:BasisAmount)) * (.[normalize-space(upper-case(ram:TypeCode)) = 'VAT']/xs:decimal(ram:RateApplicablePercent) div 100) * 10 * 10) div 100 ))) or (not(exists(.[normalize-space(upper-case(ram:TypeCode))='VAT']/xs:decimal(ram:RateApplicablePercent))) and (round(xs:decimal(ram:CalculatedAmount)) = 0))
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:TaxSubtotal> <cbc:TaxableAmount currencyID="EUR">1000.00</cbc:TaxableAmount> <cbc:TaxAmount currencyID="EUR">70.00</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>Fixed <cac:TaxSubtotal> <cbc:TaxableAmount currencyID="EUR">1000.00</cbc:TaxableAmount> <cbc:TaxAmount currencyID="EUR">190.00</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>BR-CO-18
An Invoice shall at least have one VAT breakdown group (BG-23).
- What the rule requires
- The invoice must contain at least one VAT breakdown group (BG-23) — one block per tax category, carrying the taxable amount, the tax amount, the category and the rate.
- Why it fires
- On an exempt invoice the block looks superfluous and gets left out. The rule wants it regardless: “no tax” is itself a statement, and it belongs in the breakdown.
- How to fix it
- Write one cac:TaxTotal/cac:TaxSubtotal in UBL, or one ram:ApplicableTradeTax in CII, per tax category — including E or Z, with a tax amount of zero and the exemption reason.
- What the validator checks
UBLexists(cac:TaxTotal/cac:TaxSubtotal)CII//rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:LegalMonetaryTotal> <cbc:TaxExclusiveAmount currencyID="EUR">1000.00</cbc:TaxExclusiveAmount> <cbc:TaxInclusiveAmount currencyID="EUR">1000.00</cbc:TaxInclusiveAmount> </cac:LegalMonetaryTotal>Fixed <cac:TaxTotal> <cbc:TaxAmount currencyID="EUR">0.00</cbc:TaxAmount> <cac:TaxSubtotal> <cbc:TaxableAmount currencyID="EUR">1000.00</cbc:TaxableAmount> <cbc:TaxAmount currencyID="EUR">0.00</cbc:TaxAmount> <cac:TaxCategory> <cbc:ID>E</cbc:ID> <cbc:Percent>0</cbc:Percent> <cbc:TaxExemptionReason>Kleinunternehmer nach § 19 UStG</cbc:TaxExemptionReason> <cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme> </cac:TaxCategory> </cac:TaxSubtotal> </cac:TaxTotal> <cac:LegalMonetaryTotal> <cbc:TaxExclusiveAmount currencyID="EUR">1000.00</cbc:TaxExclusiveAmount> <cbc:TaxInclusiveAmount currencyID="EUR">1000.00</cbc:TaxInclusiveAmount> </cac:LegalMonetaryTotal>BR-CO-19
If Invoicing period (BG-14) is used, the Invoicing period start date (BT-73) or the Invoicing period end date (BT-74) shall be filled, or both.
- What the rule requires
- If an invoicing period (BG-14) is sent, at least one date must be in it: the start (BT-73), the end (BT-74), or both. An empty period states nothing.
- Why it fires
- The group is created unconditionally in the mapping and populated afterwards. Where the source system holds neither date — a one-off service, say — an empty shell is left behind in the document.
- How to fix it
- Create the group only when at least one date exists: cac:InvoicePeriod with cbc:StartDate and/or cbc:EndDate in UBL, ram:BillingSpecifiedPeriod with ram:StartDateTime and/or ram:EndDateTime in CII.
- What the validator checks
UBLexists(cbc:StartDate) or exists(cbc:EndDate) or (exists(cbc:DescriptionCode) and not(exists(cbc:StartDate)) and not(exists(cbc:EndDate)))CII(ram:StartDateTime) or (ram:EndDateTime)
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cbc:BuyerReference>04011000-12345-03</cbc:BuyerReference> <cac:InvoicePeriod/>Fixed <cbc:BuyerReference>04011000-12345-03</cbc:BuyerReference> <cac:InvoicePeriod> <cbc:StartDate>2026-08-01</cbc:StartDate> <cbc:EndDate>2026-08-31</cbc:EndDate> </cac:InvoicePeriod>BR-CO-20
If Invoice line period (BG-26) is used, the Invoice line period start date (BT-134) or the Invoice line period end date (BT-135) shall be filled, or both.
- What the rule requires
- If a line period (BG-26) is sent on an invoice line, at least one date must be in it: the start (BT-134), the end (BT-135), or both. The line-level counterpart to BR-CO-19.
- Why it fires
- The same mistake as in the header, only more often: lines are produced in a loop and the period group is created for every one of them, though only some lines actually have a period.
- How to fix it
- Emit cac:InvoicePeriod inside cac:InvoiceLine only when a date exists — and in CII, ram:BillingSpecifiedPeriod inside the line, on the same condition.
- What the validator checks
UBLexists(cbc:StartDate) or exists(cbc:EndDate)CII(ram:StartDateTime) or (ram:EndDateTime)
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:InvoiceLine> <cbc:ID>1</cbc:ID> <cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount> <cac:InvoicePeriod/> </cac:InvoiceLine>Fixed <cac:InvoiceLine> <cbc:ID>1</cbc:ID> <cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount> <cac:InvoicePeriod> <cbc:StartDate>2026-08-01</cbc:StartDate> <cbc:EndDate>2026-08-31</cbc:EndDate> </cac:InvoicePeriod> </cac:InvoiceLine>BR-CO-21
Each Document level allowance (BG-20) shall contain a Document level allowance reason (BT-97) or a Document level allowance reason code (BT-98), or both.
- What the rule requires
- Every document-level allowance (BG-20) must be justified — in plain text (BT-97), as a code (BT-98), or both. An amount with no reason is something the recipient cannot check.
- Why it fires
- The amount lives in the invoice object and the reason only in the PDF rendering. What survives into the structured data is a deduction of 50.00 with no explanation attached to it.
- How to fix it
- Supply at least one of the two: cbc:AllowanceChargeReason or cbc:AllowanceChargeReasonCode inside cac:AllowanceCharge in UBL, ram:Reason or ram:ReasonCode inside ram:SpecifiedTradeAllowanceCharge in CII.
- What the validator checks
UBLexists(cbc:AllowanceChargeReason) or exists(cbc:AllowanceChargeReasonCode)CII(../ram:Reason) or (../ram:ReasonCode)
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:AllowanceCharge> <cbc:ChargeIndicator>false</cbc:ChargeIndicator> <cbc:Amount currencyID="EUR">50.00</cbc:Amount> </cac:AllowanceCharge>Fixed <cac:AllowanceCharge> <cbc:ChargeIndicator>false</cbc:ChargeIndicator> <cbc:AllowanceChargeReason>Mengenrabatt</cbc:AllowanceChargeReason> <cbc:Amount currencyID="EUR">50.00</cbc:Amount> </cac:AllowanceCharge>BR-CO-22
Each Document level charge (BG-21) shall contain a Document level charge reason (BT-104) or a Document level charge reason code (BT-105), or both.
- What the rule requires
- Every document-level charge (BG-21) must carry a reason — in plain text (BT-104), as a code (BT-105), or both.
- Why it fires
- Charges often appear late in the process, out of a shipping provider for instance, and are passed through as a bare amount. The recipient receives a surcharge nobody can attribute, and invoices with unexplained surcharges sit unpaid.
- How to fix it
- Add cbc:AllowanceChargeReason or cbc:AllowanceChargeReasonCode to the cac:AllowanceCharge whose cbc:ChargeIndicator is true in UBL, or ram:Reason or ram:ReasonCode to the ram:SpecifiedTradeAllowanceCharge in CII.
- What the validator checks
UBLexists(cbc:AllowanceChargeReason) or exists(cbc:AllowanceChargeReasonCode)CII(../ram:Reason) or (../ram:ReasonCode)
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:AllowanceCharge> <cbc:ChargeIndicator>true</cbc:ChargeIndicator> <cbc:Amount currencyID="EUR">20.00</cbc:Amount> </cac:AllowanceCharge>Fixed <cac:AllowanceCharge> <cbc:ChargeIndicator>true</cbc:ChargeIndicator> <cbc:AllowanceChargeReason>Frachtkosten</cbc:AllowanceChargeReason> <cbc:Amount currencyID="EUR">20.00</cbc:Amount> </cac:AllowanceCharge>BR-CO-23
Each Invoice line allowance (BG-27) shall contain an Invoice line allowance reason (BT-139) or an Invoice line allowance reason code (BT-140), or both.
- What the rule requires
- Every allowance on an invoice line (BG-27) must be justified — in plain text (BT-139), as a code (BT-140), or both.
- Why it fires
- Line discounts come out of a pricing engine that rarely carries more than a percentage. The reason frequently does not exist in the source system at all, so there is nothing available to map.
- How to fix it
- Carry the discount type through from pricing, or set a default reason that is actually true. It is written as cbc:AllowanceChargeReason or cbc:AllowanceChargeReasonCode inside the line's cac:AllowanceCharge in UBL, ram:Reason or ram:ReasonCode in CII.
- What the validator checks
UBLexists(cbc:AllowanceChargeReason) or exists(cbc:AllowanceChargeReasonCode)CII(../ram:Reason) or (../ram:ReasonCode)
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:InvoiceLine> <cbc:ID>1</cbc:ID> <cac:AllowanceCharge> <cbc:ChargeIndicator>false</cbc:ChargeIndicator> <cbc:Amount currencyID="EUR">10.00</cbc:Amount> </cac:AllowanceCharge> </cac:InvoiceLine>Fixed <cac:InvoiceLine> <cbc:ID>1</cbc:ID> <cac:AllowanceCharge> <cbc:ChargeIndicator>false</cbc:ChargeIndicator> <cbc:AllowanceChargeReason>Rabatt</cbc:AllowanceChargeReason> <cbc:Amount currencyID="EUR">10.00</cbc:Amount> </cac:AllowanceCharge> </cac:InvoiceLine>BR-CO-24
Each Invoice line charge (BG-28) shall contain an Invoice line charge reason (BT-144) or an Invoice line charge reason code (BT-145), or both.
- What the rule requires
- Every charge on an invoice line (BG-28) must carry a reason — in plain text (BT-144), as a code (BT-145), or both.
- Why it fires
- The rarest of the four cases, and therefore the one most likely to have no field in the mapping at all: the amount is passed through because the total needs it, and the reason is dropped.
- How to fix it
- Write cbc:AllowanceChargeReason or cbc:AllowanceChargeReasonCode into the line's cac:AllowanceCharge with cbc:ChargeIndicator true in UBL, or ram:Reason or ram:ReasonCode into the line's ram:SpecifiedTradeAllowanceCharge in CII.
- What the validator checks
UBLexists(cbc:AllowanceChargeReason) or exists(cbc:AllowanceChargeReasonCode)CII(../ram:Reason) or (../ram:ReasonCode)
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:InvoiceLine> <cbc:ID>1</cbc:ID> <cac:AllowanceCharge> <cbc:ChargeIndicator>true</cbc:ChargeIndicator> <cbc:Amount currencyID="EUR">25.00</cbc:Amount> </cac:AllowanceCharge> </cac:InvoiceLine>Fixed <cac:InvoiceLine> <cbc:ID>1</cbc:ID> <cac:AllowanceCharge> <cbc:ChargeIndicator>true</cbc:ChargeIndicator> <cbc:AllowanceChargeReason>Montage</cbc:AllowanceChargeReason> <cbc:Amount currencyID="EUR">25.00</cbc:Amount> </cac:AllowanceCharge> </cac:InvoiceLine>BR-CO-26
In order for the buyer to automatically identify a supplier, the Seller identifier (BT-29), the Seller legal registration identifier (BT-30) and/or the Seller VAT identifier (BT-31) shall be present.
- What the rule requires
- So the buyer can identify the supplier automatically, at least one seller identifier must be present: a seller identifier (BT-29), the legal registration identifier (BT-30), or the VAT identifier (BT-31).
- Why it fires
- The seller is sent as a name and an address, because that is enough for a human. It is not enough for an automatic match against the recipient's supplier master data — spellings change, identifiers do not.
- How to fix it
- Send at least one of them: cac:PartyIdentification/cbc:ID (BT-29), cac:PartyLegalEntity/cbc:CompanyID (BT-30) or cac:PartyTaxScheme/cbc:CompanyID (BT-31) in UBL; ram:SellerTradeParty/ram:ID, ram:SpecifiedLegalOrganization/ram:ID or ram:SpecifiedTaxRegistration/ram:ID in CII.
- What the validator checks
UBLexists(cac:Party/cac:PartyTaxScheme[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:CompanyID) or exists(cac:Party/cac:PartyIdentification/cbc:ID[not(@schemeID = 'SEPA')]) or exists(cac:Party/cac:PartyLegalEntity/cbc:CompanyID)CII(ram:ID) or (ram:GlobalID) or (ram:SpecifiedLegalOrganization/ram:ID) or (ram:SpecifiedTaxRegistration/ram:ID[@schemeID='VA'])
In the XML
A UBL fragment. The CII path is named above — same change, different element names.
Fails <cac:Party> <cac:PartyName> <cbc:Name>Muster GmbH</cbc:Name> </cac:PartyName> <cac:PostalAddress> <cbc:CityName>Berlin</cbc:CityName> </cac:PostalAddress> </cac:Party>Fixed <cac:Party> <cac:PartyName> <cbc:Name>Muster GmbH</cbc:Name> </cac:PartyName> <cac:PostalAddress> <cbc:CityName>Berlin</cbc:CityName> </cac:PostalAddress> <cac:PartyTaxScheme> <cbc:CompanyID>DE811234567</cbc:CompanyID> <cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme> </cac:PartyTaxScheme> </cac:Party>
NormAPI provides technical validation, not tax or legal advice.