AttestwireRule referenceBR-DE-17

BR-DE-17 XRechnung narrows the invoice type codes to eight

noun · XRechnung · warning · BT-3

an invoice type XRechnung has never heard of.

Your invoice has been flagged, not refused, because its document type code is not one of the eight that German public-sector invoices are expected to use. KoSIT, the German office that publishes the official checker, treats this as a warning, so the invoice is accepted; we suggest sticking to the eight codes anyway. In the standard the type code is BT-3, and BR-DE-17 is the XRechnung rule that narrows the list.

Diese Regel auf Deutsch: BR-DE-17 — auf Deutsch erklärt.

Business term
BT-3 — Invoice type code
Severity
warning
Applies to
xrechnung-ubl, xrechnung-cii

The fix

Use invoiceTypeCode: "380" unless you have a specific reason not to; it is the ordinary commercial invoice and the right answer most of the time. "384" is a corrected invoice, and if you use it, also supply precedingInvoices naming the document you are correcting (BR-DE-26 asks for that). "326" is a partial invoice, "389" self-billing, "381" a credit note.

All eight generate: "381" and the rest of the UNTDID 1001 credit-note list emit a ubl:CreditNote in UBL, and a CrossIndustryInvoice with ram:TypeCode 381 in CII.

What the rule requires

XRechnung, Germany’s version of the European standard, asks that the invoice type code (BT-3) be one of eight codes from the list UNTDID 1001: 326 partial invoice, 380 commercial invoice, 381 credit note, 384 corrected invoice, 389 self-billed invoice, and 875/876/877 for the construction-industry partial invoices.

It is one rule across both document types. KoSIT’s schematron tests the invoice code and the credit note code against the same eight-code list, so a credit note is held to the same set.

The German text says sollen, “should”, and KoSIT flags the rule as a warning rather than an error. The document is accepted, and the wider UNTDID list stays legal under the European standard.

Failing and passing

Warns — a code outside the eight
validateInput({
  profile: "xrechnung-ubl",
  // ...
  invoiceTypeCode: "82",     // "metered services invoice" <- BR-DE-17
});
// -> { valid: true, warnings: [ { rule: "BR-DE-17", severity: "warning", ... } ] }
// note: valid stays TRUE. This one is in result.warnings.
Passes — a corrected invoice, with the reference it needs
validateInput({
  profile: "xrechnung-ubl",
  // ...
  invoiceTypeCode: "384",
  precedingInvoices: [{ invoiceNumber: "2026-000141",
                        issueDate: "2026-07-09" }],
});
// -> { valid: true, errors: [] }

Why it exists

Type codes are how a receiving system decides what to do with a document before it reads any of it: book it, net it against an earlier invoice, route it for construction-industry withholding. The eight codes are the ones the German rule set names for public-sector invoices.

KoSIT only warns on a code outside them, so the portal accepts the invoice; we suggest sticking to the eight so that the receiving side has a code it expects.

What the library returns

The full error returned by @attestwire/en16931. It includes the rule ID, affected field and suggested correction. Developers can use this object in their application:

TeachingError
{
  "rule": "BR-DE-17",
  "field": "BT-3",
  "severity": "warning",
  "message": "XRechnung asks that the invoice type code (BT-3) be one of 326, 380, 381, 384, 389, 875, 876 and 877 from UNTDID 1001, but \"999\" was supplied. The rule is one rule across both document types: KoSIT's schematron tests `cbc:InvoiceTypeCode = $supportedInvAndCNTypeCodes or cbc:CreditNoteTypeCode = $supportedInvAndCNTypeCodes`, against one eight-code list, so a credit note is held to exactly the same eight. The German text says \"sollen\", and KoSIT flags the rule \"warning\" rather than \"fatal\": the document is accepted, and the wider UNTDID list stays legal under core EN 16931. Treat it as a portal-compatibility finding rather than a rejection — a receiving system that only branches on the eight listed codes will not know what to do with yours, which is a slower and more expensive failure than a bounce.",
  "fix": "Use \"380\" for a commercial invoice, \"384\" for a corrected invoice (and then also supply precedingInvoices with the number of the invoice you are correcting, BR-DE-26), \"326\" for a partial invoice, \"389\" for self-billing, \"381\" for a credit note. All eight generate: \"381\" and the rest of the UNTDID 1001 credit-note list emit a ubl:CreditNote in the UBL syntax and a CrossIndustryInvoice with ram:TypeCode 381 in CII.",
  "example": "\"invoiceTypeCode\": \"380\"",
  "xpath": "/ubl:Invoice/cbc:InvoiceTypeCode",
  "docsUrl": "https://attestwire.com/rules/BR-DE-17"
}

xpath is always a UBL path. On a CII invoice, look for the matching CII field instead.

How our check differs

Severity is the thing to get right here. We report warning, matching KoSIT, so result.valid stays true and the finding is in result.warnings rather than result.errors. If your integration only reads errors, you will never see this. Read the warnings.

Three lists, and passing one says nothing about the others. BR-CL-01 admits the full UNTDID 1001 invoice-and-credit-note set. PEPPOL-EN16931-P0100 admits twenty-six codes, scoped to the Peppol billing process.

BR-DE-17 admits eight. A code can be fine under one and reported under another, and which ones you are held to depends on the profile you send.

See also: BR-CL-01 — the full UNTDID 1001 code list; PEPPOL-EN16931-P0100 — Peppol’s own, different narrowing; BR-DE-26 — a corrected invoice must reference what it corrects.