oneapi
Sign in
Anaf v1 / SAF-T (D406)

Validate D406

The official SAF-T XSD's verdict on a D406, plus our own checks that catch what the schema lets through — inconsistent periods and totals that do not add up.

POST
https://oneapi.ro/api/anaf/v1/saft/validate

Request body

The request body is the whole document, sent raw (Content-Type: application/xml) — not an object with fields.

D406 return

saft-d406.xml application/xml · 17,5 KB — sent as is, in the request body.

A quarterly D406

saft-d406-trimestrial.xml application/xml · 17,5 KB — sent as is, in the request body.

An annual D406

saft-d406-anual.xml application/xml · 17,5 KB — sent as is, in the request body.

Query parameters

This route accepts no query parameters.

How it behaves

  • The document can also be sent as a file: multipart/form-data with the fisier field (curl -F fisier=@document.xml), instead of the raw body. The file content becomes the request body, so the result is identical; options stay in the query string, and any other form field next to fisier returns 422 camp_necunoscut.
  • Takes the standard fiscal control file (D406/SAF-T) in the request body and returns the validation verdict: (1) structural — the official ANAF XSD (v2.4.9), validated natively; (2) own checks — root <AuditFile>, presence of <Header> and journal balance (Σ debit = Σ credit).
  • SAF-T has no public Schematron — the official business rules live in the ANAF validator (DUKIntegrator, a jar) which we do not reimplement; the XSD + own checks catch the bulk of structural and arithmetic errors before filing.
  • An invalid XML is not a request error: the response is 200 with valid:false and the list of errors in errors[], each with cod, mesaj, camp.
  • Zero retention: the file received is not written and not logged.

What it is for

  • Checking a D406 generated by accounting software, before filing it in SPV.
  • Catching an unbalanced journal (debit ≠ credit) before ANAF rejects the file.

Response structure

FieldTypeDescription
data.valid boolean The cumulative verdict (XSD + own checks).
data.standard string D406.
data.schema_version string The schema/standard used for validation, when available (e.g. FACT1 or d390_v3.xsd); otherwise null.
data.xsd_checked boolean Whether XSD validation ran.
data.errors array The validation errors {cod,mesaj,camp}. Empty when valid is true.
data.warnings array Non-blocking warnings (e.g. unbalanced journal).
meta.generat_la string When the response was built (ISO 8601).

Response examples

body: @saft-d406.xml

POSThttps://oneapi.ro/api/anaf/v1/saft/validate
{
    "data": {
        "valid": true,
        "standard": "D406",
        "schema_version": "RO SAF-T v2.4.9 (2025)",
        "xsd_checked": true,
        "errors": [],
        "warnings": []
    },
    "meta": {
        "generat_la": "2026-10-06T23: 59: 44+03: 00"
    }
}

Error codes

CodeSlugWhen it occurs
401neautentificatToken missing, invalid, expired or revoked.
403feature_indisponibilThe current plan does not include the feature the route depends on.
413payload_prea_mareThe document exceeds the accepted size limit.
422parametru_necunoscutA query parameter the route does not accept.
429cota_depasitaThe plan's monthly quota has been exceeded (see Retry-After).

Errors in the Anaf module use the same envelope as the rest of the platform, with two extra fields: {eroare: {cod, mesaj, camp, request_id}}. eroare.cod is stable and is what you branch on; on validation (422) the code is case-specific: xml_invalid, xml_nu_e_factura, unitate_necunoscuta, luna_invalida and so on. camp points at the offending field when one can be identified.

{
    "eroare": {
        "cod": "parametru_necunoscut",
        "mesaj": "A query parameter the route does not accept.",
        "camp": null,
        "request_id": "0199c1f0-6e1a-7c3d-9b2f-4a8e5d1c7b04"
    }
}