Facturae in Python
The electronic invoice XML Spanish law makes mandatory, generated from Python.
Open-source libraries · Python · Code published
The problem
Royal Decree 238/2026, of 25 March, turns business-to-business electronic invoicing from an option into an obligation. The format is neither a PDF nor JSON: it is an XML with a fixed structure, amounts that must reconcile with each other, and fields rejected if their shape is wrong. Hand-writing it works until the first invoice with two VAT rates.
What it does
- Generates Facturae XML from invoice data written as plain Python structures.
- Computes the totals from the lines — quantity times price — instead of taking them as given.
- Validates the fields that get rejected on submission: Spanish postcode, alpha-2 country code and tax blocks.
- Fails when a key is misspelled, rather than emitting an XML that is silently missing that field.
What was decided while building it
- Totals are computed, never taken as given
- If the caller could pass the total, sooner or later they would pass one that disagrees with the lines, and the resulting XML would be valid to a parser and rejected at the far end. The total always comes from the lines. It is less flexible and that is exactly the point: there is no way to write an invoice that disagrees with itself.
- A misspelled key stops the process
- A dictionary accepts any key, so a typo does not raise an error: it produces an XML missing that field, which looks fine and gets rejected weeks later. The library checks the keys and fails there and then, while somebody is still looking.
- Amounts use decimal, not floating point
- Money in floating point produces the cent that does not add up, and on an invoice that cent is a rejection. The standard library decimal type is used instead: exact in base ten, and the reason the library needs no dependency at all for reliable arithmetic.
How far it goes
- The XML comes out unsigned. Facturae requires an XAdES signature before submission to FACe, and this library does not provide it.
- It does not validate against the official schema at runtime: it checks what it checks, and what that is is written down.
- It transmits nothing. Producing the document and filing it are two different problems and only the first is here.
- It is not tax or legal advice. What must be invoiced, and when, is out of scope.
- 0 dependencies
- MIT licence
- 3.9+ Python
Built with
- Python
- ElementTree
- decimal