oneapi
Autentificare
Anaf v1 / Utilitare cross-zonă

Reconciliere e-Factura ↔ SAF-T

Compară facturile pe care le-ai emis cu ce apare în fişierul SAF-T şi arată nepotrivirile în ambele sensuri: facturi lipsă din D406 şi înregistrări din D406 fără factură.

POST
https://oneapi.ro/api/anaf/v1/reconciliere-facturi

Corpul cererii

Se trimite ca JSON, în corpul cererii (Content-Type: application/json) — nu în şirul de interogare.

NumeTipDescriereValori acceptate
saftobligatoriu string Fişierul SAF-T (D406) faţă de care se face reconcilierea, XML brut ori base64.
facturiobligatoriu string[] | object[] Facturile e-Factura de reconciliat, cel mult 5.000. Un element e fie XML-ul direct (text ori base64), fie obiectul {id, xml}. Cu id, rândurile din lipsa_in_saft, diferente_valoare şi numere_duplicate poartă identificatorul tău. Un element care nu e nici XML, nici {id, xml} întoarce 422 cu poziţia lui.
SAF-T cu o factură netransmisă Corpul cererii
{
    "saft": "@saft-d406.xml",
    "facturi": [
        "@efactura.xml"
    ]
}

În exemplu, @saft-d406.xml, @efactura.xml ţin locul unor documente întregi: trimiţi conţinutul fişierului — XML-ul ca text sau base64, un PDF ca base64 — nu numele lui.

Două facturi față de SAF-T Corpul cererii
{
    "saft": "@saft-d406.xml",
    "facturi": [
        "@efactura.xml",
        "@efactura-stornare.xml"
    ]
}

În exemplu, @saft-d406.xml, @efactura.xml, @efactura-stornare.xml ţin locul unor documente întregi: trimiţi conţinutul fişierului — XML-ul ca text sau base64, un PDF ca base64 — nu numele lui.

Doar o stornare Corpul cererii
{
    "saft": "@saft-d406.xml",
    "facturi": [
        "@efactura-stornare.xml"
    ]
}

În exemplu, @saft-d406.xml, @efactura-stornare.xml ţin locul unor documente întregi: trimiţi conţinutul fişierului — XML-ul ca text sau base64, un PDF ca base64 — nu numele lui.

Parametri de interogare

Ruta nu acceptă parametri de interogare.

Cum se comportă

  • Compară facturile emise (XML-uri e-Factura) cu facturile de vânzare raportate în fișierul SAF-T (D406, SourceDocuments/SalesInvoices), pe numărul de factură. Prinde ce nu se leagă între cele două raportări: facturi într-o parte dar nu în cealaltă, și totaluri (cu TVA) care diferă.
  • Corp JSON: {saft: "<xml>", facturi: ["<xml>", ...]} (XML brut sau base64). Se potrivesc pe număr, iar sumele se compară în bani întregi (fără capcanele virgulei mobile). Pe partea e-Factura se citește TaxInclusiveAmount (BT-112), care are aceeași semantică cu GrossTotal din SAF-T — nu PayableAmount, care scade avansurile încasate și ar produce diferențe false.
  • Dacă același număr apare de două ori pe aceeași parte (tipic o factură și un storno cu același număr), rândul e raportat în numere_duplicate în loc să fie suprascris silențios.
  • Zero retenție și fără SPV — totul e in-memory; nu depune nimic.

La ce folosește

  • Verifici că SAF-T-ul lunii conține exact facturile emise prin e-Factura, cu aceleași sume, înainte de depunere.
  • Găsești rapid factura lipsă sau suma greșită dintre sute de documente.

Structura răspunsului

CâmpTipDescriere
data.are_diferente boolean true dacă există cel puțin o nepotrivire.
data.sumar object Numărători: facturi_efactura, facturi_saft, lipsa_in_saft, lipsa_in_efactura, diferente_valoare, numere_duplicate.
data.sumar.facturi_efactura integer
data.sumar.facturi_saft integer
data.sumar.lipsa_in_saft integer
data.sumar.lipsa_in_efactura integer
data.sumar.diferente_valoare integer
data.sumar.numere_duplicate integer
data.lipsa_in_saft array Facturi emise (e-Factura) care lipsesc din SAF-T [{id?,numar,tip,total}]; id apare doar dacă l-ai trimis.
data.lipsa_in_efactura[] array Facturi din SAF-T fără e-Factură corespondentă [{numar,total}].
data.lipsa_in_efactura[].numar string Numărul de elemente numărate.
data.lipsa_in_efactura[].total integer
data.diferente_valoare array Facturi potrivite dar cu total diferit [{id?,numar,efactura,saft,diferenta}].
data.numere_duplicate array Numere care apar de mai multe ori pe aceeași parte [{id?,numar,sursa,tip}].
meta.generat_la string Momentul la care s-a construit răspunsul (ISO 8601).
meta.request_id string Identificatorul cererii. Dă-l când raportezi o problemă: după el se găseşte răspunsul.

Exemple de răspuns

body: {"saft": "@saft-d406.xml", "facturi": ["@efactura.xml"]}

POSThttps://oneapi.ro/api/anaf/v1/reconciliere-facturi
{
    "data": {
        "are_diferente": true,
        "sumar": {
            "facturi_efactura": 1,
            "facturi_saft": 2,
            "lipsa_in_saft": 0,
            "lipsa_in_efactura": 1,
            "diferente_valoare": 0,
            "numere_duplicate": 0
        },
        "lipsa_in_saft": [],
        "lipsa_in_efactura": [
            {
                "numar": "FCT-2026-0143",
                "total": 1210
            }
        ],
        "diferente_valoare": [],
        "numere_duplicate": []
    },
    "meta": {
        "generat_la": "2026-10-06T23: 59: 41+03: 00",
        "request_id": "01a11303-d87c-724c-9897-cd8c69f79a4e"
    }
}

Coduri de eroare

CodSlugCând apare
401neautentificatToken lipsă, invalid, expirat sau revocat.
403feature_indisponibilPlanul curent nu include funcţia de care ţine ruta.
422xml_invalidLipsește saft sau facturi, sau un document nu e XML valid; peste 5000 de facturi.
429cota_depasitaCota lunară a planului a fost depăşită (vezi Retry-After).

Erorile modulului Anaf au acelaşi plic ca restul platformei, cu două câmpuri în plus: {eroare: {cod, mesaj, camp, request_id}}. eroare.cod e stabil şi e cel după care ramifici în cod; la validare (422) codul e specific cazului: xml_invalid, xml_nu_e_factura, unitate_necunoscuta, luna_invalida ş.a. camp arată câmpul vinovat, când se poate identifica.

{
    "eroare": {
        "cod": "xml_invalid",
        "mesaj": "Lipsește saft sau facturi, sau un document nu e XML valid; peste 5000 de facturi.",
        "camp": null,
        "request_id": "0199c1f0-6e1a-7c3d-9b2f-4a8e5d1c7b04"
    }
}