D406 — SAF-T (Standard Audit File for Tax)¶
The fișierul standard de control fiscal — a large, structured export of a
company's accounting master data and transactions in a fixed OECD-derived
XML shape. This file is a completion guide: what the declaration reports,
who files it and when, and a section-by-section semantic map of the schema —
followed by the technical XSD/DUK layer. This is the best-effort form: the
schema is an order of magnitude larger than any other declaration (873 element
declarations, ~130 KB, root AuditFile) and a real file is produced by the
caller's ERP/accounting system, so the guide maps the reporting sections rather
than authoring a full instance from scratch. ANAF's validator jar
(D406Validator.jar / D406TValidator.jar) and the schema-definition workbook
are the authority; this guide is a distilled bridge, not a re-implementation.
The worked example is the committed minimal instance
tests/fixtures/declaratii/d406t-minimal.xml, re-validated clean on 2026-07-17.
Purpose & legal basis¶
SAF-T replaces the ad-hoc data extracts a tax inspection used to request with a single standardized file: the chart of accounts, customer/supplier masters, the tax and unit-of-measure tables, the full general-ledger journal, and the source documents (sales, purchases, payments, stock movements, asset transactions) for a period. ANAF ingests it into its analytics and reconciles it against the taxpayer's other filings — it is informative (no tax is self-assessed on it), but it exposes the accounting detail behind every other return.
- Legal basis: the SAF-T reporting obligation sits in the Fiscal Procedure Code (Legea 207/2015); the D406 model, structure, and filing procedure are approved by OPANAF 1783/2021 (and subsequent amendments), which ANAF's SAF-T project page cites as the governing order. Failure to file by the deadline is a contravention under art. 337¹ of Legea 207/2015 (fine 1 000–5 000 lei), plus a fine for incorrect/incomplete filing.
- Schema version: the current XSD is
Ro_SAFT_Schema_v249_2025.xsd(schema v2.4.9, republished 19.02.2026), namespacemfp:anaf:dgti:d406t:declaratie:v1, rootAuditFile. The authoritative element table and nomenclatures live in the companion workbookRO_SAFT_SchemaDefCod_16.02.2026.xlsx— consult it, not just the XSD, when composing a real file. - Periodicity: SAF-T is filed monthly by taxpayers on a monthly VAT
fiscal period, and quarterly by taxpayers on a quarterly or annual VAT
period (and by persons not registered for VAT). The
Assetssection is filed annually andStocksonly on ANAF's request — see below.
Who files & when¶
- Who — the obligation was phased in by taxpayer category (the ANAF SAF-T page and ANAF regional notices give the onboarding dates):
| Category | SAF-T obligation from |
|---|---|
| Large taxpayers on 1 Jan 2022 who were also large in 2021 | 1 Jan 2022 |
| Large taxpayers newly classified large on 1 Jan 2022 (not large in 2021) | 1 Jul 2022 |
| Medium taxpayers (as classified on 31 Dec 2021) | 1 Jan 2023 |
| Small taxpayers and non-residents VAT-registered in Romania | 1 Jan 2025 |
From 1 January 2025 the obligation therefore covers all remaining active companies (small and medium included) plus non-residents holding a Romanian VAT code. A handful of categories are exempted or file a reduced set by separate order; the taxpayer's own classification is authoritative.
- Deadline — the last calendar day of the month following the reporting period (the month after a reporting month; the month after quarter-end for a quarterly filer), rolled to the next working day when it falls on a weekend or public holiday. (For December 2025 / Q4 2025 this pushed the deadline to 2 February 2026.)
- The three file variants — one schema, three submission cadences signalled
in the
Header: - Periodic D406 (monthly or quarterly):
Header+MasterFiles+GeneralLedgerEntries+SourceDocuments, minus the asset/stock sections. - Assets (Active) — the
AssetTransactions/ asset master content, filed once a year, by the deadline for filing the annual financial statements (not the monthly deadline). - Stocks (Stocuri) — the
PhysicalStockcontent, filed only when ANAF requests it, within a deadline ANAF sets that may not be shorter than 30 calendar days from the request. - Grace period — the first submissions are penalty-free (an on-ramp for correctly assembling the file), and the grace window shrinks with each period:
- Monthly filers: 6 months grace for the first reference month, then 5, 4, 3, 2 for the next four — i.e. the fifth report is the last with any grace, and from the sixth the standard deadline binds.
- Quarterly filers: 3 months grace for the first quarter (e.g. Q1 2025 filable up to 30 June 2025); from the second quarter the standard deadline binds.
- Corrections — there is no partial amendment. A correction is a
complete corrected file re-submitted for the same period (a rectifying
D406 replacing the prior one); the reporting-period identity in the
Headeris what ties the correction to the original. The exact rectification flag is defined in the schema workbook / filing instructions.
Filling guide¶
Unlike the attribute-only declarations (D300), SAF-T is a deep element tree. anafpy does not author it field-by-field — the ERP does — so the value-add here is a map of the reporting sections onto the wire elements, so an agent can reason about a file the accounting system produced and about what ANAF will reconcile. The full element list is the workbook; the structure/validator gotchas are in the DUK reference — this section is the semantic bridge between them.
Top-level shape. AuditFile has four reporting blocks under the root:
| Block | Carries | SME reality |
|---|---|---|
Header |
Entity identity (Company), reporting period (SelectionCriteria), DefaultCurrencyCode, TaxAccountingBasis (accounting regime: A normal, IFRS, BANK, INSURANCE, IFN, NORMA39, …), and the reporting-type marker |
Always present; small errors here (wrong period, wrong regime code) fail structure validation before any data is read |
MasterFiles |
The reference tables the ledger and documents point at | The bulk of a first file; must be internally consistent with the entries |
GeneralLedgerEntries |
The accounting journal for the period (Journal → Transaction → TransactionLine) with NumberOfEntries / TotalDebit / TotalCredit control totals |
The double-entry backbone; every line references an AccountID and carries TaxInformation |
SourceDocuments |
The commercial documents behind the ledger | Where SAF-T ties to e-Factura / D300 / D394 |
MasterFiles sub-sections (the reference data):
| Element | Reports |
|---|---|
GeneralLedgerAccounts → Account |
Chart of accounts: AccountID, AccountDescription, StandardAccountID (the standardized RO plan code), AccountType, opening/closing balances |
Customers → Customer / Suppliers → Supplier |
Partner masters with CustomerID/SupplierID, AccountID, and a CompanyStructure (name, address, RegistrationNumber) |
TaxTable → TaxTableEntry |
The tax codes used (TaxType 300 = VAT), each TaxCodeDetails giving TaxCode, TaxPercentage, BaseRate (a fraction, 1.00 not 100.00), country |
UOMTable → UOMTableEntry |
Units of measure (UN/ECE codes, H87 = piece) |
Products → Product |
Goods/services catalogue (may be empty for a services-only nil file) |
PhysicalStock |
Inventory positions — populated only in the Stocks variant |
Owners, Assets → Asset |
Ownership / fixed-asset registers — Assets populated in the Assets variant |
AnalysisTypeTable, MovementTypeTable |
Analytic/movement nomenclatures — present but empty in the current wire (their children answer maxOccurs=0; see the DUK reference) |
SourceDocuments sub-sections (the commercial layer):
| Element | Reports | Reconciles against |
|---|---|---|
SalesInvoices → Invoice |
Issued invoices (header + lines + tax) | e-Factura, D300 output VAT, D394 |
PurchaseInvoices → Invoice |
Received invoices | D300 input VAT, D394 |
Payments → Payment |
Receipts/payments (relevant under cash accounting) | D300 exigibility timing |
MovementOfGoods |
Goods movements — present but empty in the current wire | — |
AssetTransactions |
Asset acquisitions/disposals/depreciation | The Assets annual variant |
Identifiers and nomenclatures. Partner identifiers
(CustomerID/SupplierID, partner RegistrationNumber) carry a 2-char
ID-type prefix from the SAF-T nomenclature (00 + CUI for a Romanian company,
check-digit-verified; 080000000000000 is the generic no-ID person code). The
prefix table, the reporting-type codes, the account-type and tax-code lists are
all in the RO_SAFT_SchemaDefCod_*.xlsx workbook — it is the source of
truth for every coded value, and the DUK reference records the structure traps
converged when building the minimal file.
Common scenarios¶
SAF-T is not authored from scratch by anafpy, so there is one worked, DUK-validated example rather than a family of hand-built instances: the committed minimal file. What a real SME file adds is described, not authored.
1 — Minimal / structural instance (the committed fixture
tests/fixtures/declaratii/d406t-minimal.xml). It exercises the full skeleton
with one of everything: a one-account chart, one customer, one supplier, one
VAT TaxTableEntry, one UOM, one balanced two-line general-ledger transaction
(debit + credit, both zero-amount), the present-but-empty
MovementTypeTable/Products/Owners/Assets and SourceDocuments
→MovementOfGoods. It validated clean (ok, zero findings) against
D406TValidator.jar J2.0.6 on 2026-07-17. This is the pipeline proof and the
shape reference; it is not a realistic period (all amounts are zero, one
transaction).
2 — A real monthly SME file (not authored here) adds, on top of that
skeleton: the full chart of accounts with real opening/closing balances,
every customer and supplier touched in the period, the complete
general-ledger journal for the month (with NumberOfEntries/TotalDebit/
TotalCredit matching the lines), and populated SalesInvoices /
PurchaseInvoices / Payments under SourceDocuments. Each Transaction and
each TransactionLine needs both CustomerID and SupplierID; each
AmountStructure needs Amount + CurrencyCode + CurrencyAmount; each line
needs TaxInformation. This is the shape the ERP emits — not DUK-exercised in
this repo beyond the skeleton; the caller's file is validated on its own bytes.
What is and isn't DUK-exercised here. DUK-exercised: the skeleton above
(every section present, one populated GL transaction), under both -v D406T and
— by swapping only the namespace — -v D406. Not exercised: a full-volume
period, populated SalesInvoices/PurchaseInvoices/Payments, the annual
Assets variant, and the on-demand Stocks variant. Those validate on the
ERP's own output; anafpy's role is validate → render → sign → file that output,
which the D406T pipeline proves end-to-end.
Special attention & gotchas¶
The pitfalls below trace either to direct XSD/validator verification (the
skeleton structure rules, the namespace split, the present-but-empty sections,
and the ID-type prefixes were each observed against D406TValidator.jar
J2.0.6 when converging the minimal file — see the DUK reference)
or to an authoritative source (the ANAF SAF-T page, ANAF regional notices,
and OPANAF 1783/2021 for the calendar, categories, and reconciliation). No
uncorroborated practitioner claim is carried.
- The file is ERP-produced, not hand-authored. anafpy validates, renders, signs, and files the accounting system's XML; it does not compose SAF-T the way it composes an invoice. The agent's job is to reason about a file the ERP emitted and warn about deadlines/reconciliation — not to fill elements.
- The grace period shrinks — don't bank on it. For monthly filers it steps down 6→5→4→3→2 months over the first five reports and is gone from the sixth; for quarterly filers it is 3 months for the first quarter only. A filer who treats the first grace window as permanent misses the standard last-day deadline once grace runs out. During grace, late filing is not sanctioned; after it, art. 337¹ applies (1 000–5 000 lei).
AssetsandStocksare separate submissions on different clocks.Assetsrides the annual financial-statements deadline, not the monthly one;Stocksis filed only on ANAF's written request (≥ 30 days). Folding them into the periodic file, or expecting a monthly cadence for them, is a common misconception.- Reconciliation is the real filing risk, not the XML. ANAF cross-checks
SAF-T against D300 (VAT return), D394 (domestic informative), and
e-Factura — the same coherence the pre-filled RO e-TVA decont relies
on. Output VAT in
SalesInvoicesshould tie to D300's collected rows and to issued e-Factura; input VAT inPurchaseInvoicesto D300's deductible rows; per-partner totals to D394. A SAF-T that disagrees with the period's D300/D394 invites a conformity notification even though SAF-T itself assesses no tax. - ID-type prefixes and coded values come from the workbook. A partner
RegistrationNumber/CustomerID/SupplierIDwithout its 2-char SAF-T ID-type prefix fails structure validation;00+CUI is check-digit-verified. Account types, tax codes, reporting-type and unit codes are all closed nomenclatures inRO_SAFT_SchemaDefCod_*.xlsx— never invent a code. - Present-but-empty sections are mandatory, not optional.
MovementTypeTable,AnalysisTypeTable, andSourceDocuments→MovementOfGoodsmust appear as empty elements in the current wire (their children answermaxOccurs=0); omitting the container is a structure error, populating it is another.Products/Owners/Assetsmay be empty in a periodic file. - D406 vs D406T is a namespace, not a content, difference. The identical
body validates under both validators —
-v D406wantsmfp:anaf:dgti:d406:declaratie:v1,-v D406Twants…:d406t:…. D406T is ANAF's sanctioned no-fiscal-effect test declaration (the only permitted production test filing — DESIGN.md §12); a real filing is D406. See the D406 vs D406T section. - Version-alignment trap. SAF-T structure rules are period-versioned
inside the validator jar (parameter tables 2019-01 → 2023-01 → 2024-01 →
2025-07), so a rule that holds for one reporting period can shift for another,
and the workbook's
Linenaming is the newer schema's while the v1 wire element isTransactionLine. Validate against the jar for the actual reporting period; do not assume last year's structure. TheD406Tjars ship only in the dedicatedduk_SAFTdistribution and are not in the DUK update feed, so the staleness check never reports D406T — see the DUK reference.
The hands-on SAF-T work already lives elsewhere — do not duplicate it:
- DUK reference §"The SAF-T module (D406/D406T)" — jar
sourcing (the dedicated
duk_SAFTdistribution; the core-jar update needed to avoidNoClassDefFoundError: dec/DECTagStruct), the structure gotchas converged when building the minimal file (ID-type prefixes, empty-but-presentMovementTypeTable/MovementOfGoods,BaseRateas a fraction,F:fatal findings), and the period-versioned parameters. - The committed minimal instance
tests/fixtures/declaratii/d406t-minimal.xml(in the repo) — re-verified clean 2026-07-17 withD406TValidator.jarJ2.0.6. - portal-upload reference §5 — the live D406T
filing on the production portal (index redacted to
1100000005).
D406 vs D406T (verified 2026-07-17)¶
- Same document content, two namespaces: the identical body validates
clean under both validators —
-v D406wantsmfp:anaf:dgti:d406:declaratie:v1,-v D406Twantsmfp:anaf:dgti:d406t:declaratie:v1. Confirmed by swapping only the namespace on the committed fixture: both returnedok=True, zero findings. - D406T is the sanctioned no-fiscal-effect test declaration (the only permitted production filing — DESIGN.md §12); D406 is the real filing.
- Jar sourcing differs:
D406is in the update feed (D406_35/,J2.2.18);D406Tis not — its jars ship only insideduk_SAFT_*.zip, sofeed_versionsnever reports D406T staleness. - Published schema file
Ro_SAFT_Schema_v249_2025.xsddeclarestargetNamespace mfp:anaf:dgti:d406t:declaratie:v1(the T namespace);TaxAccountingBasisenumerates the entity regimes (A,I,IFRS,BANK,INSURANCE,NORMA39,IFN, ...).
Schema entry points (for orientation)¶
AuditFile → Header (with TaxAccountingBasis, TaxEntity) →
MasterFiles (GeneralLedgerAccounts, Customers, Suppliers, TaxTable,
UOMTable, Products, PhysicalStock, Owners, Assets, and the
present-but-empty MovementTypeTable) → GeneralLedgerEntries →
SourceDocuments (SalesInvoices / PurchaseInvoices / Payments /
MovementOfGoods / AssetTransactions). The
RO_SAFT_SchemaDefCod_*.xlsx workbook is the current, authoritative element
table and nomenclature source — consult it, not just the XSD, when composing
a real file.
Not attempted here¶
A structured authoring path for SAF-T (the way anafpy.efactura.authoring
covers invoices) is out of scope for this survey. SAF-T is normally produced
by the caller's ERP/accounting system; anafpy's role is validate → render →
sign → file the ERP's XML, which the D406T pipeline already proves
end-to-end.