Python code samples for creating, validating, and parsing UBL electronic invoices using the InvoiceXML API. Compatible with Python 3.7+ (3.10+ recommended). Runs in Django, Flask, FastAPI, Pandas pipelines, Jupyter notebooks, AWS Lambda, Google Cloud Functions, Azure Functions, or plain scripts.
For background on UBL itself (what it is, the profiles, where it is used), 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 api_key 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.
- Python 3.7 or later (3.10+ recommended)
- The
requestslibrary
pip install requestsOr with uv (the modern alternative):
uv pip install requestsrequests is the standard HTTP client for Python: clean multipart support, automatic JSON decoding, and the most familiar API in the ecosystem. If you prefer the modern async alternative, httpx translates almost line-for-line.
| File | Operation | API endpoint |
|---|---|---|
create.py |
Build a UBL 2.1 XML invoice | POST /v1/create/ubl |
validate.py |
Validate a UBL file against the EN 16931 and Peppol rules | POST /v1/validate/ubl |
extract_json.py |
Parse a UBL XML into JSON | POST /v1/extract/json |
ai_convert.py |
(Experimental) Convert a plain PDF to UBL with AI | POST /v1/transform/to/ubl |
render.py |
Render UBL XML into a human-readable PDF | POST /v1/render/ubl/to/pdf |
Each file is standalone and runnable with python create.py. 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
api_keyis already defined. When in doubt, copy the complete file.
import requests
payload = {
"invoice": {
"invoiceNumber": "UBL-2026-001",
"issueDate": "2026-05-18",
"currency": "EUR",
"buyerReference": "PO-2026-5571",
"seller": {
"name": "Acme GmbH",
"vatIdentifier": "DE123456789",
"legalRegistration": {"identifier": "HRB 12345"},
"postalAddress": {"line1": "Hauptstraße 12", "city": "Berlin", "postCode": "10115", "country": "DE"},
"electronicAddress": {"identifier": "DE123456789", "schemeId": "9930"},
},
"buyer": {
"name": "Globex SAS",
"postalAddress": {"line1": "15 rue de Rivoli", "city": "Paris", "postCode": "75001", "country": "FR"},
"electronicAddress": {"identifier": "FR40303265045", "schemeId": "9957"},
},
"paymentDetails": {"paymentAccountIdentifier": "DE89370400440532013000"},
"lines": [{
"quantity": 10,
"priceDetails": {"netPrice": 150.00},
"vatInformation": {"rate": 19.00},
"item": {"name": "Senior consulting"},
}],
},
"options": {"profile": "peppol-bis-3"},
}
response = requests.post(
"https://api.invoicexml.com/v1/create/ubl",
headers={"Authorization": f"Bearer {api_key}"},
json=payload,
)
with open("invoice-ubl.xml", "w", encoding="utf-8") as f:
f.write(response.text)options.profile decides both the CustomizationID stamped into the document and the rules it is validated against. Peppol BIS Billing 3.0 is the default and needs an electronicAddress on both parties plus a buyerReference; for invoices that never touch the Peppol network, set the profile to en16931 and those become optional.
The response is the UBL 2.1 XML document, validated against the profile's rules before delivery.
Full example: create.py | API reference
import requests
response = requests.post(
"https://api.invoicexml.com/v1/validate/ubl",
headers={"Authorization": f"Bearer {api_key}"},
files={"file": ("invoice.xml", open("invoice.xml", "rb"), "application/xml")},
)
print(response.text)Returns a JSON validation report listing any rule failures (EN 16931 BR-* and BR-CO-, plus the CIUS rules, e.g. PEPPOL-EN16931- for Peppol BIS).
Full example: validate.py | API reference
Useful for Pandas pipelines, Django/Flask models, or any system that prefers JSON over XML.
import requests
response = requests.post(
"https://api.invoicexml.com/v1/extract/json",
headers={"Authorization": f"Bearer {api_key}"},
files={"file": ("invoice.xml", open("invoice.xml", "rb"), "application/xml")},
)
invoice = response.json()
# The invoice fields sit at the root of the response.
print(invoice["seller"]["name"], invoice["totals"]["grandTotalAmount"])Full example: extract_json.py | API reference
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 sending it to a customer or tax authority. See the AI conversion notes in the main README.
The endpoint takes the PDF alone; no extra parameters are needed.
import requests
response = requests.post(
"https://api.invoicexml.com/v1/transform/to/ubl",
headers={"Authorization": f"Bearer {api_key}"},
files={"file": ("plain-invoice.pdf", open("plain-invoice.pdf", "rb"), "application/pdf")},
)
with open("converted-ubl.xml", "w", encoding="utf-8") as f:
f.write(response.text)Full example: ai_convert.py | API reference
UBL 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.
import requests
response = requests.post(
"https://api.invoicexml.com/v1/render/ubl/to/pdf",
headers={"Authorization": f"Bearer {api_key}"},
files={"file": ("invoice.xml", open("invoice.xml", "rb"), "application/xml")},
data={"language": "de"}, # en, de, or fr
)
with open("invoice-preview.pdf", "wb") as f:
f.write(response.content)Full example: render.py | API reference
Return a UBL invoice from a view:
from django.http import HttpResponse
def download_ubl(request, invoice_id):
pdf_bytes = create_ubl_for(invoice_id)
response = HttpResponse(pdf_bytes, content_type="application/pdf")
response["Content-Disposition"] = f'attachment; filename="invoice-{invoice_id}.pdf"'
return responseStore the API key in settings.py via INVOICEXML_API_KEY = os.environ["INVOICEXML_API_KEY"].
from flask import send_file
from io import BytesIO
@app.route("/invoices/<int:invoice_id>/ubl")
def get_ubl(invoice_id):
pdf_bytes = create_ubl_for(invoice_id)
return send_file(BytesIO(pdf_bytes), mimetype="application/pdf",
as_attachment=True, download_name=f"invoice-{invoice_id}.pdf")from fastapi.responses import Response
@app.get("/invoices/{invoice_id}/ubl")
async def get_ubl(invoice_id: int):
pdf_bytes = create_ubl_for(invoice_id)
return Response(
content=pdf_bytes,
media_type="application/pdf",
headers={"Content-Disposition": f'attachment; filename="invoice-{invoice_id}.pdf"'},
)The extract_json.py example fits naturally into a Pandas pipeline: walk a folder of UBL PDFs, extract each to JSON, and load into a DataFrame for analysis or bulk archival.
import pandas as pd, requests, os
rows = []
for pdf in os.listdir("invoices/"):
with open(f"invoices/{pdf}", "rb") as f:
data = requests.post(
"https://api.invoicexml.com/v1/extract/json",
headers={"Authorization": f"Bearer {api_key}"},
files={"file": (pdf, f, "application/pdf")},
).json()
rows.append(data)
df = pd.DataFrame(rows)The requests library works in Lambda directly. Include it via Lambda layers or in your deployment zip.
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: settingapi_keyto 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).SSL: CERTIFICATE_VERIFY_FAILEDon macOS: run/Applications/Python\ 3.x/Install\ Certificates.commandto install Python's CA bundle. Do not disable SSL verification in production.ConnectionErroror timeout: add an explicittimeout=30parameter torequests.post(). AI conversion in particular can take 10 to 30 seconds for larger PDFs.- File handle warnings: the examples use inline
open()for brevity. For production code, preferwith open(...) as f:to ensure file handles close deterministically. - PEPPOL-EN16931- failures on Validate*: a Peppol-specific requirement is missing. The most common are a missing electronic address on the seller or buyer, and a missing
buyerReference(Peppol requires a buyer reference or a purchase order reference).