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

  1. Anlegen — du schickst Kunde und Positionen, der Server rechnet Summen und USt.
  2. Ausstellen — die Rechnungsnummer wird atomar vergeben, das XML gerendert und gegen die EN-16931-Geschäftsregeln validiert.
  3. 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

TypeScript
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.

TypeScript
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 berechnet

2) Ausstellen

TypeScript
await api(`/invoices/${id}/finalize`, { method: 'PATCH' });
// → Status "issued", Nummer vergeben, XRechnung-XML (CII) gerendert + validiert

3) XML abholen

TypeScript
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", nicht 1250.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-Key pro 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 →

Häufige Fragen

Wie erzeuge ich eine XRechnung mit Node.js?
Über drei HTTP-Calls gegen eine E-Rechnung API: POST zum Anlegen (der Server rechnet die Summen), PATCH zum Ausstellen (Nummer wird vergeben, XML gerendert und validiert) und GET zum Abholen des XRechnung-XML. In Node.js genügt dafür der eingebaute fetch (ab Node 18) — keine zusätzliche HTTP-Bibliothek nötig.
Warum nicht XRechnung-XML in Node selbst bauen?
Weil die EN 16931 und ihre Profile ein bewegliches Ziel sind: Codelisten, Geschäftsregeln und Profilversionen ändern sich jährlich. Eine API kapselt diese Pflege, sodass deine Node-Integration stabil bleibt, während der Dienst die Formatstände nachzieht. Du sparst dir die Schematron-Validierung und das PDF/A-3-Handling im eigenen Code.
Wie mache ich die Erzeugung idempotent?
Setze beim Anlegen einen Idempotency-Key-Header mit einem pro Rechnung stabilen Wert (z. B. crypto.randomUUID() oder deine eigene Beleg-ID). Ein wiederholter POST mit demselben Key ist dann ein Replay statt eines Duplikats — wichtig in Queue-Workern und bei Webhook-Retries. Beträge schickst du als DECIMAL-Strings, nie als JavaScript-Number.