An EU VAT invoice is not just a payment receipt. Under Article 226 of the EU VAT Directive (2006/112/EC), a VAT invoice must contain 16 specific items of information. Missing any one of them makes the invoice legally non-compliant — which can cause the buyer to lose their right to deduct input VAT and expose you to questions from tax authorities. For SaaS companies issuing hundreds or thousands of invoices per month, getting this right in code is essential.
The 16 mandatory invoice fields under Article 226
| # | Field | Notes |
|---|---|---|
| 1 | Date of issue | Date the invoice was generated |
| 2 | Sequential invoice number | Must be unique, must be sequential within a series |
| 3 | Seller's VAT identification number | Your VAT number, prefixed with country code |
| 4 | Buyer's VAT number (B2B only) | Required for reverse charge; not required for B2C |
| 5 | Seller's full name and address | Legal entity name, not just the trading name |
| 6 | Buyer's full name and address | |
| 7 | Description of goods/services supplied | e.g. 'TaxID API subscription — June 2026' |
| 8 | Date of supply (if different from invoice date) | Or the date of advance payment |
| 9 | Taxable amount per VAT rate | Net amount before VAT, grouped by rate |
| 10 | VAT rate applied | e.g. 0%, 21%, 25% |
| 11 | VAT amount payable | In the currency of the invoice |
| 12 | Unit price (excluding VAT) | Per unit if applicable |
| 13 | Any discounts or rebates | If not included in unit price |
| 14 | Reference to applicable VAT exemption or zero-rate provision | e.g. 'Art. 196 EU VAT Directive' for reverse charge |
| 15 | For new means of transport: technical details | Rarely applicable to digital services |
| 16 | For margin scheme: reference to the scheme applied | Not applicable to standard digital service supply |
Warning
Missing even one of the 16 mandatory fields makes the invoice non-compliant under EU law. Buyers receiving a non-compliant invoice may lose their right to deduct the input VAT — which can create disputes and chargebacks even when the underlying supply was correct.
Fields that change based on customer type
Fields 4, 10, 11, and 14 all change based on whether the customer is a B2B reverse-charge customer or a B2C customer. Getting these right is where most SaaS invoice implementations go wrong.
| Field | B2B reverse charge | B2C (OSS) |
|---|---|---|
| Buyer's VAT number (field 4) | Required — the VIES-validated number | Not required (consumer has none) |
| VAT rate (field 10) | 0% | Customer's country rate (e.g. 20% UK, 19% DE) |
| VAT amount (field 11) | €0.00 | Calculated: net × rate |
| Legal reference (field 14) | "Reverse charge — Art. 196 EU VAT Directive" | Not required |
| 'Reverse charge' notation | Required — explicit text on invoice face | Not applicable |
Generating compliant invoices in code
interface InvoiceData {
invoiceNumber: string;
issueDate: Date;
supplyDate: Date;
seller: { name: string; address: string; vatNumber: string };
buyer: { name: string; address: string; vatNumber?: string };
lineItems: Array<{ description: string; unitPrice: number; quantity: number }>;
vatTreatment: 'reverse_charge' | 'oss_b2c' | 'domestic';
vatRate: number; // 0 for reverse_charge, customer country rate for oss_b2c
currency: string;
}
function generateInvoiceFields(data: InvoiceData) {
const netAmount = data.lineItems.reduce(
(sum, item) => sum + item.unitPrice * item.quantity, 0
);
const vatAmount = netAmount * data.vatRate;
return {
// Article 226 required fields
invoiceNumber: data.invoiceNumber, // field 2
issueDate: data.issueDate.toISOString(), // field 1
supplyDate: data.supplyDate.toISOString(), // field 8
sellerName: data.seller.name, // field 5
sellerAddress: data.seller.address, // field 5
sellerVatNumber: data.seller.vatNumber, // field 3
buyerName: data.buyer.name, // field 6
buyerAddress: data.buyer.address, // field 6
buyerVatNumber: data.buyer.vatNumber ?? null, // field 4 (B2B only)
lineItems: data.lineItems, // field 7, 12
netAmount: netAmount, // field 9
vatRate: data.vatRate, // field 10
vatAmount: vatAmount, // field 11
currency: data.currency,
// Reverse-charge specific
reverseChargeNotation: data.vatTreatment === 'reverse_charge'
? 'VAT: Reverse charge — Article 196, Council Directive 2006/112/EC'
: null, // field 14
};
}// Invoice HTML generation (simplified)
function renderInvoiceLegalSection(invoice: ReturnType<typeof generateInvoiceFields>) {
if (invoice.reverseChargeNotation) {
return (
<div className="invoice-legal">
<p>VAT: 0.00 {invoice.currency}</p>
<p><strong>Reverse charge</strong> — Article 196, EU VAT Directive 2006/112/EC.</p>
<p>The VAT on this supply is to be accounted for by the recipient.</p>
<p>Buyer VAT number: {invoice.buyerVatNumber}</p>
</div>
);
}
return (
<div className="invoice-legal">
<p>VAT ({(invoice.vatRate * 100).toFixed(0)}%): {invoice.vatAmount.toFixed(2)} {invoice.currency}</p>
</div>
);
}What Stripe, Paddle, and Chargebee handle automatically
Billing platforms vary significantly in how much Article 226 compliance they handle for you. Stripe Tax generates invoices with most required fields but requires you to supply the customer's VAT number and set tax_exempt correctly — it does not validate the number via VIES. Paddle (as a Merchant of Record) handles the entire VAT compliance chain including VIES validation, invoice generation, and OSS remittance. Chargebee generates compliant invoice PDFs when you supply the tax configuration and customer data correctly, but does not validate VAT numbers.
Storing the validation record with the invoice
The link between the VIES validation and the invoice is critical for audit purposes. Store the request_id from the validation call alongside the invoice ID, and include the validation timestamp. If you are ever audited, this record demonstrates that you verified the customer's taxable status before applying zero-rate treatment.
CREATE TABLE invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
invoice_number TEXT NOT NULL UNIQUE,
customer_id UUID NOT NULL REFERENCES customers(id),
issue_date TIMESTAMPTZ NOT NULL,
net_amount NUMERIC(12,2) NOT NULL,
vat_rate NUMERIC(5,4) NOT NULL,
vat_amount NUMERIC(12,2) NOT NULL,
vat_treatment TEXT NOT NULL, -- 'reverse_charge' | 'oss_b2c' | 'domestic'
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE invoice_vat_validations (
invoice_id UUID NOT NULL REFERENCES invoices(id),
vat_number TEXT NOT NULL,
country_code TEXT NOT NULL,
validation_status TEXT NOT NULL, -- 'active' | 'service_unavailable'
request_id TEXT NOT NULL, -- TaxID API request_id for audit trail
validated_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (invoice_id)
);Related resources
Start validating EU VAT numbers
Free plan — 100 validations/month. No credit card required.