SAFTCheck
← Blog
2026-05-11 · 6 min read

How to validate a SAF-T (PT) before submitting to AT

A pre-submission checklist: encoding, BOM, NIF, ATCUD, header dates, per-document date range, block totals, currency, and document status — what AT catches at intake and how to fix it before day 5.

Why pre-validate at all

The AT portal accepts your SAF-T file. Or rejects it with "ficheiro inválido". There is no middle ground and there is no useful feedback. If you submit on day 5 at 23:50 and the file bounces, you have ten minutes to find the bug and re-export from your ERP. That is the wrong time to discover that your invoice numbering reset, your NIF check-digit is wrong, or your encoding got mangled.

Pre-validation closes the gap. You upload the file to a validator that runs the same XSD plus the heuristics AT actually applies on intake — encoding, BOM, NIF Mod-11, ATCUD format, header dates, document totals, currency rates, document status consistency — and you see every issue with a line number before AT does.

What to validate, in order

  1. Encoding. The single most common silent rejection. AT requires Windows-1252; most ERPs export UTF-8. The XSD parser is fine with both, but AT's intake pipeline is not. See the encoding rule.
  2. BOM. If your ERP prepends a UTF-8 byte-order-mark before <?xml ?>, AT rejects. Same for UTF-16 BOMs.
  3. Schema. The XSD validates the structural shape: required elements, attribute types, enumeration values. This is what AT runs first. schema 1.04_01 is the current SAF-T (PT) version for monthly billing.
  4. NIF Mod-11. Every TaxRegistrationNumber and CustomerTaxID / SupplierTaxID needs a valid Portuguese check digit and an allowed prefix. See the NIF rule.
  5. ATCUD format. Eight uppercase alphanumerics, dash, sequence ≥ 1. Mandatory since 2023.
  6. Header dates. StartDate < EndDate, FiscalYear matches the year of StartDate, DateCreated >= EndDate, EndDate not in the future.
  7. Per-document date range. Every Invoice, Payment, WorkDocument, StockMovement date must fall inside the Header's reporting window.
  8. Block totals. SalesInvoices.NumberOfEntries matches the count of children; TotalDebit + TotalCredit matches the summed GrossTotals within 0.01 €.
  9. Currency. Foreign-currency documents need CurrencyCode + CurrencyAmount + ExchangeRate > 0. Missing rate is invisible to the XSD; AT rejects on intake.
  10. Document status. InvoiceStatus in {N, S, A, R, F}; InvoiceStatusDate >= InvoiceDate.

What pre-validation does NOT catch

Be honest with yourself: a validator can confirm the file is well-formed and consistent. It cannot guarantee that AT will accept it. The two things it cannot see:

  • Server-side AT business rules that change without notice. AT occasionally tightens intake rules without updating the public XSD. Schema-change monitors and community advisories help — but new rejection patterns sometimes emerge first in real submissions.
  • Account-level constraints. Submission window already used, taxpayer mid-suspension, certified-software registration mismatch — none of those are file-level issues; they are taxpayer-level.

For the first class, post-rejection feedback loops still matter. For the second, your accountant or AT support is the right channel.

Workflow for the day-5 deadline

  1. Export the monthly SAF-T from your ERP a day early — ideally day 3 of the next month.
  2. Run it through a pre-validator. Fix every error reported. If the encoding rule fires, run the auto-fix and re-validate.
  3. Re-export only when a structural issue is reported. Re-running pre-validation on the same broken file twice gets the same answer.
  4. Submit to AT once the validator returns clean. Save the PDF report as part of your submission folder.

Ready?

Upload your SAF-T XML and see what AT will reject — before you submit. Validate now →

Other posts

Usamos um cookie de sessão para autenticação, um cookie de idioma para a preferência linguística e o Tawk.to para o widget de chat ao vivo (que define os seus próprios cookies quando abre o chat). O Google Analytics (GA4) só é carregado depois de aceitar, com anonimização de IP; sem rastreadores publicitários. Consulte a nossa Política de Privacidade.