This tutorial walks through adding XRechnung validation to a Node.js invoicing service using the xingen REST API. By the end you'll have a function that takes an invoice XML buffer, validates it, and either returns the converted ZUGFeRD output or throws a typed error with per-field details.
Prerequisites
- Node.js 18+
- A xingen API key (
xgn_live_…) — get one free - Your invoice generation code already producing UBL 2.1 or CII XML
Step 1 — Install a fetch client
Node 18+ ships with fetch built in, so no extra dependencies are needed. We'll wrap the xingen endpoints in a small module.
Step 2 — Create xingen.js
const BASE_URL = "https://api.xingen.de";
const API_KEY = process.env.XINGEN_API_KEY;
export async function validateInvoice(xmlBuffer) {
const res = await fetch(`${BASE_URL}/v1/validate`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/xml",
},
body: xmlBuffer,
});
if (!res.ok) {
const err = await res.json();
throw new Error(`xingen API error: ${err.error.message}`);
}
return res.json(); // { valid, format, errors, warnings }
}
export async function convertToZugferd(xmlBuffer) {
const res = await fetch(`${BASE_URL}/v1/convert`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/xml",
"X-Target-Format": "zugferd-2.3",
},
body: xmlBuffer,
});
if (!res.ok) {
const err = await res.json();
throw new Error(`xingen convert error: ${err.error.message}`);
}
return res.json(); // { job_id, status, output_format, output }
} Step 3 — Use it in your invoice flow
import { validateInvoice, convertToZugferd } from "./xingen.js";
export async function issueInvoice(invoiceXml) {
// 1. Validate before sending to customer
const { valid, errors } = await validateInvoice(invoiceXml);
if (!valid) {
// Surface structured errors to the UI
throw new InvoiceValidationError(errors);
// errors[0] → { code, severity, message, path, line, suggestion }
}
// 2. Convert to ZUGFeRD for customers that prefer PDF+XML
const { output } = await convertToZugferd(invoiceXml);
await sendToCustomer(output);
} Step 4 — Handle validation errors in your UI
Because xingen returns structured error objects with path and line, you can map them directly to form fields in your invoice editor:
errors.forEach(err => {
// err.path = "/Invoice/AccountingCustomerParty/Party/PartyLegalEntity/RegistrationName"
const fieldName = pathToFieldName(err.path);
setFieldError(fieldName, err.message);
// Show err.suggestion as a tooltip
}); Step 5 — Add a sandbox smoke test
Use a xgn_test_ key in CI to validate a fixture invoice on every deploy. Test keys return deterministic responses and don't consume quota:
// invoice.test.js
import { validateInvoice } from "./xingen.js";
import { readFileSync } from "fs";
test("fixture invoice is valid XRechnung", async () => {
const xml = readFileSync("fixtures/valid-xrechnung.xml");
const result = await validateInvoice(xml);
expect(result.valid).toBe(true);
expect(result.format).toBe("xrechnung-3.0");
}); That's the full integration. The Endpoints reference covers all available parameters, and the Error Reference has the complete list of validation codes you may need to handle.