Self-billed invoice
Self-billing under § 14(2) of the German VAT Act: the recipient of the supply issues the document, not the supplier. Two line items, two tax rates, no charges or allowances – and as the only difference from an ordinary commercial invoice, document type 389.
The basis is the official sample invoice
E10_01_Gutschrift from the Factur-X / ZUGFeRD
documentation (FeRD sample package, ZUGFeRD 2.5.0). The code below
produces exactly that document, field by field and in the same order.
Some links on this page lead into the in-depth documentation in the customer area and require a sign-in.
Two things called “Gutschrift”
The German word carries two entirely different meanings, and confusing them is the most common mistake in this area:
| Self-billing (389) | Credit note (381) | |
|---|---|---|
| FactoorSharp | InvoiceType.SelfBilledInvoice |
InvoiceType.CreditNote |
| Who issues it? | The recipient of the supply bills on the supplier's behalf. | The supplier itself. |
| Amounts | positive – the document looks like an invoice | positive; the document mirrors the invoice as a whole |
| Legal basis | § 14(2) German VAT Act (self-billed invoice) | correction of a receivable |
| This page | covers this case | see correction invoice (type 384) for the distinction |
In self-billing all amounts stay positive. Setting negative quantities here because “Gutschrift” sounds like a reversal produces a document that is wrong in business terms. Negative amounts belong in the correction invoice.
COMFORT profile instead of EXTENDED
Unlike the other examples on these pages, this document sits in the
Profile.Comfort profile – guideline ID
urn:cen.eu:en16931:2017, the pure EN 16931 profile without
extensions. That is not an oversight in the reference but the statement that
this business case gets by without EXTENDED fields.
Consequently, every field that the extended goods invoice demonstrates is absent here: document name (BT-X-2), test indicator, packaging details (BT-X-9) and the extended tax bases. Setting them would not be an error – the writer would silently discard them in the COMFORT profile.
1. Document header
Number, date and currency as in any invoice. The only line that makes this a
self-billed document is invoice.Type.
// BT-1 document number, BT-2 document date, BT-5 currency
FacturXInvoice invoice = FacturXInvoice.CreateInvoice(
invoiceNo: "471102",
invoiceDate: new DateTime(2026, 3, 5),
currency: CurrencyCodes.EUR);
// BT-3: 389 = self-billed invoice. The only difference from an ordinary commercial
// invoice (380) in the whole document – all amounts stay positive.
invoice.Type = InvoiceType.SelfBilledInvoice;
' BT-1 document number, BT-2 document date, BT-5 currency
Dim invoice As FacturXInvoice = FacturXInvoice.CreateInvoice(
invoiceNo:="471102",
invoiceDate:=New DateTime(2026, 3, 5),
currency:=CurrencyCodes.EUR)
' BT-3: 389 = self-billed invoice. The only difference from an ordinary commercial
' invoice (380) in the whole document – all amounts stay positive.
invoice.Type = InvoiceType.SelfBilledInvoice
2. Free-text notes
Four notes in ram:IncludedNote (BG-1). The first comes without a
subjectCode, two carry REG for regulatory information,
the last ACB for the format identification.
// Note without a subjectCode: plain free text, here the reference to the order.
invoice.AddNote("Rechnung gemäß Bestellung vom 01.03.2026.");
// REG = regulatory information. This example uses two of them: the legal
// representation and the commercial register number.
invoice.AddNote("Geschäftsführer: Hans Muster",
subjectCode: SubjectCodes.REG);
invoice.AddNote("Handelsregisternummer: H A 123",
subjectCode: SubjectCodes.REG);
// ACB = additional information, here the format identification.
invoice.AddNote("ZUGFeRD vers 2.5.0 EN16931 (Comfort)",
subjectCode: SubjectCodes.ACB);
' Note without a subjectCode: plain free text, here the reference to the order.
invoice.AddNote("Rechnung gemäß Bestellung vom 01.03.2026.")
' REG = regulatory information. This example uses two of them: the legal
' representation and the commercial register number.
invoice.AddNote("Geschäftsführer: Hans Muster",
subjectCode:=SubjectCodes.REG)
invoice.AddNote("Handelsregisternummer: H A 123",
subjectCode:=SubjectCodes.REG)
' ACB = additional information, here the format identification.
invoice.AddNote("ZUGFeRD vers 2.5.0 EN16931 (Comfort)",
subjectCode:=SubjectCodes.ACB)
3. Seller with two tax registrations
AddSellerTaxRegistration can be called more than once. Here the
national tax number (FC, BT-32) comes first, then the VAT ID
(VA, BT-31). The order in the XML follows the order of the calls.
// id → ram:ID (BT-29) internal supplier number
// globalID → ram:GlobalID (BT-29-0) with schemeID "0088" = GLN
invoice.SetSeller(
name: "Lieferant GmbH",
postcode: "80333",
city: "München",
street: "Lieferantenstraße 20",
country: CountryCodes.DE,
id: "549910",
globalID: new GlobalID(GlobalIDSchemeIdentifiers.GLN, "4000001123452"));
// BT-32 / BT-31: two tax registrations for the seller. The order in the XML
// follows the order of the calls here.
// FC = national tax number (BT-32)
// VA = VAT ID (BT-31)
invoice.AddSellerTaxRegistration("201/113/40209", TaxRegistrationSchemeID.FC);
invoice.AddSellerTaxRegistration("DE136695976", TaxRegistrationSchemeID.VA);
' id → ram:ID (BT-29) internal supplier number
' globalID → ram:GlobalID (BT-29-0) with schemeID "0088" = GLN
invoice.SetSeller(
name:="Lieferant GmbH",
postcode:="80333",
city:="München",
street:="Lieferantenstraße 20",
country:=CountryCodes.DE,
id:="549910",
globalID:=New GlobalID(GlobalIDSchemeIdentifiers.GLN, "4000001123452"))
' BT-32 / BT-31: two tax registrations for the seller. The order in the XML
' follows the order of the calls here.
' FC = national tax number (BT-32)
' VA = VAT ID (BT-31)
invoice.AddSellerTaxRegistration("201/113/40209", TaxRegistrationSchemeID.FC)
invoice.AddSellerTaxRegistration("DE136695976", TaxRegistrationSchemeID.VA)
4. Buyer – with a tax number of its own
This is where self-billing shows up in the data model: because the recipient of the supply issues the document, it carries a VAT ID as well (BT-48). On an ordinary commercial invoice that field usually stays empty.
// Only an internal customer number here, no GLN.
invoice.SetBuyer(
name: "Kunden AG Mitte",
postcode: "69876",
city: "Frankfurt",
street: "Kundenstraße 15",
country: CountryCodes.DE,
id: "GE2020211");
// BT-48: the buyer's VAT ID. Under self-billing the recipient of the supply issues the
// document, which is why its tax number appears here.
invoice.AddBuyerTaxRegistration("DE136695976", TaxRegistrationSchemeID.VA);
// BT-72: actual delivery date → ram:ActualDeliverySupplyChainEvent
invoice.ActualDeliveryDate = new DateTime(2026, 3, 3);
' Only an internal customer number here, no GLN.
invoice.SetBuyer(
name:="Kunden AG Mitte",
postcode:="69876",
city:="Frankfurt",
street:="Kundenstraße 15",
country:=CountryCodes.DE,
id:="GE2020211")
' BT-48: the buyer's VAT ID. Under self-billing the recipient of the supply issues the
' document, which is why its tax number appears here.
invoice.AddBuyerTaxRegistration("DE136695976", TaxRegistrationSchemeID.VA)
' BT-72: actual delivery date → ram:ActualDeliverySupplyChainEvent
invoice.ActualDeliveryDate = New DateTime(2026, 3, 3)
5. Document lines
Two line items (BG-25) without any item discount. Gross and net price are
therefore identical – the reference still writes both elements. Note that
both lines sit in the same tax category S and differ only in the
rate: 19 % for office supplies, 7 % for food.
// Line 1: 20 dividers at EUR 9.90 = EUR 198.00, 19 % VAT
invoice.AddTradeLineItem(
lineID: "1",
name: "Trennblätter A4",
netUnitPrice: 9.90m,
grossUnitPrice: 9.90m,
unitCode: QuantityCodes.H87, // H87 = piece
billedQuantity: 20m,
lineTotalAmount: 198.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S, // S = standard rate
taxPercent: 19m,
sellerAssignedID: "TB100A4",
id: new GlobalID(GlobalIDSchemeIdentifiers.EAN, "4012345001235"));
// Line 2: 50 yoghurts at EUR 5.50 = EUR 275.00, 7 % VAT.
// Food, hence the reduced rate – within the same tax category S.
invoice.AddTradeLineItem(
lineID: "2",
name: "Joghurt Banane",
netUnitPrice: 5.50m,
grossUnitPrice: 5.50m,
unitCode: QuantityCodes.H87,
billedQuantity: 50m,
lineTotalAmount: 275.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 7m,
sellerAssignedID: "ARNR2",
id: new GlobalID(GlobalIDSchemeIdentifiers.EAN, "4000050986428"));
' Line 1: 20 dividers at EUR 9.90 = EUR 198.00, 19 % VAT
invoice.AddTradeLineItem(
lineID:="1",
name:="Trennblätter A4",
netUnitPrice:=9.9D,
grossUnitPrice:=9.9D,
unitCode:=QuantityCodes.H87, ' H87 = piece
billedQuantity:=20D,
lineTotalAmount:=198D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S, ' S = standard rate
taxPercent:=19D,
sellerAssignedID:="TB100A4",
id:=New GlobalID(GlobalIDSchemeIdentifiers.EAN, "4012345001235"))
' Line 2: 50 yoghurts at EUR 5.50 = EUR 275.00, 7 % VAT.
' Food, hence the reduced rate – within the same tax category S.
invoice.AddTradeLineItem(
lineID:="2",
name:="Joghurt Banane",
netUnitPrice:=5.5D,
grossUnitPrice:=5.5D,
unitCode:=QuantityCodes.H87,
billedQuantity:=50D,
lineTotalAmount:=275D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=7D,
sellerAssignedID:="ARNR2",
id:=New GlobalID(GlobalIDSchemeIdentifiers.EAN, "4000050986428"))
6. Payment means
SEPA credit transfer (type code 58) and the payee's account. Other payment means and their fields are described under loading and saving.
// BG-16 ram:SpecifiedTradeSettlementPaymentMeans, type code 58 = SEPA credit transfer.
invoice.SetPaymentMeans(PaymentMeansTypeCodes.SEPACreditTransfer);
// BG-17: the payee's account. The BIC stays empty – for a SEPA credit transfer
// within Germany the IBAN is enough. AccountName (BT-85) is the account holder.
invoice.AddCreditorFinancialAccount(
iban: "DE89370400440532013000",
bic: null,
name: "Lieferant GmbH");
' BG-16 ram:SpecifiedTradeSettlementPaymentMeans, type code 58 = SEPA credit transfer.
invoice.SetPaymentMeans(PaymentMeansTypeCodes.SEPACreditTransfer)
' BG-17: the payee's account. The BIC stays empty – for a SEPA credit transfer
' within Germany the IBAN is enough. AccountName (BT-85) is the account holder.
invoice.AddCreditorFinancialAccount(
iban:="DE89370400440532013000",
bic:=Nothing,
name:="Lieferant GmbH")
7. VAT breakdown
Without document-level charges and allowances the basis amount is simply the sum
of the matching lines – no allowanceChargeBasisAmount, no
correction arithmetic:
| Field | 7 % (line 2) | 19 % (line 1) |
|---|---|---|
basisAmount |
275.00 | 198.00 |
taxAmount |
275.00 × 7 % = 19.25 | 198.00 × 19 % = 37.62 |
The column order is not a slip: the reference lists 7 % before
19 %, so not in the order of the lines. FactoorSharp emits the
tax groups in the order of the AddApplicableTradeTax calls and
does not sort them afterwards. If you want to match the reference file
character for character, order the calls accordingly.
// The order follows the reference – 7 % before 19 %, so NOT the order of the
// lines. The writer emits the groups in the order they were added and does not
// sort them afterwards.
invoice.AddApplicableTradeTax(
basisAmount: 275.00m,
percent: 7m,
taxAmount: 19.25m,
typeCode: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S);
invoice.AddApplicableTradeTax(
basisAmount: 198.00m,
percent: 19m,
taxAmount: 37.62m,
typeCode: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S);
' The order follows the reference – 7 % before 19 %, so NOT the order of the
' lines. The writer emits the groups in the order they were added and does not
' sort them afterwards.
invoice.AddApplicableTradeTax(
basisAmount:=275D,
percent:=7D,
taxAmount:=19.25D,
typeCode:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S)
invoice.AddApplicableTradeTax(
basisAmount:=198D,
percent:=19D,
taxAmount:=37.62D,
typeCode:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S)
8. Payment terms
A due date only, no early payment discount. That description is
null here is deliberate: the writer then does not emit
ram:Description at all rather than writing an empty element.
// BT-9: a due date only, no early payment discount and no free text. description stays
// null, so the writer does not emit ram:Description at all.
invoice.AddTradePaymentTerms(
description: null,
dueDate: new DateTime(2026, 4, 5));
' BT-9: a due date only, no early payment discount and no free text. description stays
' Nothing, so the writer does not emit ram:Description at all.
invoice.AddTradePaymentTerms(
description:=Nothing,
dueDate:=New DateTime(2026, 4, 5))
9. Document totals
| BT code | Parameter | Calculation |
|---|---|---|
| BT-106 | lineTotalAmount |
198.00 + 275.00 = 473.00 |
| BT-107 / BT-108 | allowanceTotalAmount / chargeTotalAmount |
0.00 – no charges or allowances |
| BT-109 | taxBasisAmount |
473.00 |
| BT-110 | taxTotalAmount |
19.25 + 37.62 = 56.87 |
| BT-112 | grandTotalAmount |
473.00 + 56.87 = 529.87 |
| BT-115 | duePayableAmount |
529.87 − 0.00 = 529.87 |
invoice.SetTotals(
lineTotalAmount: 473.00m, // BT-106
chargeTotalAmount: 0.00m, // BT-108
allowanceTotalAmount: 0.00m, // BT-107
taxBasisAmount: 473.00m, // BT-109
taxTotalAmount: 56.87m, // BT-110
grandTotalAmount: 529.87m, // BT-112
totalPrepaidAmount: 0.00m, // BT-113
duePayableAmount: 529.87m); // BT-115
invoice.SetTotals(
lineTotalAmount:=473D, ' BT-106
chargeTotalAmount:=0D, ' BT-108
allowanceTotalAmount:=0D, ' BT-107
taxBasisAmount:=473D, ' BT-109
taxTotalAmount:=56.87D, ' BT-110
grandTotalAmount:=529.87D, ' BT-112
totalPrepaidAmount:=0D, ' BT-113
duePayableAmount:=529.87D) ' BT-115
10. Saving
Here it is Profile.Comfort instead of Profile.Extended
– the only difference from the other examples at this point:
// Version25 + Profile.Comfort + CII produces the profile identifier
// "urn:cen.eu:en16931:2017" – the pure EN 16931 profile without extensions.
invoice.Save("E10_01_Gutschrift.xml",
ZUGFeRDVersion.Version25,
Profile.Comfort,
ZUGFeRDFormats.CII);
' Version25 + Profile.Comfort + CII produces the profile identifier
' "urn:cen.eu:en16931:2017" – the pure EN 16931 profile without extensions.
invoice.Save("E10_01_Gutschrift.xml",
ZUGFeRDVersion.Version25,
Profile.Comfort,
ZUGFeRDFormats.CII)
11. Cross-check
The generated file has to have the same tree structure as
E10_01_Gutschrift.xml from the FeRD sample package. Numbers are
compared numerically, so that 9.90 and 9.9000 count as
equal.
Whether the document also complies with the rule set is something you check either in the browser or straight from your code. For a human look at the document, the visualizer renders a readable version:
// A human-readable rendition – not a legal document and not a hybrid ZUGFeRD PDF.
// It is rendered from the object, not from the XML file.
InvoiceVisualizer.RenderPdf(invoice, "E10_01_Gutschrift.pdf");
InvoiceVisualizer.RenderHtml(invoice, "E10_01_Gutschrift.html");
' A human-readable rendition – not a legal document and not a hybrid ZUGFeRD PDF.
' It is rendered from the object, not from the XML file.
InvoiceVisualizer.RenderPdf(invoice, "E10_01_Gutschrift.pdf")
InvoiceVisualizer.RenderHtml(invoice, "E10_01_Gutschrift.html")
That is a plain rendition, not a legal document and not a hybrid ZUGFeRD PDF – details under visualization.