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.
Request body
The request body is the whole document, sent raw (Content-Type: application/xml) — not an object with fields.
Query parameters
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
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-datawith thefisierfield (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 tofisierreturns422 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=1an ANAF-validator cross-check is added, and its confirmation appears separately inanaf_confirmation. Without it, validation is fully local — the same official Schematron, but with the XML never leaving the process. - With
?vies=1the buyer's non-RO VAT number is additionally checked against VIES (European Commission) — useful for exempt intra-community supplies (categoryK), 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 inviesand does not affectvalid. It is an optional external call: if VIES does not answer, the field returnsstate: unavailableand local validation carries on. - An invalid XML is not a request error. The response is
200withvalid: falseand the list of errors inerrors[], each withcod,mesajandcamp. "Invalid" without saying what is wrong does not help someone trying to fix the invoice. codand the rule quoted inmesajmay disagree — e.g.cod: BR-RO-081with a message starting[BR-RO-080]. This is not our bug: the officialROeFactura-UBL-validation-Invoice.xslthas 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. Takecodas 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 (categoryK).
Response structure
FACT1 or FCN.
FACT1 or d390_v3.xsd); otherwise null.
{cod,mesaj,camp}. Empty when valid is true.
{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.
{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
{
"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"
}
}
body: @efactura-cii.xml
{
"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"
}
}
body: @efactura-corectata.xml
{
"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
| Code | Slug | When it occurs |
|---|---|---|
| 401 | neautentificat | Token missing, invalid, expired or revoked. |
| 403 | feature_indisponibil | The current plan does not include the feature the route depends on. |
| 422 | xml_invalid | The body is not parsable XML. A structurally valid but non-conformant XML returns 200 with valid: false. |
| 429 | cota_depasita | The 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"
}
}