Guide / Receive

Receive Factur-X and XRechnung as ERP JSON

A supplier sends you a Factur-X PDF or XRechnung XML. Your application needs to validate it, normalize the payload, and push clean invoice data to your ERP. This guide walks through the complete receive pipeline, step by step.

The Receive Pipeline

Pipeline Flow

  Supplier PDF/XML
       |
       v
  +-----------------+
  |  /v1/validate   |  ->  Is this invoice EN 16931 compliant?
  +--------+--------+
       | valid?
       v
  +-----------------+
  |  /v1/extract    |  ->  Pull raw XML fields as JSON (Community)
  +--------+--------+
       |
       v
  +-----------------+
  |  /v1/serialize  |  ->  Normalized, typed JSON with schema (Pro)
  +--------+--------+
       |
       v
  Your Database / ERP

Step 1 Validate

Before trusting any invoice data, run it through Schematron validation. This catches structural errors, missing fields, and business rule violations.

curl
curl -X POST "http://localhost:8000/v1/validate" \
  -F "file=@supplier_invoice.pdf"
validate.py Python
import requests

def validate_invoice(file_path):
    r = requests.post(
        "http://localhost:8000/v1/validate",
        files={"file": open(file_path, "rb")}
    )
    report = r.json()

    if report["valid"]:
        print(f"Compliant ({report['flavor']})")
    else:
        print(f"Errors: {report['errors']}")

    return report
Response
{
  "valid": true,
  "format": "factur-x",
  "flavor": "en16931",
  "validation_completeness": "full",
  "layers_executed": ["syntax", "schematron"]
}

Tip: Check validation_completeness in your application logic. A value of "partial" means some validation layers were skipped (e.g., Saxon-HE not available).

Step 2 Extract Community

Extract the embedded XML and get a standard JSON representation. This endpoint mirrors the raw XML fields.

curl
curl -X POST "http://localhost:8000/v1/extract" \
  -F "file=@supplier_invoice.pdf"
Response (truncated)
{
  "format_detected": "factur-x",
  "profile_detected": "en16931",
  "xml_extracted": true,
  "invoice_json": {
    "invoice_number": "INV-2025-0042",
    "invoice_date": "2025-03-01",
    "seller": { "name": "ACME SAS" },
    "buyer": { "name": "Client Corp" },
    "totals": {
      "net_amount": "750.00",
      "tax_amount": "150.00",
      "payable_amount": "900.00"
    }
  }
}

Step 3 Serialize Pro

For ERP integration, use /v1/serialize instead of /v1/extract. It returns a normalized, typed JSON object with a versioned schema — ready for direct database insertion.

serialize.py Python (Pro)
import requests

def serialize_invoice(file_path):
    r = requests.post(
        "http://localhost:8000/v1/serialize",
        files={"file": open(file_path, "rb")}
    )
    data = r.json()

    if data["success"]:
        invoice = data["invoice"]
        # Insert directly into your database
        db.invoices.insert({
            "number":     invoice["invoice_number"],
            "date":       invoice["invoice_date"],
            "seller":     invoice["seller"]["name"],
            "amount_due": invoice["amount_due"],
        })
    return data

/v1/extract (Community)

  • • Raw XML fields as JSON
  • • String values for amounts
  • • No schema versioning

/v1/serialize (Pro)

Error Handling

Non-compliant Invoice

When valid is false, the errors array contains the Schematron violations:

{
  "valid": false,
  "errors": [
    "BR-CO-10: Sum of Invoice line net amount must equal net total",
    "BR-S-08: VAT breakdown missing for category S"
  ]
}

No Embedded XML

If the PDF does not contain a Factur-X/ZUGFeRD XML attachment:

{
  "format_detected": null,
  "xml_extracted": false,
  "errors": ["NO_XML: No Factur-X XML found in PDF"]
}

Invalid File

Uploading a non-PDF/XML file returns HTTP 400:

HTTP 400 Bad Request
{ "detail": "Invalid input: uploaded file is not a valid PDF or XML" }

Complete Pipeline — Node.js

receive-pipeline.js Node.js
const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');

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

async function receiveInvoice(pdfPath) {
  // Step 1: Validate
  const form1 = new FormData();
  form1.append('file', fs.createReadStream(pdfPath));

  const validation = await axios.post(
    `${ENGINE}/v1/validate`, form1,
    { headers: form1.getHeaders() }
  );

  if (!validation.data.valid) {
    throw new Error(`Non-compliant: ${validation.data.errors}`);
  }

  // Step 2: Extract (or serialize for Pro)
  const form2 = new FormData();
  form2.append('file', fs.createReadStream(pdfPath));

  const extraction = await axios.post(
    `${ENGINE}/v1/extract`, form2,
    { headers: form2.getHeaders() }
  );

  return extraction.data.invoice_json;
}

See Also

Start Receiving Invoices

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