Automation MCP Server Features Blog Pricing Contact
Integration ZUGFeRD Format

Create and Validate ZUGFeRD and XRechnung Invoices in Node.js: Complete Guide

Both German e-invoice formats from one JavaScript integration: ZUGFeRD hybrid PDFs for B2B and XRechnung pure XML for the public sector. Creating from JSON, validating against EN 16931 and the KoSIT rules, reading incoming invoices (AI parsing for old-school PDFs, deterministic extraction for structured ones, attachments as a ZIP), and rendering previews, all with native fetch and FormData. The same code runs on Node.js 18+, Vercel, Cloudflare Workers, Deno, and Bun, and an honest look at node-zugferd and the npm ecosystem is included.

German e-invoicing reached JavaScript teams the moment the B2B mandate landed. Since January 2025 every German business must be able to receive structured e-invoices, issuance phases in from 2027, and an ordinary PDF no longer counts. For B2B that means ZUGFeRD, the hybrid PDF with embedded XML. Invoice a federal ministry, a state agency, or a municipality, and the portals demand XRechnung instead: pure XML, no visual layer, and a routing field (the Leitweg-ID) with its own rejection rule. If your stack is Node.js, Next.js, or anything else that speaks JavaScript, you need both formats, and you need them from code.

This guide covers both from a single integration, deliberately: ZUGFeRD and XRechnung share the EN 16931 semantic model, the API accepts the same invoice JSON for both, and the only differences in the code below are the endpoint and two extra fields. Every example uses native fetch and FormData, no axios, no SDK, nothing to install, which has a second payoff: the identical code runs on Node.js 18+, Vercel and Netlify functions, Cloudflare Workers, Deno, and Bun. The serverless section makes that concrete.

JavaScript is also the one ecosystem outside Java where open-source ZUGFeRD tooling is actively growing, with node-zugferd leading. This guide is honest about what those packages do well (generation) and where they stop (validation, PDF/A conformance, unstructured input), and shows how the ZUGFeRD API and XRechnung API cover the full lifecycle. If your market is France rather than Germany, the same hybrid format travels as Factur-X, covered in the companion guide to Factur-X in Node.js.

ZUGFeRD and XRechnung: the two German formats

ZUGFeRD carries the B2B mandate. Receiving e-invoices has been mandatory for all German businesses since January 2025; issuing becomes mandatory from January 2027 for companies above EUR 800k turnover and from January 2028 for everyone else. ZUGFeRD is a PDF/A-3 the recipient can read with embedded CII XML their software can parse. Profiles EN 16931 and above qualify as e-invoices under the mandate; MINIMUM and BASIC WL are booking aids and do not. The current version is ZUGFeRD 2.3, technically identical to France's Factur-X 1.0.7, and the embedded attachment is named factur-x.xml in both brandings.

XRechnung carries the B2G side. Invoices to German federal authorities have had to be XRechnung since November 2020, with state and municipal buyers following. It is Germany's national CIUS of EN 16931, maintained by KoSIT, with version 3.0 in force since February 2024. It adds the German BR-DE business rules, makes several optional EN 16931 fields mandatory, and is submitted through portals such as ZRE and OZG-RE that enforce the KoSIT rule set on acceptance. Because it has no PDF layer, a rendering step for human review becomes part of a serious pipeline.

GoBD archiving frames both. German bookkeeping rules require invoices to be archived as received, immutable and machine-evaluable. ZUGFeRD is convenient there (one file is both the visual record and the data); XRechnung archives as the XML itself.

Underneath sits the engineering that makes "just attach XML to a PDF" deceptively deep: PDF/A-3 containers with embedded fonts and XMP profile declarations, CII XML satisfying roughly 200 EN 16931 business rules, German overlays on top, and Schematron to prove all of it. The technical overview of ZUGFeRD automation walks the layers in detail.


Which format does your invoice need?

Your customerFormatDeliverableEndpoint
German business (B2B)ZUGFeRD EN 16931Hybrid PDF/A-3/v1/create/zugferd
Federal or state authority (B2G)XRechnung 3.0Pure XML + Leitweg-ID/v1/create/xrechnung
French businessFactur-XHybrid PDF/A-3/v1/create/facturx
Peppol network recipientPeppol BIS 3.0UBL XML/v1/create/ubl

Every row consumes the same invoice JSON, so supporting all four in JavaScript is a ternary over endpoints, not four integrations. This guide works the first two rows; Factur-X and Peppol UBL have Node.js guides of their own.


The npm library landscape

Unlike most ecosystems, JavaScript has a live open-source scene around ZUGFeRD, and it deserves a fair look before the API route:

node-zugferd is the most complete package: TypeScript, profile-aware, and able to generate ZUGFeRD/Factur-X XML and embed it into PDF/A files. It is the closest thing npm has to Java's Mustangproject, which makes its boundaries the important part of the story.

Lighter generators such as zugferd-generator produce the invoice XML and leave PDF handling to you, and a few invoice-generator packages bundle XRechnung XML output alongside PDF rendering.

Now the boundaries, which are structural rather than a matter of maturity:

Validation is not in the box. The official EN 16931 and KoSIT rule sets are Schematron compiled to XSLT 2.0. JavaScript's mainstream XML tooling stops at XSLT 1.0; Saxon-JS can execute XSLT 2.0+, but nobody ships a maintained npm package that wires the EN 16931 and XRechnung pipelines onto it and tracks the yearly KoSIT releases. Generating an invoice locally tells you nothing about whether a portal will accept it.

PDF/A-3 conformance of arbitrary input is the sharp edge. Embedding XML into a PDF you generated for the purpose is one thing. Taking the PDF your existing billing system already produces (Puppeteer, PDFKit, a template engine) and making it genuinely PDF/A-3 conformant, fonts embedded, color spaces declared, is not solved on npm, and defects surface as recipient-side rejections.

Unstructured input is out of scope entirely. No library reads an ordinary supplier PDF, a scan, or a photo into structured invoice data. That is document understanding, not XML plumbing.

The honest conclusion: generation-side npm tooling is real and improving, but generation is the easy fifth of the problem. Validation, PDF/A conformance, XRechnung, intake, and the yearly KoSIT release cycle stay yours, and every specification update means waiting for a package release, bumping the dependency, and redeploying. The rest of this guide takes the route where none of that exists: the full lifecycle behind one API, with the current rule sets always live server-side.


Client setup with fetch

Node.js 18+ ships fetch, FormData, and Blob globally, so there is nothing to install and no HTTP client to choose. Examples use ES modules with top-level await.

Sign up for a free InvoiceXML account and you will receive 100 free credits, no credit card required. Keep the key in an environment variable or your secrets manager, never in source, and never in client-side code:

const BASE_URL = "https://api.invoicexml.com";
const API_KEY = process.env.INVOICEXML_API_KEY;

if (!API_KEY) throw new Error("INVOICEXML_API_KEY is not set");

Create ZUGFeRD invoices in Node.js

The payload is the invoice as JSON. Send net prices, quantities, and rates; totals and the VAT breakdown are computed server-side, and the document is validated against the full EN 16931 rule set before it comes back. The sample mixes Germany's 19% standard and 7% reduced rates so the breakdown does real work:

import { writeFile } from "node:fs/promises";

const invoice = {
  invoice: {
    invoiceNumber: "RE-2026-1105",
    issueDate: "2026-07-28",
    currency: "EUR",
    seller: {
      name: "Spreewerk Solutions GmbH",
      vatIdentifier: "DE811234567",
      postalAddress: {
        line1: "Köpenicker Str. 154",
        city: "Berlin",
        postCode: "10997",
        country: "DE"
      }
    },
    buyer: {
      name: "Main Handel GmbH",
      postalAddress: {
        line1: "Zeil 65",
        city: "Frankfurt am Main",
        postCode: "60313",
        country: "DE"
      }
    },
    paymentDetails: {
      paymentAccountIdentifier: "DE02120300000000202051"
    },
    lines: [
      {
        quantity: 60,
        item: { name: "SaaS-Lizenz Team, Juli 2026" },
        priceDetails: { netPrice: 12.50 },
        vatInformation: { rate: 19 }
      },
      {
        quantity: 10,
        item: { name: "Benutzerhandbuch (Druckausgabe)" },
        priceDetails: { netPrice: 18.00 },
        vatInformation: { rate: 7 }
      }
    ]
  }
};

const response = await fetch(`${BASE_URL}/v1/create/zugferd`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify(invoice)
});

if (!response.ok) throw new Error(await response.text());

await writeFile("rechnung-zugferd.pdf", Buffer.from(await response.arrayBuffer()));

The response is a finished ZUGFeRD 2.3 document: PDF/A-3 container, embedded factur-x.xml, XMP metadata declaring the EN 16931 profile, already checked against the official Schematron. If the data cannot produce a compliant invoice (a breakdown that does not reconcile, a missing mandatory field), the API answers HTTP 400 with the violated rules as structured findings, so an invalid document never leaves your system.

Runnable Node.js examples on GitHub →


Validate ZUGFeRD invoices in Node.js

Validate outgoing documents before customers see them, and incoming supplier PDFs that claim to be ZUGFeRD. The endpoint extracts the embedded XML, detects the declared profile from BT-24, and runs the matching XSD plus Schematron rules. Uploads are standard FormData; one gotcha worth knowing is that you must not set the Content-Type header yourself, because fetch generates the multipart boundary for you:

import { readFile } from "node:fs/promises";

async function validateZugferd(path) {
  const form = new FormData();
  form.append(
    "file",
    new Blob([await readFile(path)], { type: "application/pdf" }),
    "rechnung.pdf"
  );

  const response = await fetch(`${BASE_URL}/v1/validate/zugferd`, {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}` },
    body: form
  });
  return response.json();
}

const report = await validateZugferd("rechnung-zugferd.pdf");

if (report.valid) {
  console.log(`Valid ZUGFeRD, profile ${report.data.profile}`);
} else {
  for (const finding of report.errors) {
    console.log(`[${finding.rule}] ${finding.message}`);
    console.log(`  fields: ${(finding.fields ?? []).join(", ")}`);
  }
}

Both valid and invalid invoices answer HTTP 200; the verdict lives in valid, and non-2xx statuses mean transport problems such as a bad API key or an unreadable upload. Findings carry the rule id, a plain-language message, business term codes, and field paths, ready for a UI or a supplier email as-is.

The same call is a natural CI guard. One Vitest (or Jest) test pins the mapping from your data model to the API payload:

import { expect, test } from "vitest";

test("billing payload produces valid ZUGFeRD", async () => {
  const pdf = await createZugferd(sampleInvoice());
  const report = await validateZugferd(pdf);
  expect(report.valid, JSON.stringify(report.errors)).toBe(true);
});

Create XRechnung invoices in Node.js

Same JSON shape, different endpoint, and two additions that XRechnung enforces while base EN 16931 does not:

buyerReference carries the Leitweg-ID. It maps to BT-10 and is checked by rule BR-DE-15. The public sector buyer assigns it during onboarding; store it per buyer and inject it into every request. It is routing infrastructure, not invoice data, so no source document will ever contain it.

Seller contact and electronic addresses are mandatory. XRechnung requires the full seller.contact group (name, phone, email) and electronic addresses for both parties.

const xrechnung = {
  invoice: {
    invoiceNumber: "RE-2026-1106",
    issueDate: "2026-07-28",
    dueDate: "2026-08-27",
    currency: "EUR",
    buyerReference: "11302-00567-22",
    seller: {
      name: "Spreewerk Solutions GmbH",
      vatIdentifier: "DE811234567",
      postalAddress: {
        line1: "Köpenicker Str. 154",
        city: "Berlin",
        postCode: "10997",
        country: "DE"
      },
      contact: {
        name: "Lena Hoffmann",
        phone: "+49 30 5557890",
        email: "[email protected]"
      },
      electronicAddress: { identifier: "DE811234567", schemeId: "0204" }
    },
    buyer: {
      name: "Bezirksamt Friedrichshain-Kreuzberg von Berlin",
      postalAddress: {
        line1: "Frankfurter Allee 35",
        city: "Berlin",
        postCode: "10247",
        country: "DE"
      },
      electronicAddress: { identifier: "11302-00567-22", schemeId: "0204" }
    },
    paymentDetails: {
      paymentMeansCode: 58,
      paymentAccountIdentifier: "DE02120300000000202051"
    },
    lines: [
      {
        quantity: 95,
        unitCode: "HUR",
        item: { name: "Softwareentwicklung Fachverfahren" },
        priceDetails: { netPrice: 110.00 },
        vatInformation: { rate: 19, categoryCode: "S" }
      }
    ]
  }
};

const response = await fetch(`${BASE_URL}/v1/create/xrechnung`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify(xrechnung)
});

if (!response.ok) throw new Error(await response.text());

await writeFile("rechnung-xrechnung.xml", Buffer.from(await response.arrayBuffer()));

The response is XRechnung 3.0 in CII syntax, already checked against the EN 16931 Schematron and the KoSIT rules. XRechnung also permits a UBL binding, which matters if your stack serves the Peppol network too: create it via POST /v1/create/ubl with options: { profile: "xrechnung" } and the same invoice JSON. Both outputs are legally equivalent.


Validate XRechnung invoices in Node.js

POST /v1/validate/xrechnung auto-detects CII or UBL syntax and runs three layers: the XSD, the EN 16931 core Schematron, and the German BR-DE rules from the KoSIT Schematron, the same rule set ZRE and OZG-RE enforce on submission. It accepts XML from any producer, including node-zugferd output or hand-built documents:

async function validateXrechnung(path) {
  const form = new FormData();
  form.append(
    "file",
    new Blob([await readFile(path)], { type: "application/xml" }),
    "rechnung.xml"
  );

  const response = await fetch(`${BASE_URL}/v1/validate/xrechnung`, {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}` },
    body: form
  });
  return response.json();
}

const report = await validateXrechnung("rechnung-xrechnung.xml");

if (!report.valid) {
  for (const finding of report.errors) {
    // finding.layer is "xsd", "en16931", or "cius" (the German BR-DE rules)
    console.log(`[${finding.rule}] (${finding.layer}) ${finding.message}`);
  }
}

The layer tag is worth surfacing to users: a cius failure means the document is a perfectly fine EN 16931 invoice that specifically misses a German requirement, and the classic case is BR-DE-15 firing on a missing Leitweg-ID. Note this endpoint accepts XML only; a ZUGFeRD hybrid PDF declaring the XRechnung reference profile goes to /v1/validate/zugferd, and its embedded XML is routed to the same KoSIT rules automatically.


Extract invoice data: AI parsing, XML extraction, attachments

Receiving is the half of the mandate that arrived first: since January 2025 you cannot refuse a compliant e-invoice, so your intake pipeline has to cope with whatever suppliers send. In practice that inbox holds three kinds of documents, and each has its own endpoint: structured e-invoices (ZUGFeRD PDFs or XRechnung XML), old-school PDFs with no structured data at all, and invoices carrying embedded supporting documents you also need.

Deterministic JSON from ZUGFeRD and XRechnung

For documents that already carry structured data, POST /v1/extract/json is pure XML parsing, no AI involved: upload a ZUGFeRD or Factur-X hybrid PDF (the embedded XML is pulled out automatically) or a standalone XRechnung, CII, or UBL XML file, and get back a normalized InvoiceDocument JSON whose field names mirror the /v1/create request bodies:

async function extractInvoice(path) {
  const form = new FormData();
  form.append("file", new Blob([await readFile(path)]), "invoice");

  const response = await fetch(`${BASE_URL}/v1/extract/json`, {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}` },
    body: form
  });
  if (!response.ok) throw new Error(await response.text());
  return response.json();
}

const data = await extractInvoice("lieferant-zugferd.pdf");
console.log(`Rechnung ${data.invoiceNumber} von ${data.seller.name}`);

Because the parsing is deterministic, the output is an exact mapping of the source XML: what the supplier declared is what you get, byte-for-byte faithful, which is what an ERP import wants. If you prefer the raw embedded XML itself, /v1/extract/xml streams it out of the PDF container untouched.

AI parsing for old-school invoice PDFs

Not every supplier sends e-invoices yet. For typed, scanned, or photographed PDFs with no embedded XML, POST /v1/parse/json reads the document with AI and returns the same InvoiceDocument shape, plus a confidence object that makes the non-determinism explicit: an overall score and four area scores (seller identification, buyer identification, tax calculation, line items), each from 0.0 to 1.0. That lets you automate the confident reads and route the shaky ones to a human:

async function parseInvoicePdf(path) {
  const form = new FormData();
  form.append(
    "file",
    new Blob([await readFile(path)], { type: "application/pdf" }),
    "eingang.pdf"
  );

  const response = await fetch(`${BASE_URL}/v1/parse/json`, {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}` },
    body: form
  });
  if (!response.ok) throw new Error(await response.text());
  return response.json();
}

const { invoice, confidence } = await parseInvoicePdf("lieferant-scan.pdf");

if (confidence.overall < 0.7) {
  await queueForHumanReview(invoice, confidence);
} else {
  await importIntoErp(invoice);
}

The two endpoints compose into one intake function: try the deterministic route first, and fall back to AI only when the API reports error code 4006 (no embedded XML):

async function readIncomingInvoice(path) {
  const form = new FormData();
  form.append("file", new Blob([await readFile(path)]), "invoice");

  const response = await fetch(`${BASE_URL}/v1/extract/json`, {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}` },
    body: form
  });
  if (response.ok) {
    return { source: "embedded-xml", invoice: await response.json() };
  }

  const error = await response.json();
  if (error.errorCode !== 4006) throw new Error(JSON.stringify(error));

  // No embedded XML: an old-school PDF, hand it to the AI parser
  const parsed = await parseInvoicePdf(path);
  return { source: "ai", ...parsed };
}

Extract embedded attachments

E-invoices can carry their supporting documents inside the invoice itself: delivery notes, timesheets, or the original order, embedded base64 in the EN 16931 attachment group (BG-24). XRechnung makes this routine, because German B2G portals transmit supporting documents inside the XML rather than as separate files. POST /v1/extract/attachments collects every embedded payload and returns the original files as one ZIP:

async function extractAttachments(path, zipPath) {
  const form = new FormData();
  form.append("file", new Blob([await readFile(path)]), "invoice");

  const response = await fetch(`${BASE_URL}/v1/extract/attachments`, {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}` },
    body: form
  });
  if (response.status === 404) return null; // invoice carries no attachments
  if (!response.ok) throw new Error(await response.text());

  await writeFile(zipPath, Buffer.from(await response.arrayBuffer()));
  return zipPath;
}

await extractAttachments("rechnung-xrechnung.xml", "belege.zip");

It accepts a standalone XML e-invoice (XRechnung in either syntax, CII, or UBL) or a ZUGFeRD / Factur-X hybrid PDF, and the attachment bytes are decoded into the ZIP verbatim, never parsed or rendered server-side. An invoice without embedded attachments answers 404 rather than an empty archive, which is why the snippet treats that status as a normal outcome.


Render XRechnung previews

XRechnung has no visual layer, so before submission (or when an incoming one needs human review) render it as a readable PDF:

async function renderXrechnung(xmlPath, pdfPath) {
  const form = new FormData();
  form.append(
    "file",
    new Blob([await readFile(xmlPath)], { type: "application/xml" }),
    "rechnung.xml"
  );

  const response = await fetch(`${BASE_URL}/v1/render/xrechnung/to/pdf`, {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}` },
    body: form
  });
  await writeFile(pdfPath, Buffer.from(await response.arrayBuffer()));
}

await renderXrechnung("rechnung-xrechnung.xml", "rechnung-vorschau.pdf");

The preview is for human eyes only; the XML remains the legal document, and the rendered PDF should never be submitted to a portal.


Serverless and edge deployments

Everything above used only fetch, FormData, and Blob, which are Web APIs, not Node.js APIs. That is what makes this integration serverless-native: no native modules to compile, no headless browser, no filesystem requirement, no bundle-size fight, and no long-running process. The library route struggles on exactly those points, because PDF tooling tends to assume a full Node.js environment.

A complete Cloudflare Worker that turns JSON into a ZUGFeRD PDF, with the key held in a Worker secret:

export default {
  async fetch(request, env) {
    if (request.method !== "POST") {
      return new Response("Method not allowed", { status: 405 });
    }

    const payload = await request.json();
    const upstream = await fetch("https://api.invoicexml.com/v1/create/zugferd", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${env.INVOICEXML_API_KEY}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ invoice: payload })
    });

    if (!upstream.ok) {
      return new Response(await upstream.text(), { status: 400 });
    }

    return new Response(upstream.body, {
      headers: {
        "Content-Type": "application/pdf",
        "Content-Disposition": 'attachment; filename="rechnung-zugferd.pdf"'
      }
    });
  }
};

Note the last line of the happy path: upstream.body is a stream, so the PDF flows from the API through the Worker to the client without ever being buffered in memory. The same shape works as a Next.js route handler, where it doubles as the correct answer to "can the browser call the API directly?" (it should not, because client-side code would expose your key):

// app/api/invoices/route.js  (Next.js App Router, also fine on Vercel Edge)
export async function POST(request) {
  const { invoice } = await request.json();

  const upstream = await fetch("https://api.invoicexml.com/v1/create/zugferd", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.INVOICEXML_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ invoice })
  });

  if (!upstream.ok) {
    return new Response(await upstream.text(), { status: 400 });
  }
  return new Response(upstream.body, {
    headers: { "Content-Type": "application/pdf" }
  });
}

Deno and Bun need no changes at all beyond how they read environment variables. For retries in any of these runtimes, retry transport failures and 5xx responses only; an HTTP 400 carries validation findings about your data, and resending the same payload produces the same findings.


npm library vs REST API

The fair summary for JavaScript teams:

The library route is a standing commitment, not a dependency. node-zugferd is free and actively developed, and building on it still means owning the gap on validation (without an in-process path to the official Schematron, you are trusting your mapping, not verifying it), the PDF/A conformance of your output, XRechnung, intake, and the KoSIT calendar. Every specification release, and KoSIT ships a new XRechnung version yearly with patches between, means waiting for the package to catch up, bumping it, retesting, and redeploying every service that touches invoices.

The API is a complete compliance service. Creation, validation, extraction, AI parsing, and rendering behind one integration, with the official EN 16931 and KoSIT rule sets live server-side before their effective dates. New format versions ship out of the box: nothing to monitor, no npm release to wait for, no redeploy, which matters double in serverless deployments where you would otherwise be re-bundling workers on someone else's schedule. The domain expertise sits on our side with professional support behind it, missing features get integrated on request, and both German formats plus Factur-X and Peppol UBL run behind one integration that deploys anywhere JavaScript runs.

The deciding question is who owns the moving target. Compliance rules change on a government calendar, not yours. Build on a package and that calendar sets your release cadence indefinitely; build on the API and your integration is finished the day it works, because staying compliant is our job.

Data handling is stateless by architecture: documents are processed in memory and purged when the response ships. Nothing is stored, nothing is logged, and no invoice data trains any model. VAT numbers, IBANs, and Leitweg-IDs exist server-side only for the duration of a single HTTP exchange.


Endpoint reference

OperationEndpointInputOutput
Create ZUGFeRDPOST /v1/create/zugferdJSONPDF/A-3 binary
Create XRechnung (CII)POST /v1/create/xrechnungJSONXRechnung 3.0 XML
Create XRechnung (UBL)POST /v1/create/ubl + options.profile: xrechnungJSONXRechnung UBL XML
Validate ZUGFeRDPOST /v1/validate/zugferdZUGFeRD PDFValidation JSON
Validate XRechnungPOST /v1/validate/xrechnungXRechnung XML (CII or UBL)Validation JSON
Render XRechnung as PDFPOST /v1/render/xrechnung/to/pdfXRechnung XMLPDF binary
Extract as JSONPOST /v1/extract/jsonZUGFeRD PDF or XRechnung XMLStructured JSON
Parse PDF with AIPOST /v1/parse/jsonAny invoice PDF (typed, scanned, photo)Structured JSON + confidence
Extract attachmentsPOST /v1/extract/attachmentsZUGFeRD PDF or XRechnung XMLZIP archive
Extract CII XMLPOST /v1/extract/xmlZUGFeRD PDFCII XML

Full OpenAPI 3.1 schema: api.invoicexml.com/v1/openapi  |  Interactive API explorer: api.invoicexml.com/v1/scalar


Get started

Every snippet in this guide runs on a stock Node.js 18+ with zero dependencies: paste it into an .mjs file, set INVOICEXML_API_KEY, and run it. Runnable examples live in the examples repository:

InvoiceXML/facturx-api-examples/nodejs →

Create a free InvoiceXML account → get 100 credits for free, no credit card required.

Related resources:


InvoiceXML is a REST API for European e-invoice compliance covering ZUGFeRD, XRechnung, Factur-X, Peppol UBL, and CII. Stateless processing, GDPR compliant by architecture, and callable from any JavaScript runtime: Node.js, Next.js, Vercel, Cloudflare Workers, Deno, or Bun.

Start free today

Ready to automate your invoices?

Validate, convert and embed compliant e-invoices through one API. Start your 30-day free trial. No credit card required.

GDPR Compliant No credit card required Setup in minutes
Peppol UBL
Factur-X
EN 16931
142 / 142 passed
Compliant
PDF/A-3 embedded