Eine gültige XRechnung aus Node.js erzeugst du am robustesten über eine E-Rechnung API statt durch eigenes XML-Bauen: Du sendest strukturierte Rechnungsdaten per fetch, der Dienst rechnet die Summen, vergibt die Nummer und rendert das EN-16931-konforme XML. Der Flow sind drei Calls — anlegen, ausstellen, XML abholen — die sich mit dem eingebauten fetch in wenigen Zeilen TypeScript abbilden lassen.
XRechnung ist reines XML nach EN 16931. In Node.js kannst du dieses
XML selbst zusammenbauen — und Codelisten, Geschäftsregeln und Profilstände jährlich
nachpflegen — oder die Erzeugung an eine E-Rechnung API
delegieren. Dieser Beitrag zeigt den API-Weg mit dem eingebauten fetch (ab Node 18),
ganz ohne zusätzliche HTTP-Bibliothek.
Der Flow: drei Calls
- Anlegen — du schickst Kunde und Positionen, der Server rechnet Summen und USt.
- Ausstellen — die Rechnungsnummer wird atomar vergeben, das XML gerendert und gegen die EN-16931-Geschäftsregeln validiert.
- XML abholen — das reine XRechnung-XML (CII).
Base-URL und Test-Key (sk_test_…) kommen aus der Umgebung, damit sich der Code später
ohne Änderung auf produktiv umstellen lässt — beides bekommst du mit dem Sandbox-Zugang.
Ein kleiner Client
const BASE_URL = process.env.FORTLAUF_API_URL!;
const API_KEY = process.env.FORTLAUF_API_KEY!;
async function api(path: string, init: RequestInit = {}) {
const res = await fetch(`${BASE_URL}${path}`, {
...init,
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
...init.headers
}
});
if (!res.ok) throw new Error(`${init.method ?? 'GET'} ${path} → ${res.status}`);
return res;
}1) Rechnung anlegen
Beträge und Mengen gehen als DECIMAL-Strings über die Leitung — nie als
JavaScript-number, sonst verfälscht die Float-Arithmetik die Summen. Der
Idempotency-Key macht einen wiederholten Anlege-Call zum Replay statt zum Duplikat.
import { randomUUID } from 'node:crypto';
const draft = await api('/invoices', {
method: 'POST',
headers: { 'Idempotency-Key': randomUUID() },
body: JSON.stringify({
currency: 'EUR',
documentTarget: 'b2g',
buyer: {
name: 'Hochschule Kaltenwies — Dezernat IV Beschaffung',
addressLine1: 'Campusallee 20',
postalCode: '88529',
city: 'Kaltenwies',
countryCode: 'DE'
},
buyerElectronicAddress: '<LEITWEG-ID>',
buyerReference: '<LEITWEG-ID>', // Leitweg-ID (BT-10), nur B2G
invoiceTypeCode: '380',
lineItems: [
{
description: 'Lizenz Prüfungsverwaltung, Staffel B (2026)',
quantity: '1',
unitCode: 'C62',
unitPriceNet: '5900.00',
taxRatePercent: '19'
}
]
})
});
const { id } = await draft.json(); // Status "draft", Summen serverseitig berechnet2) Ausstellen
await api(`/invoices/${id}/finalize`, { method: 'PATCH' });
// → Status "issued", Nummer vergeben, XRechnung-XML (CII) gerendert + validiert3) XML abholen
import { writeFile } from 'node:fs/promises';
const xml = await (await api(`/invoices/${id}/xml`)).text();
await writeFile(`rechnung-${id}.xml`, xml); // reines XRechnung-XML (CII)Brauchst du zusätzlich einen menschenlesbaren Sichtbeleg, holst du dasselbe Dokument als
hybrides ZUGFeRD/Factur-X über GET /invoices/${id}/pdf — siehe
ZUGFeRD/Factur-X per API erzeugen.
Worauf es in Node ankommt
- Beträge als Strings.
"1250.00", nicht1250.0. Der Server rechnet in ganzzahligen Minor-Units und rundet einmal — das vermeidet die Summen-Inkonsistenzen, die der Validator sonst anmahnt (KoSIT-Validierung erklärt). - Idempotenz im Worker. In BullMQ-, SQS- oder Webhook-Workern kann ein Job doppelt
laufen; ein stabiler
Idempotency-Keypro Rechnung hält den Nummernkreis lückenlos. - Kein Format selbst pflegen. CII/UBL, Codelisten, Schematron und der Übergang zu XRechnung 4.0 liegen hinter der API — deine TypeScript-Typen für “Kunde, Positionen, Steuersätze” bleiben stabil.
Der gezeigte Code ist illustrativ: Die vollständige Dokumentation steht dir in der API-Referenz zur Verfügung. Base-URL und Test-Key bekommst du mit dem Sandbox-Zugang im Entwickler-Bereich. Ob du die E-Rechnung selbst baust oder einbindest, ordnet E-Rechnung selbst bauen oder eine API einbinden? ein.
Selbst ausprobieren? Der Zugang ist kostenlos und ohne Karte — Produkt-Einstieg für Entwickler →
Oder den eigenen Abrechnungsfluss konkret durchsprechen: 30 Min, technisch, mit dem Gründer →