ISDOC.PDF: an XML invoice in a PDF, and the VAT date it can't structure
Czech VAT law lets you put a month of separate supplies on one tax document, as long as each supply states its own date. ISDOC, the national e-invoicing format, has one tax date per document. Move that element onto an invoice line and validation fails:
Element 'TaxPointDate': This element is not expected.
Expected is one of ( Note, VATNote, Item, ... )
A human reading the PDF sees every date. The accounting software importing the XML sees one.
What ISDOC is #
ISDOC is an XML invoice format, free to use, and maintained by the Czech Ministry of the Interior as the “national standard for electronic invoicing.” The current version is 6.0.2 from March 2022. Many accounting programs open it directly, so nobody retypes the invoice. A 2018 Ministry of Finance notice also lists ISDOC 5.2 and later among the formats central government bodies must accept.
A document comes in three representations :
.isdoc: plain XML in UTF-8-isdoc.pdf: the XML embedded in a PDF/A-3, known as ISDOC.PDF.isdocx: an archive with a manifest and attachments
ISDOC.PDF is the practical choice when you don’t know the recipient’s software. It opens like any PDF, and software that understands the format extracts the XML. German ZUGFeRD and French Factur-X use the same idea.
Making an ISDOC.PDF #
The spec’s requirements for the PDF:
- The PDF is PDF/A-3a, and the file name ends in
-isdoc.pdf. - The embedded XML is named exactly
invoice.isdoc, with subtypetext/xml. - Its
/Filespecis listed both in the catalog’s/AFarray and in the/EmbeddedFilesname tree. - The
/Filespechas/F,/UFand/EF, and/EFholds bothFandUFstream entries. /AFRelationshipis/Sourceif the PDF was rendered from the XML, or/Alternativeif both came from your database.
Generate the XML first, since LaTeX has to find it at compile time. With pdflatex, the preamble is short:
\usepackage[a-3b]{pdfx} % needs \jobname.xmpdata next to the .tex
\usepackage{embedfile}
\embedfile[
filespec=invoice.isdoc,
mimetype=text/xml,
afrelationship=Alternative
]{invoice.isdoc}
If you already have a PDF, the official repository
has a Java tool that joins a PDF and an ISDOC file and links to a Ghostscript-based script. Its examples/output folder is the reference to compare against.
The ministry doesn’t run a validator , so check the output yourself:
xmllint --noout --schema isdoc-invoice-6.0.2.xsd invoice.isdoc
pdfdetach -list invoice-isdoc.pdf # 1: invoice.isdoc
pdfdetach -save 1 -o extracted.isdoc invoice-isdoc.pdf
cmp extracted.isdoc invoice.isdoc
pdfinfo -meta invoice-isdoc.pdf | grep pdfaid
pdfinfo invoice-isdoc.pdf | grep Tagged
These commands don’t prove PDF/A conformance. For that, use a validator such as veraPDF. XSD validation also doesn’t run ISDOC’s Schematron rules.
What the checks turned up #
Our generator’s XML validated, and we extracted its attachment byte for byte. The PDF still differs from the spec in two ways, as a comparison with an official sample showed:
| Official sample | pdfx + embedfile | |
|---|---|---|
pdfaid conformance | A | B |
Tagged | yes | no |
/EF keys | F, UF | F |
A tolerant reader won’t care, and pdfdetach didn’t. A strict one could. PDF/A-3a needs a fully tagged document, which this pdflatex setup doesn’t produce.
Three other traps:
- Optional validation can skip silently. Our
--xsdflag printed “validation skipped” and carried on, becauselxmlwas missing. In CI, make a skipped check fail. TaxPointDateis not the issue date. A summary document can go out up to 15 days after the end of the month, so the two differ by design.- Signing happens before embedding. The ISDOC signature is an enveloped XMLDSig inside the XML. The transform removes only the
Signatureelement, so reformatting the file afterward breaks the digest. A signature on the PDF is a separate step and should be an incremental update, which leaves the embedded bytes alone.
What the law requires and the standard lacks #
The summary tax document (souhrnný daňový doklad, § 31b of Act No. 235/2004 Sb.) covers several separate supplies to one customer within a calendar month. Under § 31b(4), it must state separately for each supply its scope, date, unit price, tax base, rate and tax, and for each received payment its date of receipt. Data common to all of them can appear once (§ 31b(3)), so a header date is enough only if every supply happened on the same day.
| VAT Act | ISDOC 6.0.2 | Gap |
|---|---|---|
| § 31b(4)(a)(2): date of each supply | TaxPointDate on Invoice only | No structured field per line |
| § 31b(4)(b)(2): date of each payment | TaxedDeposit has no date | No structured field per payment |
| § 31b: a separate kind of document | DocumentType 1–7, none for it, and the spec’s definition of “tax document” doesn’t list it | Receiver can’t tell from the standard fields |
| § 29(1)(h): date of supply on a tax document | TaxPointDate is optional in the XSD, and only the spec’s prose says to fill it | XSD accepts a document without it |
| § 26(3): customer consents to electronic form | ElectronicPossibilityAgreementReference is mandatory, but empty validates | Presence is checked, consent is not |
| § 34: origin and integrity | Optional XML signature | None: a recognized signature is one route among several |
I tested the per-line and document-type rows by editing a valid file: TaxPointDate on a line, TaxPointDate on an item, and DocumentType 8 each failed the XSD. The payment row comes from reading TaxedDepositType. This is my reading of the statute and the schema, not a ruling or tax advice. Nor does it make an ISDOC.PDF unlawful. If the visible document prints every date, the data is on the document. The gap is that the machine-readable half can’t carry it in a form a recipient’s software will understand.
The spec says its rules should, where possible, be independent of legislation. That is a fair position for validation rules. It doesn’t help when the structure has nowhere to put a value the law requires.
The implementations have the same shape #
Header-only dates aren’t unique to ISDOC. In Odoo 18, account.move.line.date is a read-only related field that mirrors the journal entry’s date, and the Czech localization adds a single taxable_supply_date to the entry, defaulting to today. Modeling this correctly took a dedicated per-line field and a rule that derives the header from it. The upstream issue
also notes that many commercial accounting systems lack per-line dates.
Our generator has its own limits: one VAT rate per invoice, CZK only, no deposits, and no reverse charge.
EN 16931, the European e-invoicing norm, offers a partial precedent. It has an invoice line period (BG-26) with start and end dates. A line period is not a tax date, but it shows that per-line dates fit in an invoice model.
What to do today #
- Print each supply’s date in the visible PDF. That is the part a person, or a tax auditor, reads.
- Set
TaxPointDateto the last supply date. Section 31b doesn’t define a document-level date, but the 2024 VAT control statement instructions ask for “the last (calendar) date” of the supplies on a summary document, so this keeps the two consistent. - Put each date on its own line in a fixed format. Both of these validate:
<Note>Datum plnění: 2026-07-14</Note>
<Item><Description>Work A</Description></Item>
<Extensions>
<x:TaxPointDate xmlns:x="urn:example:line-tax-point">2026-07-14</x:TaxPointDate>
</Extensions>
- Agree on the format with any recipient who needs to import the dates. A standard importer won’t read your note or your namespace.
- If a recipient needs structured dates and can’t parse those, issue one ordinary invoice per date. It costs you the summary document, but the data is exact.
The proper fix looks small: in a new schema version, an optional TaxPointDate on InvoiceLine and a date on payments, with the header kept as it is. Issue #7
reports the missing per-line date and is open.
Takeaway #
XSD-valid doesn’t mean legally complete, and legally complete doesn’t mean importable. Before you adopt an exchange format for regulated data, list what the law requires per record and find the element that holds each field. Any requirement that ends up in a free-text note is one your recipient’s software can’t see.