Node.js code samples for creating, validating, and parsing XRechnung electronic invoices using the InvoiceXML API. Compatible with Node.js 18 and later (native fetch and FormData). Runs in Express, NestJS, Fastify, Koa, Hapi, Next.js API routes, AWS Lambda, Cloudflare Workers, Vercel Functions, or plain scripts. Zero npm dependencies.
For background on the XRechnung standard itself (what it is, the Leitweg-ID, legal status), see the main repository README.
Every example in this folder calls the InvoiceXML REST API. Sign up and generate a free API key here:
→ https://www.invoicexml.com/account/authentication
Pass it as a Bearer token on every request:
Authorization: Bearer YOUR_API_KEY
Important: set apiKey in the examples to the raw key only, without the Bearer prefix. If your account page shows the full header value (e.g. Bearer ixml_a1b2c3...), copy only the part after Bearer . The code adds the prefix itself when building the Authorization header.
- Node.js 18.0 or later (current LTS recommended: Node 20 or 22)
- No npm packages, no
package.jsonneeded
The examples use Node's built-in global fetch, FormData, Blob, and Buffer. These have been stable in Node 18+. If you must support Node 16 or older, polyfill fetch with node-fetch and FormData with form-data.
| File | Operation | API endpoint |
|---|---|---|
create.js |
Build an XRechnung 3.0 XML invoice | POST /v1/create/xrechnung |
validate.js |
Validate an XRechnung file against the KoSIT rules | POST /v1/validate/xrechnung |
extract-json.js |
Parse an XRechnung XML into JSON | POST /v1/extract/json |
ai-convert.js |
(Experimental) Convert a plain PDF to XRechnung with AI | POST /v1/transform/to/xrechnung |
render.js |
Render XRechnung XML into a human-readable PDF | POST /v1/render/xrechnung/to/pdf |
Each file is standalone and runnable with node create.js. Open the file, replace YOUR_API_KEY with your real key, and execute.
Note on the snippets below: they are excerpts from those files and assume
apiKey(andfs) are already defined. They also useawaitat the top level, which is not valid in a plain CommonJS script; the full files wrap the calls in anasyncIIFE. When in doubt, copy the complete file.
const payload = {
invoice: {
invoiceNumber: 'XR-2026-001',
issueDate: '2026-05-18',
currency: 'EUR',
buyerReference: '991-12345-67',
seller: {
name: 'Acme GmbH',
vatIdentifier: 'DE123456789',
legalRegistration: { identifier: 'HRB 12345' },
postalAddress: { line1: 'Hauptstraße 12', city: 'Berlin', postCode: '10115', country: 'DE' },
contact: { name: 'Max Mustermann', phone: '+49 30 12345678', email: 'billing@acme.de' },
electronicAddress: { identifier: 'DE123456789', schemeId: '9930' },
},
buyer: {
name: 'Bundesamt für Musterverwaltung',
postalAddress: { line1: 'Behördenstraße 5', city: 'Bonn', postCode: '53113', country: 'DE' },
electronicAddress: { identifier: '991-12345-67', schemeId: '0204' },
},
paymentDetails: { paymentAccountIdentifier: 'DE89370400440532013000' },
lines: [{
quantity: 10,
priceDetails: { netPrice: 150.00 },
vatInformation: { rate: 19.00 },
item: { name: 'Senior consulting' },
}],
},
options: { syntax: 'ubl' },
};
const response = await fetch('https://api.invoicexml.com/v1/create/xrechnung', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + apiKey,
},
body: JSON.stringify(payload),
});
fs.writeFileSync('invoice-xrechnung.xml', await response.text());buyerReference carries the Leitweg-ID (BT-10), and the seller contact and electronicAddress groups are what the XRechnung CIUS requires on top of plain EN 16931. Omit any of them and the API returns a 400 naming the BR-DE-* rule you missed.
The response is the XRechnung 3.0 XML document, validated against the KoSIT rules before delivery.
Full example: create.js | API reference
const fs = require('fs');
const form = new FormData();
form.append('file', new Blob([fs.readFileSync('invoice.xml')], { type: 'application/xml' }), 'invoice.xml');
const response = await fetch('https://api.invoicexml.com/v1/validate/xrechnung', {
method: 'POST',
headers: { Authorization: 'Bearer ' + apiKey },
body: form,
});
console.log(await response.text());Returns a JSON validation report listing any rule failures (EN 16931 BR-* and BR-CO-, plus the German BR-DE- rules).
Full example: validate.js | API reference
Useful for feeding XRechnung invoices into Express controllers, message queues, or any pipeline that prefers JSON over XML.
const fs = require('fs');
const form = new FormData();
form.append('file', new Blob([fs.readFileSync('invoice.xml')], { type: 'application/xml' }), 'invoice.xml');
const response = await fetch('https://api.invoicexml.com/v1/extract/json', {
method: 'POST',
headers: { Authorization: 'Bearer ' + apiKey },
body: form,
});
const { invoice } = await response.json();
// The invoice document sits under the "invoice" key of the response.
console.log(invoice.seller.name, invoice.totals.grandTotalAmount);Full example: extract-json.js | API reference | Sample response
Experimental feature. Human verification required before any production use.
Real-world PDF invoices are often messy: scanned at low quality, irregularly formatted, multi-page, or missing fields that EN 16931 requires. AI extraction can make subtle mistakes that automated validators may not catch: wrong tax category codes, transposed amounts, missing seller VAT identifiers, incorrect currency formatting.
Always review the output before submitting it to a public authority. See the AI conversion notes in the main README.
The endpoint takes the PDF plus a buyerReference form field: the Leitweg-ID cannot be inferred from the source document, so you must supply it.
Full example: ai-convert.js | API reference
XRechnung has no visual layer: the XML is the invoice, which is fine for machines and useless for the person in accounts payable who wants to read it. This endpoint renders the XML into a formatted PDF preview, auto-detecting whether the file is CII or UBL syntax. The PDF is for reading only; the XML file remains the authoritative invoice for compliance and tax purposes.
const fs = require('fs');
const form = new FormData();
form.append('file', new Blob([fs.readFileSync('invoice.xml')], { type: 'application/xml' }), 'invoice.xml');
form.append('language', 'de'); // en, de, or fr
const response = await fetch('https://api.invoicexml.com/v1/render/xrechnung/to/pdf', {
method: 'POST',
headers: { Authorization: 'Bearer ' + apiKey },
body: form,
});
fs.writeFileSync('invoice-preview.pdf', Buffer.from(await response.arrayBuffer()));Full example: render.js | API reference
Return an XRechnung invoice from a route handler:
const express = require('express');
const app = express();
app.get('/invoices/:id/xrechnung', async (req, res) => {
const pdf = await createXRechnung(req.params.id);
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="invoice-${req.params.id}.pdf"`);
res.send(pdf);
});Store the API key in process.env.INVOICEXML_API_KEY and read it from dotenv or your platform's secret manager.
@Controller('invoices')
export class InvoiceController {
@Get(':id/xrechnung')
@Header('Content-Type', 'application/pdf')
async download(@Param('id') id: string, @Res() res: Response) {
const pdf = await this.xrechnungService.create(id);
res.setHeader('Content-Disposition', `attachment; filename="invoice-${id}.pdf"`);
res.send(pdf);
}
}// app/api/invoices/[id]/xrechnung/route.ts
export async function GET(req: Request, { params }: { params: { id: string } }) {
const pdf = await createXRechnung(params.id);
return new Response(pdf, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="invoice-${params.id}.pdf"`,
},
});
}The examples run on Workers and Edge runtimes unchanged because fetch and FormData are part of the runtime. Replace fs.readFileSync with an await request.arrayBuffer() from the inbound request and the same pattern works in serverless environments.
Lambda's Node.js 18+ runtime provides native fetch and FormData, so the examples run as-is.
fetch is not defined: you are running Node 16 or older. Upgrade to Node 18+ (current LTS), or polyfill withnpm install node-fetch form-dataand import accordingly.HTTP 401 Unauthorized: API key missing or invalid. Generate one at invoicexml.com/account/authentication and confirm you are sendingAuthorization: Bearer YOUR_API_KEY. A frequent cause: settingapiKeyto the wholeBearer xxxvalue, which sendsBearer Bearer xxx. Set the raw key only.HTTP 400 Bad Requeston Create: a required field is missing or malformed. Frequent causes:IssueDatenot in ISO format (YYYY-MM-DD),Currencynot in ISO 4217 (EUR,USD), country codes not in ISO 3166-1 alpha-2 (DE,FR).ESMvsCommonJS: the examples use CommonJS (require). For ESM (.mjsextension or"type": "module"inpackage.json), swaprequire('fs')forimport fs from 'node:fs'and you can use top-level await without the IIFE wrapper.- Relative file paths:
fs.readFileSync('invoice.xml')resolves from the current working directory, not the script file. Use__dirname(CommonJS) orimport.meta.dirname(ESM, Node 20.11+) for absolute paths. - BR-DE- failures on Validate*: an XRechnung-specific field is missing. The most common are BR-DE-15 (no Leitweg-ID in
buyerReference), BR-DE-2 (no seller contact group), and BR-DE-1 (no seller electronic address).