Home/Blog/The ANAF API for CUI lookups in your applications: a complete technical guide

The ANAF API for CUI lookups in your applications: a complete technical guide

A technical guide to the ANAF CUI lookup API: the v9 endpoint, request, response, Python code, address mapping, caching and the pitfalls from practice.

CUI lookup flow through the ANAF API, showing a POST request with CUI and date, found and notFound responses, and caching.

ANAF, the Romanian tax authority, offers a public API, free and without authentication, with which you can find out whether a CUI (the Romanian company tax ID) exists, what the company is called, where it is based and whether it is a VAT payer. It is exactly what you need to autofill the data of a company customer in a checkout, a registration form or an ERP. The official documentation, however, is old, and the URL format has changed the order of its segments compared with most tutorials, so your first hour of work can be lost on a 404.

This guide collects what we learned using this API in several projects, including our autofill-by-CUI plugin and our shipping platform: the correct endpoint, the request and the response, a code example, how to map the address, what to cache and the pitfalls that cost you the most time.

In short: send a JSON POST to https://webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva, with a list of {cui, data} objects. The response has two lists, found and notFound. Do not decide from the HTTP code but from the response body, handle a timeout separately from “CUI not found”, normalise cedilla diacritics and use the registered office address, not the fiscal domicile.

What it is and what data it returns

The service is officially the VAT payers lookup, but the response contains much more than the VAT status. For a CUI that is found you get the company’s general data (name, address as text, trade registry number, registration status, CAEN code, legal form), information about VAT registration, the inactive status, and the registered office and fiscal domicile addresses, structured into fields.

Just as important is what you do not get: the company’s bank and IBAN do not come from this API, and there are no contact details. If you need them, you have to ask the customer. On the other hand, some third-party services that wrapped this API have moved to subscriptions, while ANAF’s official API stays free.

What the request looks like

The request is a POST with a JSON body, a list of objects with the CUI (as a number, not as text) and the date for which you ask the status:

curl -X POST https://webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva \
  -H "Content-Type: application/json" \
  -d '[{"cui": 29798947, "data": "2026-09-07"}]'

ANAF’s documentation indicates a limit of 100 CUIs per request and a rate of about one request per second. For a lookup when a customer registers that is negligible, but if you check large lists, group the CUIs and leave pauses between requests.

What the response looks like

The response contains two lists. For a non-existent CUI you get {"found": [], "notFound": [29798947]}. For one that is found, the found list contains an object with the main sections: date_generale (denumire, adresa, nrRegCom, stare_inregistrare, cod_CAEN, forma_juridica), inregistrare_scop_Tva (with the boolean field scpTVA), stare_inactiv, adresa_sediu_social and adresa_domiciliu_fiscal. The structured addresses have fields such as sdenumire_Localitate, sdenumire_Strada, snumar_Strada, sdenumire_Judet, scod_JudetAuto and scod_Postal.

A code example

The Python example below makes the request, tells a CUI that was not found apart from an infrastructure error and fixes the diacritics:

import datetime
import httpx

ANAF_URL = "https://webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva"
CEDILLA_TO_COMMA = {0x15E: 0x218, 0x15F: 0x219, 0x162: 0x21A, 0x163: 0x21B}


class AnafError(Exception):
    """Infrastructure error: timeout, unexpected response or wrong URL."""


def lookup_cui(cui: int) -> dict | None:
    payload = [{"cui": cui, "data": datetime.date.today().isoformat()}]
    try:
        r = httpx.post(ANAF_URL, json=payload, timeout=8)
    except httpx.HTTPError as e:
        raise AnafError(f"ANAF is not responding: {e}") from e
    try:
        body = r.json()
    except ValueError:
        # a 404 with an HTML body means a wrong URL, not a missing CUI
        raise AnafError(f"Response is not JSON (HTTP {r.status_code})")
    if "found" not in body or "notFound" not in body:
        raise AnafError(f"Unexpected response (HTTP {r.status_code})")
    if body["found"]:
        return body["found"][0]
    return None  # CUI not found: a normal case, not an error


def fix_diacritics(text: str) -> str:
    return text.translate(CEDILLA_TO_COMMA)

The simplicity is deceptive. What matters is that the function returns None for a CUI that was not found but raises an exception for anything else, so your application can show different messages: “we did not find the company” versus “the ANAF service is not responding, try again”.

The 6 pitfalls that cost you time

  1. The endpoint changed compared with the old documentation. The format /PlatitorTvaRest/api/v8/ws/tva consistently answers with a 404. The current one has api before the service name and no longer has /ws/.
  2. A 404 can mean two things. A non-existent CUI returns a 404 with a JSON body, while a wrong URL returns a 404 with an HTML body. Decide from the body, not from the status.
  3. The CUI has to be sent as a number. If you send it as text you get a 400. Likewise, the RO prefix has to be removed first.
  4. Diacritics come with a cedilla. ANAF returns Ş and Ţ, not Ș and Ț. If you compare the text with other sources or print it, normalise it.
  5. The text address comes from the fiscal domicile, not from the registered office. The two addresses can be in different localities. Use the structured fields in adresa_sediu_social as the main source.
  6. notFound contains the CUI as a number, without leading zeros. If you compare as text, normalise by converting to an integer.

How to map the county and locality

For the county, the scod_JudetAuto field holds the county’s licence plate code (for example “AR” for Arad), which matches exactly a county list based on the same convention, with no parsing heuristic. For the locality, sdenumire_Localitate comes with inconsistent prefixes: “Mun. Slatina”, “Orş. Lipova”. Do not try to enumerate the prefixes; strip generically the first word followed by a dot, then look up the locality in the county’s list. Bucharest comes as “Sector 6 Mun. Bucureşti”, a case that does not fit the rule above, so handle it separately. You build the final address from street, number and details, not from the free text of the adresa field.

What to cache

A request on every display of a form is unnecessary. A company’s data changes rarely, so a cache of a few weeks for the responses found is reasonable. For “not found” use a short cache, one hour, so a recent registration is not stuck. The most important rule: never cache an error. A timeout or an unexpected response says nothing about the company, and if you save it, a temporary ANAF problem becomes a permanent “CUI not found” in your application.

How it fits with local validation

The ANAF API confirms the company exists, but it does not tell you whether the CUI is written correctly before the request. Check the control digit locally first, as described in the guide on validating CNP, CUI and IBAN, so you do not send requests for obviously wrong values. If you build integrations between applications and services, API integration development can include this flow end to end, and for complete web application flows see web applications.

Frequently asked questions

Is the ANAF API free?

Yes. The VAT payers lookup endpoint is public, with no account, key or authentication.

Why do I get a 404 at the endpoint from tutorials?

Because the old URL format no longer works. Use /api/PlatitorTvaRest/v9/tva, with api before the service name.

How do I tell a non-existent CUI from an ANAF error?

From the response body: a non-existent CUI appears in the notFound list of a JSON response. Anything else (HTML, timeout, a body without found and notFound) is an infrastructure error.

Do I get the company’s bank and IBAN?

No. This data does not come from this API, so you have to ask the customer for it if you need it.

How long can I cache the responses?

For companies found, a few weeks is reasonable. For “not found”, only an hour. Errors are not cached.

If you want to use ANAF directly in your WooCommerce checkout, our free plugin does it without code.

Want to talk about your project?

Tell us what you need to solve. We come back with concrete ideas and a technical proposal, not a template quote.

or by email: contact@maxdev.ro