xingen
Tutorial

Integrating XRechnung into Your ERP in an Afternoon

February 10, 2025  ·  6 min read

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

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.

← Back to Blog