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.
requests library (pip install requests)http://localhost:8000import 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)
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 |
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"}
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")
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
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.
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.
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.