Tutorials / Python

Python Factur-X Library vs. Stateless API

Searching for a Python Factur-X library to generate EN16931 compliant e-invoices? Instead of fighting native binary Factur-X Python parsing, you can integrate the stateless Factur-X Engine API into your FastAPI, Django, or script-based workflows using the standard `requests` library.

Prerequisites

  • Python 3.x
  • requests library (pip install requests)
  • Factur-X Engine running on http://localhost:8000

Full Implementation

import requests

def generate_invoice(pdf_path, json_metadata):
    url = "http://localhost:8000/v1/convert"
    
    files = {
        'pdf': open(pdf_path, 'rb'),
    }
    data = {
        'metadata': json_metadata
    }

    # Standard Multipart Upload
    response = requests.post(url, files=files, data=data)
    
    if response.status_code == 200:
        with open("factur-x-invoice.pdf", "wb") as f:
            f.write(response.content)
        print("Success: Generated factur-x-invoice.pdf")
    else:
        print(f"Error: {response.status_code}")
        print(response.text)

# Example Metadata
metadata = """
{
    "invoice_number": "INV-2026-001",
    "seller": { "name": "Python Dev", "vat_id": "FR123456789" },
    "totals": { "net_amount": 100.00, "tax_amount": 20.00 }
}
"""
generate_invoice("input.pdf", metadata)

Python Library vs Factur-X Docker API

When searching for a Python Factur-X library, developers often encounter fragile XML parsers that break on edge cases or lack strict EN16931 / E-Rechnung validation. Native Python XML generation is tedious to maintain against evolving European mandates.

Approach Schematron Dependency footprint Profile support
factur-x (PyPI) None lxml, reportlab Partial
Mustang (Java CLI) Yes JVM, CLI wrapper Full
Factur-X Engine API Yes (Saxon-HE) requests only Full + XRechnung 3.0

Use Case: FastAPI Background Invoice Generation

The most common production pattern is triggering invoice generation as a background task after a successful payment. The Engine returns the PDF as a binary stream — write it directly to S3, GCS, or your filesystem:

from fastapi import FastAPI, BackgroundTasks
import requests, boto3

app = FastAPI()
s3 = boto3.client("s3")

def generate_and_store(order_id: str, pdf_path: str, metadata: dict):
    resp = requests.post(
        "http://facturx-engine:8000/v1/convert",
        files={"pdf": open(pdf_path, "rb")},
        data={"metadata": json.dumps(metadata)},
        timeout=30,
    )
    resp.raise_for_status()
    s3.put_object(
        Bucket="invoices",
        Key=f"{order_id}.pdf",
        Body=resp.content,
        ContentType="application/pdf",
    )

@app.post("/orders/{order_id}/invoice")
async def create_invoice(order_id: str, bg: BackgroundTasks):
    bg.add_task(generate_and_store, order_id, "base.pdf", build_metadata(order_id))
    return {"status": "queued"}

Use Case: Django Management Command for Batch Processing

For month-end batch generation of hundreds of invoices, wrap the API call in a Django management command. Use ThreadPoolExecutor to run up to 4 parallel requests against the engine's Gunicorn workers:

from django.core.management.base import BaseCommand
from concurrent.futures import ThreadPoolExecutor
import requests

ENGINE_URL = "http://localhost:8000/v1/convert"

def convert_one(invoice):
    r = requests.post(ENGINE_URL,
        files={"pdf": open(invoice.template_path, "rb")},
        data={"metadata": invoice.to_json()})
    if r.ok:
        invoice.save_pdf(r.content)
        return True
    return False

class Command(BaseCommand):
    help = "Generate Factur-X PDFs for all pending invoices"

    def handle(self, *args, **kwargs):
        pending = Invoice.objects.filter(status="pending")
        with ThreadPoolExecutor(max_workers=4) as ex:
            results = list(ex.map(convert_one, pending))
        self.stdout.write(f"{sum(results)}/{len(results)} generated")

Use Case: Validate Received Invoices Before Archiving

When receiving e-invoices from suppliers, validate compliance before injecting them into your accounting system. Non-compliant invoices (e.g. missing IBAN for SEPA payment, broken VAT math) are rejected early with a clear error report:

import requests

def validate_received_invoice(file_path: str) -> dict:
    """Returns {'is_valid': bool, 'errors': list}"""
    with open(file_path, "rb") as f:
        r = requests.post(
            "http://localhost:8000/v1/validate",
            files={"file": f},
            timeout=15,
        )
    r.raise_for_status()
    result = r.json()

    if not result["is_valid"]:
        for err in result.get("errors", []):
            print(f"[{err['code']}] {err['message']}")
        raise ValueError(f"Invoice {file_path} failed EN 16931 validation")

    return result

Frequently Asked Questions

Do I need to install any Python packages beyond requests?

No. The entire EN 16931 toolchain (Saxon-HE, VeraPDF, lxml, Schematron rulesets) runs inside the Docker container. Your Python application only needs the standard requests library to make HTTP calls to the engine.

How do I extract structured data from a received Factur-X PDF?

Use POST /v1/extract with the PDF as the file parameter. The engine returns a structured JSON object containing all invoice fields (seller, buyer, line items, totals, payment terms) parsed from the embedded XML — ready to insert into your ERP or database without manual XML parsing.

Is the Docker API compatible with async Python (asyncio / httpx)?

Yes. Replace requests.post with await httpx.AsyncClient().post(...) for fully async usage. The engine itself is built on FastAPI + Uvicorn and handles concurrent requests efficiently via multiple Gunicorn workers.

See Also