Guide / Inbound JSON

Turn XRechnung XML into ERP JSON

If your ERP receives XRechnung files, the hard part is not parsing XML. The hard part is validating the invoice, understanding what was actually checked, and returning a JSON payload your integration can trust.

Updated

Recommended Pipeline

Pipeline Flow

  XRechnung XML
       |
       v
  /v1/validate    -> EN 16931 compliance + validation completeness
       |
       v
  /v1/serialize   -> ERP-ready JSON + fallbacks_applied (Pro)
       |
       v
  /v1/extract     -> heuristic JSON fallback (Community)
                    

Validate First

Start with /v1/validate so your application knows whether the invoice passed EN 16931 checks, whether PDF/A validation ran, and whether the validation report is complete or partial.

cURL
curl -X POST "http://localhost:8000/v1/validate" \
  -F "file=@invoice-xrechnung.xml"
Response Highlights
{
  "valid": true,
  "format": "ubl",
  "flavor": "xrechnung_3.0",
  "pdfa_valid": null,
  "validation_completeness": "full"
}

Normalize into ERP JSON

For production-grade ingestion, use /v1/serialize. It returns typed invoice data plus fallbacks_applied and xml_recovery_applied, so your integration can decide what is acceptable.

cURL
curl -X POST "http://localhost:8000/v1/serialize" \
  -F "file=@invoice-xrechnung.xml"
Response Highlights
{
  "success": true,
  "invoice": {
    "invoice_number": "R-2026-0042",
    "format": "ubl",
    "profile": "xrechnung_3.0"
  },
  "xml_recovery_applied": false,
  "fallbacks_applied": []
}

Community Fallback

If you only need best-effort extraction, /v1/extract returns invoice JSON with invoice_json._meta.limitations so your team can distinguish heuristic mapping from strict normalization.

Limitations Example
{
  "invoice_json": {
    "_meta": {
      "limitations": ["heuristic_mapping", "fallback_values_used"]
    }
  }
}

Extract vs Serialize — Which Should You Use?

Both endpoints return invoice JSON from an XRechnung file. The difference is in schema guarantees, field coverage, and what happens when the XML is ambiguous.

/v1/extract /v1/serialize Pro
Availability Community + Pro Pro only
Mapping strategy Heuristic best-effort Strict, typed, versioned
Fallback transparency _meta.limitations[] fallbacks_applied[] + xml_recovery_applied
Schema version No versioning Versioned JSON schema
Best for Prototyping, low-risk ingestion Production AP pipelines, DB insertion

Python Integration Example

A complete validation-then-extract pipeline in Python — the pattern used in most ERP webhook handlers:

ingest_xrechnung.py Python
import requests

ENGINE = "http://localhost:8000"

def ingest_xrechnung(xml_path: str) -> dict:
    with open(xml_path, "rb") as f:
        data = f.read()

    # Step 1: Validate against EN 16931 + XRechnung 3.0 rules
    r = requests.post(f"{ENGINE}/v1/validate",
        files={"file": ("invoice.xml", data, "application/xml")})
    report = r.json()

    if not report["valid"]:
        errors = report.get("errors", [])
        raise ValueError(f"EN 16931 violation: {errors[0]}" if errors else "Invalid")

    # Step 2: Extract structured data
    r = requests.post(f"{ENGINE}/v1/extract",
        files={"file": ("invoice.xml", data, "application/xml")})
    result = r.json()

    invoice = result["invoice_json"]
    limitations = invoice.get("_meta", {}).get("limitations", [])
    if limitations:
        print(f"Warning: heuristic fields used: {limitations}")

    return invoice

Node.js Integration Example

Using native fetch (Node 18+) — no dependencies:

ingest-xrechnung.mjs Node.js
import { readFile } from 'node:fs/promises'

const ENGINE = 'http://localhost:8000'

async function ingestXRechnung(xmlPath) {
  const data = await readFile(xmlPath)
  const blob = new Blob([data], { type: 'application/xml' })

  // Validate first
  const form = new FormData()
  form.append('file', blob, 'invoice.xml')
  const vRes = await fetch(`${ENGINE}/v1/validate`, { method: 'POST', body: form })
  const report = await vRes.json()
  if (!report.valid) throw new Error(`Invalid: ${report.errors?.[0]}`)

  // Extract JSON
  const form2 = new FormData()
  form2.append('file', blob, 'invoice.xml')
  const eRes = await fetch(`${ENGINE}/v1/extract`, { method: 'POST', body: form2 })
  const { invoice_json } = await eRes.json()
  return invoice_json
}

Frequently Asked Questions

How do I convert XRechnung XML to JSON?

Send the XML file to POST /v1/extract. The API returns structured JSON with all invoice fields. For production pipelines requiring strict typing and fallback transparency, use POST /v1/serialize (Pro). Both accept CII (Factur-X, ZUGFeRD) and UBL (XRechnung) formats — no pre-conversion needed.

Does Factur-X Engine validate XRechnung 3.0 Schematron rules?

Yes. /v1/validate runs the full EN 16931 Schematron ruleset including XRechnung 3.0 extension rules via Saxon-HE. The response includes flavor: "xrechnung_3.0", a list of BR-code violations, and validation_completeness so you know whether all checks ran.

Can I run this without a Pro license?

Yes. /v1/validate and /v1/extract are fully available in community mode (no license required). /v1/serialize requires a Pro license and returns 403 LICENSE_REQUIRED otherwise. Start with extract and upgrade when you need guaranteed schema stability.

See Also

Start Normalizing XRechnung

docker run -d -p 8000:8000 facturxengine/facturx-engine:latest