oneapi
Sign in
Anaf v1 / e-Factura

Validate invoice

A three-layer verdict — XSD, the European EN 16931 rules and the Romanian CIUS-RO ones — for an invoice that already exists. Each error comes with the rule code, so you know exactly what you broke.

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

Request body

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

UBL CIUS-RO invoice

efactura.xml application/xml · 4,0 KB — sent as is, in the request body.

CII invoice

efactura-cii.xml application/xml · 5,5 KB — sent as is, in the request body.

Corrected invoice

efactura-corectata.xml application/xml · 4,0 KB — sent as is, in the request body.

Query parameters

NameTypeDescriptionAccepted values
anafoptional boolean At 1 (or validate=anaf) adds an ANAF-validator cross-check; the confirmation appears in anaf_confirmation. By default, validation is fully local. e.g. 1
viesoptional boolean At 1 checks the buyer's non-RO VAT number against VIES; the result appears in vies and does not affect valid. Optional external call — if VIES does not answer, state: unavailable. e.g. 1

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 an invoice XML in the request body and returns the validation verdict across three cumulative layers: (1) well-formed — the XML parses; (2) an own numeric check that recomputes the totals and reports the exact difference wherever the amounts do not add up; (3) the official EN 16931 + CIUS-RO + RO-eFactura Schematron, run locally through Saxon.
  • The own numeric check (layer 2) catches the mistake that bites most often: a total that does not match the sum of the lines. Instead of a bare "invalid", the response gives the exact difference, so you see at a glance how much and where the arithmetic broke.
  • With ?anaf=1 an ANAF-validator cross-check is added, and its confirmation appears separately in anaf_confirmation. Without it, validation is fully local — the same official Schematron, but with the XML never leaving the process.
  • With ?vies=1 the buyer's non-RO VAT number is additionally checked against VIES (European Commission) — useful for exempt intra-community supplies (category K), where the exemption is only lawful if the partner holds a valid VAT number in another member state. Schematron checks the format; VIES confirms the number actually exists and is active. The result appears separately in vies and does not affect valid. It is an optional external call: if VIES does not answer, the field returns state: unavailable and local validation carries on.
  • 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 and camp. "Invalid" without saying what is wrong does not help someone trying to fix the invoice.
  • cod and the rule quoted in mesaj may disagree — e.g. cod: BR-RO-081 with a message starting [BR-RO-080]. This is not our bug: the official ROeFactura-UBL-validation-Invoice.xslt has distinct assertions (seller address BT-35, buyer address BT-50) with different ids but the same prefix in the text. We pass both through faithfully. Take cod as the rule identifier, not the prefix in the text.
  • Zero retention: the XML received is not written and not logged; validation is stateless.

What it is for

  • Checking an invoice generated elsewhere, before filing it in SPV.
  • Debugging a total that does not add up, using the exact difference from the numeric layer.
  • An ANAF cross-check (?anaf=1) for official confirmation, when local validation is not enough.
  • A VIES confirmation (?vies=1) of the buyer's VAT number on an exempt intra-community supply (category K).

Response structure

FieldTypeDescription
data.valid boolean The cumulative verdict of the three layers.
data.standard string The standard detected from the XML: FACT1 or FCN.
data.schema_version string The schema/standard used for validation, when available (e.g. FACT1 or d390_v3.xsd); otherwise null.
data.errors array The validation errors, each {cod,mesaj,camp}. Empty when valid is true.
data.warnings array Non-blocking warnings, in the same shape.
meta.generat_la string When the response was built (ISO 8601).
data.anaf_confirmation The result of the ANAF cross-check {state,reason,messages,trace_id}; present only at ?anaf=1. state: ok/nok (the ANAF verdict) or unavailable when the validator gave no verdict — then reason says why: timeout, connection, http_, unintelligible_response (usually the maintenance page, served with HTTP 200), circuit_open, throttled_user, throttled_global. unavailable does not mean nok.
data.vies The VIES check result {state,reason,country,number,name}; present only at ?vies=1, for a non-RO buyer. state: valid/invalid/unavailable. On unavailable, reason says why there was no verdict: member_state_unavailable, busy, timeout, connection, http_, unintelligible_response, circuit_open, throttled_user, throttled_global. unavailable does not mean invalid.

Response examples

body: @efactura.xml

POSThttps://oneapi.ro/api/anaf/v1/efactura/validate
{
    "data": {
        "valid": true,
        "standard": "FACT1",
        "schema_version": "CIUS-RO 1.0.1 (EN16931)",
        "errors": [],
        "warnings": []
    },
    "meta": {
        "generat_la": "2026-10-06T23: 59: 42+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.
422xml_invalidThe body is not parsable XML. A structurally valid but non-conformant XML returns 200 with valid: false.
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": "xml_invalid",
        "mesaj": "The body is not parsable XML. A structurally valid but non-conformant XML returns 200 with valid: false.",
        "camp": "anaf",
        "request_id": "0199c1f0-6e1a-7c3d-9b2f-4a8e5d1c7b04"
    }
}