ZUGFeRD E-Rechnung per API erstellen: Anleitung mit RechnungsAPI
Seit dem 01.01.2025 ist die E-Rechnung nach § 14 UStG der Standard für Umsätze zwischen inländischen Unternehmen: Empfangen können muss sie seitdem jedes Unternehmen, für die Ausstellung gelten Übergangsfristen bis Ende 2026 bzw. 2027 (§ 27 Abs. 38 UStG). ZUGFeRD ist dafür besonders attraktiv, weil es eine gewöhnliche PDF-Rechnung mit strukturierten XML-Daten kombiniert – Empfänger ohne E-Rechnungs-Software öffnen einfach das PDF.
Dieses Tutorial zeigt Schritt für Schritt, wie Sie mit RechnungsAPI ZUGFeRD-Rechnungen erstellen, ohne sich mit XML und PDF/A-3 zu beschäftigen.
Was ist ZUGFeRD?
ZUGFeRD ist das hybride E-Rechnungsformat des Forums elektronische Rechnung Deutschland (FeRD): eine PDF/A-3-Datei, in die eine XML-Rechnung im UN/CEFACT-CII-Format eingebettet ist. Es ist technisch identisch mit dem französischen Standard Factur-X. Das BMF-Schreiben zur E-Rechnungspflicht erkennt ZUGFeRD ab Version 2.0.1 als zulässiges Format an – mit Ausnahme der Profile MINIMUM und BASIC WL. Die technischen Details erklärt unser Artikel ZUGFeRD für Entwickler.
Das Problem mit nativem CII/XML
Wer ZUGFeRD selbst implementiert, muss CII-XML erzeugen und es normkonform in ein PDF/A-3 einbetten. Schon eine einfache Adresse ist in CII tief verschachtelt:
<ram:PostalTradeAddress>
<ram:LineOne>Musterstraße 55a</ram:LineOne>
<ram:CityName>Hamburg</ram:CityName>
<ram:PostcodeCode>12345</ram:PostcodeCode>
<ram:CountryID>DE</ram:CountryID>
</ram:PostalTradeAddress>
Dieselbe Adresse in RechnungsAPI:
{
"address": {
"line1": "Musterstraße 55a",
"postalCode": "12345",
"city": "Hamburg",
"country": "DE"
}
}
Eine einzelne Rechnungsposition in CII:
<ram:IncludedSupplyChainTradeLineItem>
<ram:AssociatedDocumentLineDocument>
<ram:LineID>1</ram:LineID>
</ram:AssociatedDocumentLineDocument>
<ram:SpecifiedTradeProduct>
<ram:Name>Beratung und Konzeption</ram:Name>
<ram:Description>Analyse und Erarbeitung eines Konzepts</ram:Description>
</ram:SpecifiedTradeProduct>
<ram:SpecifiedLineTradeAgreement>
<ram:NetPriceProductTradePrice>
<ram:ChargeAmount>95.00</ram:ChargeAmount>
</ram:NetPriceProductTradePrice>
</ram:SpecifiedLineTradeAgreement>
<ram:SpecifiedLineTradeDelivery>
<ram:BilledQuantity unitCode="HUR">3</ram:BilledQuantity>
</ram:SpecifiedLineTradeDelivery>
<ram:SpecifiedLineTradeSettlement>
<ram:ApplicableTradeTax>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:CategoryCode>S</ram:CategoryCode>
<ram:RateApplicablePercent>19.00</ram:RateApplicablePercent>
</ram:ApplicableTradeTax>
<ram:SpecifiedTradeSettlementLineMonetarySummation>
<ram:LineTotalAmount>285.00</ram:LineTotalAmount>
</ram:SpecifiedTradeSettlementLineMonetarySummation>
</ram:SpecifiedLineTradeSettlement>
</ram:IncludedSupplyChainTradeLineItem>
Die gleiche Information im RechnungsAPI-Format:
{
"unitPrice": { "value": "95.00", "currency": "EUR" },
"quantity": { "value": "3", "unit": "HUR" },
"item": {
"name": "Beratung und Konzeption",
"description": "Analyse und Erarbeitung eines Konzepts",
"vat": { "code": "S", "rate": "19.00" }
}
}
Dazu kommt bei ZUGFeRD die PDF-Seite: PDF/A-3-Konformität, eingebettete Schriften und XMP-Metadaten. RechnungsAPI erzeugt beides – das CII-XML und das konforme PDF – aus einem einzigen JSON-Request.
Setup und Installation
Die Beispiele verwenden das offizielle Node.js-SDK. Da RechnungsAPI eine REST-API mit OpenAPI-Spezifikation ist, funktionieren die gleichen Aufrufe aus jeder Programmiersprache, auch mit generierten OpenAPI-Clients.
npm install @rechnungs-api/client
Client-Initialisierung
Den API-Key erhalten Sie nach der Registrierung auf rechnungs-api.de. Test-Keys beginnen mit test_, Produktions-Keys mit prod_.
import { Client } from "@rechnungs-api/client";
export const client = new Client({
apiKey: process.env.RECHNUNGS_API_KEY || "YOUR_API_KEY",
});
Schritt für Schritt zur ZUGFeRD-Rechnung
Schritt 1: Absender definieren
import type { SenderParty } from "@rechnungs-api/client";
const sender: SenderParty = {
name: "Muster GmbH",
address: {
line1: "Musterstraße 55a",
postalCode: "12345",
city: "Hamburg",
country: "DE",
},
electronicAddress: {
scheme: "EM",
value: "info@example.com",
},
contact: {
name: "Max Mustermann",
email: "max.mustermann@example.com",
phone: "+49123456789",
},
vatId: "DE123456789", // USt-IdNr.
taxId: "12/345/67890", // Steuernummer
owner: "Max Mustermann", // Inhaber bzw. Geschäftsführung
registration: {
office: "Amtsgericht Hamburg",
number: "HRB 123456",
},
};
vatId: Die Umsatzsteuer-Identifikationsnummer (in Deutschland „DE“ + 9 Ziffern).taxId: Alternativ oder zusätzlich die Steuernummer des Finanzamts.registration/owner: Handelsregistereintrag und vertretungsberechtigte Person – sie erscheinen auf der PDF-Darstellung.
Schritt 2: Empfänger definieren
import type { RecipientParty } from "@rechnungs-api/client";
const recipient: RecipientParty = {
name: "Beispiel UG (haftungsbeschränkt)",
address: {
line1: "Musterweg 3c",
postalCode: "54321",
city: "Berlin",
country: "DE",
},
electronicAddress: {
scheme: "EM",
value: "buchhaltung@beispiel-ug.de",
},
contact: {
name: "Erika Musterfrau",
email: "erika.musterfrau@beispiel-ug.de",
phone: "+49987654321",
},
vatId: "DE987654321",
};
Die electronicAddress folgt der EAS-Codeliste; im B2B-Verkehr ist EM (E-Mail-Adresse) üblich. Behörden werden über ihre Leitweg-ID mit dem Code 0204 adressiert.
Schritt 3: Zahlungsinformationen festlegen
import type { PaymentInformation } from "@rechnungs-api/client";
const payment: PaymentInformation = {
means: [{
code: "58", // SEPA-Überweisung
bankAccount: {
bankName: "Muster Bank",
iban: "DE12345678901234567890",
bic: "MUSTDE12XXX",
},
}],
terms: "Zahlbar innerhalb von 30 Tagen netto",
};
Die Zahlungsart folgt der Codeliste UNTDID 4461. Die wichtigsten Codes:
58– SEPA-Überweisung (SEPA credit transfer)30– Überweisung allgemein (Credit transfer)59– SEPA-Lastschrift (SEPA direct debit)48– Kartenzahlung (Bank card)10– Barzahlung (In cash)
Schritt 4: Rechnungspositionen erstellen
import type { DocumentLineCreateRequest } from "@rechnungs-api/client";
const lines: DocumentLineCreateRequest[] = [
{
unitPrice: { value: "95.00", currency: "EUR" },
item: {
name: "Beratung und Konzeption",
description: "Analyse und Erarbeitung eines Konzepts für die Digitalisierung",
vat: { code: "S", rate: "19.00" },
},
quantity: { value: "3", unit: "HUR" }, // HUR = Stunde
},
{
unitPrice: { value: "500.00", currency: "EUR" },
item: {
name: "Logo-Design",
description: "Entwicklung eines Corporate Designs inkl. Logo",
vat: { code: "S", rate: "19.00" },
},
quantity: { value: "1", unit: "H87" }, // H87 = Stück
},
];
Einheiten-Codes stammen aus UN/ECE Recommendation N°20 und N°21, zum Beispiel:
H87= Stück,C62= Einheit „Eins“HUR= Stunde,DAY= TagKGM= Kilogramm,MTR= Meter,MTK= Quadratmeter,LTR= Liter
Umsatzsteuer-Kategorien folgen der Codeliste UNTDID 5305:
S= regulär besteuert – gilt für jeden Steuersatz über 0 %, also sowohl 19 % als auch den ermäßigten Satz von 7 % (jeweils mit passendemrate)Z= Steuersatz 0 %E= steuerbefreit; ein Befreiungsgrund ist erforderlich, z. B. bei Kleinunternehmern nach § 19 UStGAE= Reverse Charge (§ 13b UStG)K= innergemeinschaftliche Lieferung,G= Ausfuhr,O= nicht steuerbar
Schritt 5: ZUGFeRD-Konfiguration
import type { EInvoiceConfiguration } from "@rechnungs-api/client";
const eInvoiceConfig: EInvoiceConfiguration = {
type: "zugferd",
profile: "en-16931", // 'minimum' | 'basic-wl' | 'basic' | 'en-16931' | 'extended' | 'xrechnung'
};
Die verfügbaren ZUGFeRD-Profile:
minimumundbasic-wl: nur Buchungshilfen – in Deutschland nicht als E-Rechnung nach § 14 UStG anerkanntbasic: grundlegende Daten inklusive Rechnungspositionenen-16931: das vollständige Datenmodell der EN 16931 – für die meisten B2B-Fälle die richtige Wahlextended: erweiterte Felder für komplexe Szenarienxrechnung: erfüllt zusätzlich die deutschen XRechnung-Geschäftsregeln; maximale Kompatibilität, auch für öffentliche Auftraggeber
Empfehlung: en-16931 für den B2B-Standardfall oder xrechnung für maximale Kompatibilität.
Schritt 6: Die vollständige Rechnung zusammenbauen
import type { DocumentCreateRequest } from "@rechnungs-api/client";
const documentRequest: DocumentCreateRequest = {
type: "invoice",
locale: "de-DE",
number: "RE-2026-001",
issueDate: "2026-06-15",
dueDate: "2026-07-15",
sender,
recipient,
payment,
lines,
// ZUGFeRD aktivieren
eInvoice: eInvoiceConfig,
// Käuferreferenz, z. B. Auftrags- oder Projektnummer
buyerReference: "PROJEKT-2026-ZF",
// Texte für die PDF-Darstellung
preTableText: "Sehr geehrte Damen und Herren,\n\nfür die erbrachten Leistungen stellen wir Ihnen folgende Positionen in Rechnung:",
postTableText: "Vielen Dank für Ihren Auftrag! Bitte überweisen Sie den Betrag bis zum Fälligkeitsdatum auf das angegebene Konto.",
// Optional: Gestaltung der PDF
theme: {
fontFamily: "Open Sans",
},
};
Hinweis zum Profil xrechnung: Es verlangt eine Käuferreferenz (buyerReference). Falls keine sinnvolle Referenz existiert, kann in der Praxis ein Platzhalter wie "00" verwendet werden.
Schritt 7: ZUGFeRD-Rechnung erstellen und herunterladen
Das Ergebnis ist eine PDF-Datei, die die strukturierten XML-Daten unsichtbar enthält:
import * as fs from "node:fs/promises";
const document = await client.createDocument(documentRequest);
console.log(`ZUGFeRD-Rechnung erstellt! ID: ${document.id}`);
// ZUGFeRD-PDF herunterladen (enthält das eingebettete XML)
const pdf = await client.readDocument(document.id, "pdf");
await fs.writeFile("zugferd-rechnung.pdf", Buffer.from(pdf));
Die resultierende PDF sieht so aus:

Vollständiges Arbeitsbeispiel
import * as fs from "node:fs/promises";
import type {
DocumentCreateRequest,
RecipientParty,
SenderParty,
} from "@rechnungs-api/client";
import { Client } from "@rechnungs-api/client";
const client = new Client({
apiKey: process.env.RECHNUNGS_API_KEY || "YOUR_API_KEY",
});
const sender: SenderParty = {
name: "Muster GmbH",
address: {
line1: "Musterstraße 55a",
postalCode: "12345",
city: "Hamburg",
country: "DE",
},
electronicAddress: {
scheme: "EM",
value: "info@example.com",
},
contact: {
name: "Max Mustermann",
email: "max.mustermann@example.com",
phone: "+49123456789",
},
vatId: "DE123456789",
taxId: "12/345/67890",
owner: "Max Mustermann",
registration: {
office: "Amtsgericht Hamburg",
number: "HRB 123456",
},
};
const recipient: RecipientParty = {
name: "Beispiel UG (haftungsbeschränkt)",
address: {
line1: "Musterweg 3c",
postalCode: "54321",
city: "Berlin",
country: "DE",
},
electronicAddress: {
scheme: "EM",
value: "buchhaltung@beispiel-ug.de",
},
contact: {
name: "Erika Musterfrau",
email: "erika.musterfrau@beispiel-ug.de",
phone: "+49987654321",
},
vatId: "DE987654321",
};
const documentRequest: DocumentCreateRequest = {
type: "invoice",
locale: "de-DE",
number: "RE-2026-001",
issueDate: "2026-06-15",
dueDate: "2026-07-15",
sender,
recipient,
buyerReference: "PROJEKT-2026-ZF",
preTableText: "Sehr geehrte Damen und Herren,\n\nfür die erbrachten Leistungen stellen wir Ihnen folgende Positionen in Rechnung:",
postTableText: "Vielen Dank für Ihren Auftrag! Bitte überweisen Sie den Betrag bis zum Fälligkeitsdatum auf das angegebene Konto.",
lines: [
{
unitPrice: { value: "95.00", currency: "EUR" },
item: {
name: "Beratung und Konzeption",
description: "Analyse und Erarbeitung eines Konzepts für die Digitalisierung",
vat: { code: "S", rate: "19.00" },
},
quantity: { value: "3", unit: "HUR" },
},
{
unitPrice: { value: "500.00", currency: "EUR" },
item: {
name: "Logo-Design",
description: "Entwicklung eines Corporate Designs inkl. Logo",
vat: { code: "S", rate: "19.00" },
},
quantity: { value: "1", unit: "H87" },
},
],
payment: {
means: [{
code: "58",
bankAccount: {
bankName: "Muster Bank",
iban: "DE12345678901234567890",
bic: "MUSTDE12XXX",
},
}],
terms: "Zahlbar innerhalb von 30 Tagen netto",
},
eInvoice: {
type: "zugferd",
profile: "en-16931",
},
theme: {
fontFamily: "Open Sans",
},
};
async function main() {
const document = await client.createDocument(documentRequest);
console.log(`ZUGFeRD-Rechnung erstellt! ID: ${document.id}`);
const pdf = await client.readDocument(document.id, "pdf");
await fs.writeFile("zugferd-rechnung.pdf", Buffer.from(pdf));
console.log(`Nummer: ${document.number}`);
console.log(`Nettobetrag: ${document.netAmount.value} ${document.netAmount.currency}`);
console.log(`Bruttobetrag: ${document.grossAmount.value} ${document.grossAmount.currency}`);
}
main();
Erweiterte Features
Lieferadresse und Leistungszeitraum
const documentRequest: DocumentCreateRequest = {
// ... andere Felder
delivery: {
address: {
line1: "Lieferstraße 10",
postalCode: "98765",
city: "München",
country: "DE",
},
},
deliveryPeriod: {
startDate: "2026-06-01",
endDate: "2026-06-30",
vatDate: "35",
},
};
Das optionale Feld vatDate gibt an, wann die Umsatzsteuer entsteht. Zulässig ist die von der EN 16931 vorgegebene Teilmenge der Codeliste UNTDID 2005: 3 (Rechnungsdatum), 35 (Lieferdatum) oder 432 (Zahlung).
Logo einbetten
import * as fs from "node:fs/promises";
const logoBuffer = await fs.readFile("./company-logo.png");
const logoBase64 = logoBuffer.toString("base64");
const documentRequest: DocumentCreateRequest = {
// ... andere Felder
theme: {
logo: `data:image/png;base64,${logoBase64}`,
fontFamily: "Open Sans",
},
};
Error Handling
import { ApiError } from "@rechnungs-api/client";
try {
const document = await client.createDocument(documentRequest);
// ...
} catch (error) {
if (error instanceof ApiError) {
console.error(`API Fehler [${error.status}]:`, error.body);
}
throw error;
}
Fazit
Mit RechnungsAPI erstellen Sie ZUGFeRD-Rechnungen über eine JSON-API – das CII-XML, die PDF/A-3-Einbettung und die Validierung übernimmt der Dienst. Sie liefern die Rechnungsdaten, die API liefert eine fertige hybride E-Rechnung.
Alle Details und weitere Funktionen finden Sie in der Dokumentation, wo sich die API auch direkt ausprobieren lässt.