LedgerdropPT

Invoice QR β†’ spreadsheet

Portuguese invoice PDFs, as a spreadsheet

Between businesses in Portugal there is no e-invoice XML: SAF-T is a monthly export of the whole ledger, not a document. What arrives is a PDF β€” and printed on it is the mandatory QR code, with the entire invoice inside.

Drop the files here β†’

Mandatory QRATCUDNIF check digitPeppol CIUS-PT

01 β€” What is actually in the code

The whole invoice, field by field

The QR defined by Despacho n.ΒΊ 8632/2022 is not a URL. It is text with fixed fields: A the issuer's NIF, B the customer's, F the date, G the document number, H the ATCUD, blocks I/J/K the taxable bases and VAT per band (mainland, Azores, Madeira), and O the total.

It has been mandatory on every invoice since 2022, which makes it the one piece of structured data that genuinely exists on all of them. So it is read from there rather than guessed from the page layout β€” every issuer designs the page differently, and the code is identical on all of them.

02 β€” One number the code does not carry

The VAT rate is computed, not copied

The code names the band β€” reduced, intermediate, normal β€” never the percentage. And the same band is 23% on the mainland, 22% in Madeira and 16% in the Azores, figures that still move with each budget.

So the rate comes out of the invoice itself: VAT divided by base. The result is only written down if it lands on a band currently in force; if it does not, the column shows ?%. Filling it from a fixed table would present what the table said as what the invoice said β€” and would be wrong the day after a change, with every figure still looking right.

ATCUD in a column of its own

The ATCUD identifies the document to the tax authority, and that is what a Portuguese accountant reconciles on β€” not the internal invoice number. It gets its own column next to the number; merging the two loses the one that is used for checking.

The NIF check digit is recomputed too (weights 9…2, mod 11). A wrong digit raises no error anywhere: the invoice simply never appears in the account of whoever should have had it.

999999990 is not a customer

On invoices to a final consumer the customer VAT field carries 999999990. Copied through, the ledger fills with rows for the "same" customer, and duplicate detection starts pairing invoices that have nothing to do with each other. Here it is left empty, which is what it means.

Credit notes (NC) carry positive amounts; only the type says the money is going back. They are subtracted in the total at the bottom.

The one column that stays empty

The code carries the supplier's NIF, never their name β€” so the supplier column is blank on these rows. The name is printed on the page, but reading it out of free text means deciding which line is the company and which is the address, and being wrong sometimes. A blank cell you can fill in beats a plausible name that belongs to the wrong company.

The NIF is in the row, so a lookup or a one-off find-and-replace fills the column in seconds β€” and it stays correct.

What is not done here

SAF-T (PT) is not read: it is a monthly export of an entire ledger, a different problem and a different format. Invoices to public bodies sent over Peppol CIUS-PT are read like any other European UBL β€” those are XML.

A scanned or photographed invoice with no text layer is not read either: if the QR is legible it is used, otherwise the row comes out empty and marked, never guessed.

Trying it

Drop files on the main page β€” up to 10 invoices per export and 20 a day, free, no account, and the count resets at midnight. Pro lifts both limits for 29 € once, with no renewal.

Nothing is uploaded

The files are read in the browser. There is no server they could go to, and it takes two minutes to confirm: open the developer tools, the Network tab, and drop an invoice in β€” no request goes out. Which countries are read.