Most e-invoicing integrations don't break on the happy path. They break in month two, when the first customer asks for a refund and someone on your team types "total": -119.00 into the invoice payload. Crediting is where the EN 16931 model is at its least intuitive: there are two legally valid ways to do it, the two main syntaxes (UBL and CII) express them completely differently, and getting the amount signs wrong produces documents that are either rejected — or worse, accepted and booked backwards.

This is a field guide to doing it correctly, with raw HTTP calls you can paste into a terminal.

Two sanctioned ways to credit

EN 16931 and Peppol BIS 3.0 give you exactly two mechanisms to reverse or reduce a previously issued invoice:

  1. A credit note — document type code 381 (UNTDID 1001), with positive amounts. The document type carries the crediting semantics.
  2. A negative invoice — document type code 380, with negative amounts. The sign carries the semantics.

The Peppol BIS Billing 3.0 specification is explicit that both are legal, and about who has to support what: invoice-generating systems may implement either option, while invoice-receiving systems have to support both. In the spec's own words, on a credit note "the function of crediting or debiting is controlled merely by the business document type (e.g. 380 or 381)", while on a negative invoice it "is controlled merely by the sign … of the amount concerned."

The corollary matters more than the rule: never mix the two. A type-381 document with negative amounts is semantically ambiguous — a receiving system may apply double negation and book your refund as a charge. If you remember one thing from this article, make it "381 means positive amounts, always."

UBL: a credit note is a different document

In UBL — the syntax used by Peppol BIS 3.0 and one of the two XRechnung syntaxes — a credit note is not an Invoice with a different type code. It is a separate document type with its own root element, its own XSD namespace, and renamed child elements throughout:

Invoice (380) CreditNote (381)
Root element <Invoice> <CreditNote>
Namespace …:xsd:Invoice-2 …:xsd:CreditNote-2
Type code element <cbc:InvoiceTypeCode> <cbc:CreditNoteTypeCode>
Line element <cac:InvoiceLine> <cac:CreditNoteLine>
Quantity element <cbc:InvoicedQuantity> <cbc:CreditedQuantity>

A minimal skeleton of the credit-note shape:

<CreditNote xmlns="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2"
            xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
            xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
  <cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID>
  <cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
  <cbc:ID>CN-2026-0042</cbc:ID>
  <cbc:IssueDate>2026-07-26</cbc:IssueDate>
  <cbc:CreditNoteTypeCode>381</cbc:CreditNoteTypeCode>
  <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
  <cac:BillingReference>
    <cac:InvoiceDocumentReference>
      <cbc:ID>INV-2026-0031</cbc:ID>   <!-- BT-25: the invoice you are crediting -->
    </cac:InvoiceDocumentReference>
  </cac:BillingReference>
  <!-- parties, tax totals, monetary totals: all POSITIVE -->
  <cac:CreditNoteLine>
    <cbc:ID>1</cbc:ID>
    <cbc:CreditedQuantity unitCode="C62">2</cbc:CreditedQuantity>
    <cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
    <!-- item, price -->
  </cac:CreditNoteLine>
</CreditNote>

The practical consequence: if your XML generation is string-templated around <Invoice>, you cannot "flip a flag" to emit credit notes. You need a second template — or a canonical internal model that serializes to either document. This is the classic symptom of working one level too low. (It's also why we model invoices as canonical JSON internally and treat UBL/CII as serializations — more on that below.)

CII: there is no CreditNote document

Cross-Industry Invoice — the syntax behind Factur-X/ZUGFeRD and the other XRechnung variant — takes the opposite approach. There is no separate credit-note document type at all. Every document is an rsm:CrossIndustryInvoice; crediting is expressed purely through the type code:

<rsm:ExchangedDocument>
  <ram:ID>CN-2026-0042</ram:ID>
  <ram:TypeCode>381</ram:TypeCode>
  <ram:IssueDateTime>…</ram:IssueDateTime>
</rsm:ExchangedDocument>

Same semantic model (EN 16931), same business terms (BT-3 is BT-3 in both), completely different serialization strategy. If you support both syntaxes, your credit-note logic forks at the serialization layer, not at the business layer — which is exactly where the fork belongs.

The XRechnung wrinkle: 381 vs 384

XRechnung (the German CIUS) restricts BT-3 to a whitelist of UNTDID 1001 codes: 326 (partial), 380 (commercial invoice), 381 (credit note), 384 (corrected invoice), 389 (self-billed), and 875/876/877 (construction-sector partial/final invoices). Anything else fails BR-CL-01.

The one that confuses teams is 384, "corrected invoice." It is not a credit note: a 384 replaces a prior invoice with corrected content, while a 381 offsets one with a credit. German business practice uses both — a Stornorechnung/Gutschrift flow is typically a 381 (or a negative 380) plus a fresh 380, whereas some ERP flows issue a single 384. If you're building for the German market, expose both paths and let the caller decide; don't silently map "correction" to 381.

One more German-language landmine: in everyday German, Gutschrift often means a self-billed invoice (the buyer invoices themselves — code 389), not a credit note. When a German customer asks your API for a "Gutschrift," ask which one they mean before you pick a type code.

The sign rules that actually reject documents

These are the rules a validator will throw at your crediting documents, in rough order of how often we see them:

  • BR-27 — the item net price (BT-146) must never be negative, on invoices or credit notes. On a negative invoice (380), the negativity goes into the quantity (InvoicedQuantity of −2) or the line amount — never into the unit price.
  • BR-CO-10 and friends — the totals arithmetic (sum of line amounts → tax exclusive → tax inclusive → payable) is enforced on credit notes exactly as on invoices, with the same two-decimal rounding expectations. A credit note built by multiplying an old invoice by −1 and then "abs()-ing" the totals is a reliable way to fail BR-CO-15 and BR-CO-16.
  • BR-55 — if you include a preceding invoice reference (BT-25, the BillingReference block above), it must actually contain the preceding invoice number. BT-25 is not mandatory in EN 16931 itself, but it is strongly recommended everywhere, some national rules require the linkage, and your customer's AP department will chase you for it regardless. Just always include it.
  • Mixed signs — Peppol adds rules that reject documents whose VAT breakdown signs contradict the document totals. If some lines are negative and some positive on a 380 (a "net credit" scenario), the document-level totals decide whether it's a debit or credit; keep the VAT category sums consistent with them.

Validate both shapes before they leave your system

Crediting is a document-layer problem, and this is where the document-layer/network-layer distinction earns its keep: your Peppol access point is a courier, not a proofreader. It will happily transport a malformed credit note to a receiver that rejects it three days later, refund cycle restarted. Validating at document-build time — in CI, or right before dispatch — is cheap insurance.

Here's a credit note going through validation as raw HTTP, no SDK required:

curl -s -X POST https://fakturwire.com/v1/validate \
  -H "Authorization: Bearer $FAKTURWIRE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profile": "peppol-bis-3",
    "xml": "<CreditNote xmlns=\"urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2\">…</CreditNote>"
  }'

A failing document comes back with machine-readable findings — rule ID, severity, and the XPath of the offending element:

{
  "valid": false,
  "findings": [
    {
      "rule": "BR-27",
      "severity": "error",
      "message": "The Item net price (BT-146) shall NOT be negative.",
      "path": "/CreditNote/cac:CreditNoteLine[1]/cac:Price/cbc:PriceAmount"
    }
  ],
  "counts": { "error": 1, "warning": 0 }
}

And if you'd rather never hand-build the UBL/CII split at all, send canonical JSON and let conversion produce the right document type for the target format:

curl -s -X POST https://fakturwire.com/v1/convert \
  -H "Authorization: Bearer $FAKTURWIRE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "target": "xrechnung-ubl",
    "invoice": { "typeCode": "381", "number": "CN-2026-0042",
                 "precedingInvoiceReference": [ { "number": "INV-2026-0031" } ], "...": "..." }
  }'

A document that fails validation during conversion returns HTTP 422 with the same findings list — and a 422 is never billed.

Which shape should you pick?

If you're the sender and free to choose: issue a 381 credit note with positive amounts. It's the least ambiguous shape, it matches what accounting systems expect, and it round-trips cleanly through both UBL and CII. Reach for negative invoices (380) when your internal model genuinely produces mixed-sign documents, or when a specific receiver's ERP demands them. If you're the receiver, you don't get a vote — Peppol obliges you to accept both, so test your intake against both shapes before go-live.

Either way: put the preceding invoice number in BT-25, keep unit prices positive, and validate before you transmit.

Try it now: drop a credit note into the free validator — no account needed — or sign up for 50 free credits and wire the /v1/validate call into your CI.


Last verified: 2026-07-26. Sources: Peppol BIS Billing 3.0 specification (credit notes vs negative invoices, receiver obligations, rounding); Peppol UBL CreditNote syntax; e-invoice.be on credit-note amount signs; XRechnung BT-3 allowed type codes.