You build and validate the D declarations, in one call.
You build a D declaration’s XML from a business JSON — with the totals calculated for you —, validate it against the official ANAF XSD for that form, read it back as JSON and render it as a PDF. One API parameterised by form, without storing anything.
The API is a registry of forms, and it is extensible: 13 D tax declarations are live today — from the VAT returns (D300, D301, D311, D390, D394) to taxes and income (D101, D205, D212), payroll (D112), notifications (D100, D700, D710) and the redirection of 3.5% of income tax (D230). D390 (the VIES recapitulative statement) is only one example. Processing happens in memory; filing in SPV stays with you.
// Content-Type: application/json
{ "luna": 7, "an": 2026, "rectificativa": false,
"declarant": { "cui": "RO12345678", "den": "ACME SRL" },
"operatiuni": [
{ "tip": "L", "tara": "DE", "cod": "DE811234567", "baza": 18400 },
{ "tip": "A", "tara": "FR", "cod": "FR40303265045", "baza": 9250 } ] }
// → { formular: "D390", xml: "<Declaratie390 …>",
// totaluri: { livrari_L: 18400, achizitii_A: 9250, nr_randuri: 2 } }
GET /declaratii/v1/forms // the registry of supported forms
{ "forms": [
{ "cod": "D390", "nume": "Recapitulativă VIES", "status": "live" },
{ "cod": "D300", "nume": "Decont TVA", "status": "live" },
{ "cod": "D394", "nume": "Informativă", "status": "live" }
// … D100, D101, D112, D205, D212, D301, D700, D710 — all live ] }
- Forms
- 13 D declarations (D390 VIES…)
- Routes
- build · validate · parse · render
- Registry
- /forms · /codes/{list}
- XSD
- official ANAF, per form
- Retention
- none
A correct D declaration is more than an XML. And it is not your product.
Between the data in your accounts and a D390 that ANAF accepts at filing sits a layer of rules — XSD structure, totals by operation type, validated partner codes — that someone has to build and check.
The form, as XML
D390 is not a web form, it is an XML with a strict structure: a header with the period and the declarant, one row per intra-community partner, with operation type, country, VAT code and base. Order and types matter.
Totals by type
The recapitulative statement wants amounts grouped by operation type — supplies, acquisitions, services, triangular. A wrong sum or a row filed under the wrong type, and the declaration does not match the VAT return.
Partner codes
Every row carries an intra-community VAT code, with a country prefix. An invalid code, or one that does not match the country, slips past a human but fails at validation — and often you only find out at filing.
The official schema
The structure is checked against the official ANAF XSD for the form. A missing element, a wrong type, a broken cardinality, and the file is rejected.
Corrective filings
A declaration filed wrong is corrected with a corrective filing, with its own marking rules. It has to be built as rigorously as the original, not improvised.
Rules that change
The schemas get revised, forms are added and amended. What passed last year can fail today, and you find out from the rejection, not from an announcement.
Four operations and a registry of forms.
Each is an independent POST call, parameterised by form. You build, validate, read and render — and list the supported forms. You use the one you need.
/{form}/build
Build
You send a business JSON and get the declaration XML. The totals by operation type are calculated from the rows, you do not carry them yourself. You come out with a file valid against the form’s schema.
/{form}/validate
Validate
You send an existing XML and get the verdict. We check it against the official ANAF XSD for the form plus checks of our own — header, totals, partner codes. You get what was checked, the errors and the warnings.
/{form}/parse
Read
A declaration XML becomes clean JSON: the header (declarant, period, corrective flag) and the rows, with the totals by type. You read it without touching the XML.
/{form}/render
Render
From the declaration comes a human-readable PDF, generated locally — the header, the rows and the totals by type, for the file or for a review, without an external rendering service.
/forms
Form registry
The list of supported forms, with the status of each — all 13 D declarations live today, with the status read from GET /forms. One place to find out what you can build, without guessing.
/codes/{list}
Code lists
The official lists the forms use — country codes, operation types — served as a reference, so you can check from the form before building.
Your tax data goes nowhere. Not to us, not to ANAF.
This is the answer to “why would I send my tax data to a third party”. Because, in practice, you are not sending it anywhere — we process and forget.
Processed in memory
The JSON and the XML are never written to disk. They arrive in the request, are built, validated or read in memory and are gone when the response leaves. There is no declaration of yours at our end.
Local validation
The official XSD and our own checks run on our side, on the server holding your response. We do not send the declaration to ANAF to find out whether it is valid — the rules are here, the verdict is local.
Logs without content
What we keep is a result code and a duration — that is all. The VAT number, the partners, the amounts, the VAT codes go into no log, neither ours nor an observability service’s.
The result only
An optional header tells us to return only the verdict or the totals, without the rows in the clear. For integrations where you want the build or the validation but would rather the data did not travel back through the response.
We do not just say “invalid”. We say exactly what and where.
We run the form’s official ANAF XSD locally, plus checks of our own, and give you the diagnosis before filing — by field and by row.
The official XSD schema
We check the structure against the official ANAF XSD for the form: elements, types, order, cardinalities. If a mandatory field is missing or a type does not match, we catch it before it ever reaches SPV.
Our own checks
On top of the XSD we run our own checks: a complete and coherent header, the totals by operation type, VAT codes with a valid country prefix. The things that pass the schema and still make the declaration wrong.
Diagnosis, not just a verdict
The response tells you what was checked, whether the declaration is valid, plus the list of errors and warnings with the rule and the row involved. The difference between knowing there is a problem and knowing where it is.
No filing in SPV
Validation is entirely local: by default, nothing leaves us for ANAF. Filing D390 through SPV stays with you, using your own credentials — we give you the verdict and the totals before that step.
Thirteen D declarations, all live, on the same pattern.
The whole suite of D forms live now, each with build, validate, parse and render, and with the totals calculated for you.
We cover the complete suite of D declarations — from the VAT returns to the unified return —, each with its official XSD, totals and corrective filings. We add new forms on the same pattern — if you need one that is not live yet, write to us — real cases take priority in what we build.
A business JSON in. Valid XML and the totals out.
You send the JSON and get the XML with the calculated totals — or, on validate, the verdict. No mandatory SDK — the HTTP client you already have.
# build: a business JSON → valid D390 XML + totals calculated for you
curl "https://oneapi.ro/api/anaf/v1/declaratii/D390/build" \
-H "Authorization: Bearer $ONEAPI_TOKEN" \
-H "Content-Type: application/json" \
-d @d390_2026_07.json
# → 200 OK
{
"formular": "D390",
"xml": "<Declaratie390 …>",
"totaluri": { "livrari_L": 18400, "achizitii_A": 9250, "nr_randuri": 2 }
}
What was checked, the rule and the row
A validation response does not just say “invalid”. It tells you what was checked — the form’s XSD and our own checks — whether it is valid, plus the errors and warnings with the rule and the row involved. Enough to fix it first time instead of guessing.
HTTP/1.1 422 Unprocessable Content
{ "valid": false,
"formular": "D390",
"xsd_checked": "v3.0",
"errors": [ {
"regula": "D390-COD-01",
"mesaj": "Cod TVA partener invalid pentru țara DE.",
"camp": "operatiuni[0].cod"
} ], "warnings": [] }
Before you file the first D390.
The rest, in the frequently asked questions and in the documentation.