Acasă/Blog/API-ul ANAF pentru verificarea CUI în aplicațiile tale: ghid tehnic complet

API-ul ANAF pentru verificarea CUI în aplicațiile tale: ghid tehnic complet

Ghid tehnic pentru API-ul ANAF de verificare CUI: endpoint-ul v9, cererea, răspunsul, cod Python, maparea adreselor, cache și capcanele din practică.

Flux de verificare CUI prin API-ul ANAF: cerere POST cu CUI și dată, răspunsuri found și notFound și cache.

ANAF oferă un API public, gratuit și fără autentificare, cu care poți afla dacă un CUI există, cum se numește firma, unde are sediul și dacă este plătitoare de TVA. Este exact ce îți trebuie ca să completezi automat datele unui client-firmă într-un checkout, într-un formular de înregistrare sau într-un ERP. Documentația oficială e însă veche, iar formatul URL-ului a schimbat ordinea segmentelor față de ce găsești în majoritatea tutorialelor, deci prima ta oră de lucru poate să se piardă pe un 404.

Ghidul adună ce am învățat folosind acest API în mai multe proiecte, printre care pluginul nostru de completare automată după CUI și platforma noastră de expedieri: endpoint-ul corect, cererea și răspunsul, un exemplu de cod, felul în care mapezi adresa, ce pui în cache și capcanele care te costă cel mai mult timp.

Pe scurt: trimiți un POST JSON către https://webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva, cu o listă de obiecte {cui, data}. Răspunsul are două liste, found și notFound. Nu decide după codul HTTP, ci după corpul răspunsului, tratează timeout-ul separat de „CUI negăsit”, normalizează diacriticele cu sedilă și folosește adresa sediului social, nu domiciliul fiscal.

Ce este și ce date returnează

Serviciul se numește oficial verificarea plătitorilor de TVA, dar răspunsul conține mult mai mult decât statutul de TVA. Pentru un CUI găsit, primești datele generale ale firmei (denumire, adresă ca text, număr de înregistrare la registrul comerțului, starea înregistrării, cod CAEN, forma juridică), informații despre înregistrarea în scopuri de TVA, starea de inactiv, precum și adresa sediului social și cea a domiciliului fiscal, structurate pe câmpuri.

La fel de important este ce nu primești: banca și IBAN-ul firmei nu vin din acest API, iar nu găsești date de contact. Dacă ai nevoie de ele, trebuie să le ceri de la client. Pe de altă parte, unele servicii terțe care împachetau acest API au trecut la abonament, în timp ce API-ul oficial al ANAF rămâne gratuit.

Cum arată cererea

Cererea este un POST cu corp JSON, o listă de obiecte cu CUI-ul (ca număr, nu ca text) și data pentru care întrebi starea:

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

Documentația ANAF indică o limită de 100 de CUI-uri pe cerere și o rată de aproximativ o cerere pe secundă. Pentru un lookup la înregistrarea unui client, asta e neglijabil, dar dacă verifici liste mari, grupează CUI-urile și lasă pauze între cereri.

Cum arată răspunsul

Răspunsul conține două liste. Pentru un CUI inexistent, obții {"found": [], "notFound": [29798947]}. Pentru unul găsit, lista found conține un obiect cu secțiunile principale: date_generale (denumire, adresa, nrRegCom, stare_inregistrare, cod_CAEN, forma_juridica), inregistrare_scop_Tva (cu câmpul boolean scpTVA), stare_inactiv, adresa_sediu_social și adresa_domiciliu_fiscal. Adresele structurate au câmpuri precum sdenumire_Localitate, sdenumire_Strada, snumar_Strada, sdenumire_Judet, scod_JudetAuto și scod_Postal.

Un exemplu de cod

Exemplul de mai jos, în Python, face cererea, distinge un CUI negăsit de o eroare de infrastructură și corectează diacriticele:

import datetime
import httpx

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


class AnafError(Exception):
    """Eroare de infrastructura: timeout, raspuns neasteptat sau URL gresit."""


def cauta_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 nu raspunde: {e}") from e
    try:
        body = r.json()
    except ValueError:
        # un 404 cu corp HTML inseamna URL gresit, nu CUI inexistent
        raise AnafError(f"Raspuns care nu e JSON (HTTP {r.status_code})")
    if "found" not in body or "notFound" not in body:
        raise AnafError(f"Raspuns neasteptat (HTTP {r.status_code})")
    if body["found"]:
        return body["found"][0]
    return None  # CUI negasit: caz normal, nu eroare


def repara_diacritice(text: str) -> str:
    return text.translate(SEDILA_LA_VIRGULA)

Simplitatea e înșelătoare. Partea care contează e că funcția întoarce None pentru un CUI negăsit, dar aruncă o excepție pentru orice altceva, ca aplicația ta să poată arăta mesaje diferite: „nu am găsit firma” față de „serviciul ANAF nu răspunde, încearcă din nou”.

Cele 6 capcane care te costă timp

  1. Endpoint-ul s-a schimbat față de documentația veche. Formatul /PlatitorTvaRest/api/v8/ws/tva răspunde constant cu 404. Cel actual are api înaintea numelui serviciului și nu mai are /ws/.
  2. Un 404 poate însemna două lucruri. Un CUI inexistent dă 404 cu corp JSON, iar un URL greșit dă 404 cu corp HTML. Decide după corp, nu după status.
  3. CUI-ul trebuie trimis ca număr. Dacă îl trimiți ca text, obții 400. Tot așa, prefixul RO trebuie scos înainte.
  4. Diacriticele vin cu sedilă. ANAF returnează Ş și Ţ, nu Ș și Ț. Dacă compari textul cu alte surse sau îl tipărești, normalizează-l.
  5. Adresa text vine din domiciliul fiscal, nu din sediul social. Două adrese pot fi în localități diferite. Folosește câmpurile structurate din adresa_sediu_social ca sursă principală.
  6. notFound conține CUI-ul ca număr, fără zerouri la început. Dacă compari ca text, normalizează prin conversie la întreg.

Cum mapezi județul și localitatea

Pentru județ, câmpul scod_JudetAuto conține codul auto al județului (de exemplu „AR” pentru Arad), care se potrivește exact cu un nomenclator de județe bazat pe aceeași convenție, fără nicio euristică de parsare. Pentru localitate, sdenumire_Localitate vine cu prefixe inconsistente: „Mun. Slatina”, „Orş. Lipova”. Nu încerca să enumeri prefixele; scoate generic primul cuvânt urmat de punct, apoi caută localitatea în lista județului. Bucureștiul vine ca „Sector 6 Mun. Bucureşti”, un caz care nu se potrivește cu regula de mai sus, deci tratează-l separat. Adresa finală o construiești din stradă, număr și detalii, nu din textul liber al câmpului adresa.

Ce pui în cache

O cerere pe fiecare afișare a unui formular nu e necesară. Datele unei firme se schimbă rar, așa că un cache de câteva săptămâni pentru răspunsurile găsite este rezonabil. Pentru „negăsit” folosește un cache scurt, de o oră, ca o înregistrare recentă să nu rămână blocată. Regula cea mai importantă: nu pune niciodată în cache o eroare. Un timeout sau un răspuns neașteptat nu spune nimic despre firmă, iar dacă îl salvezi, o problemă trecătoare a ANAF devine un „CUI negăsit” permanent în aplicația ta.

Cum se leagă cu validarea locală

API-ul ANAF confirmă că firma există, dar nu îți spune dacă CUI-ul e scris corect înainte de cerere. Verifică întâi cifra de control local, așa cum e descris în ghidul despre validarea CNP, CUI și IBAN, ca să nu trimiți cereri pentru valori evident greșite. Dacă faci integrări cu aplicații și servicii, dezvoltarea de integrări API poate include acest flux de la capăt la capăt, iar pentru fluxuri complete de aplicații web vezi aplicațiile web.

Întrebări frecvente

API-ul ANAF este gratuit?

Da. Endpoint-ul de verificare a plătitorilor de TVA este public, fără cont, cheie sau autentificare.

De ce primesc 404 la endpoint-ul din tutoriale?

Pentru că formatul vechi al URL-ului nu mai funcționează. Folosește /api/PlatitorTvaRest/v9/tva, cu api înaintea numelui serviciului.

Cum disting un CUI inexistent de o eroare a ANAF?

După corpul răspunsului: un CUI inexistent apare în lista notFound a unui răspuns JSON. Orice altceva (HTML, timeout, corp fără found și notFound) e o eroare de infrastructură.

Primeșc banca și IBAN-ul firmei?

Nu. Aceste date nu vin din acest API, deci trebuie să le ceri de la client, dacă ai nevoie de ele.

Cât timp pot pune în cache răspunsurile?

Pentru firme găsite, câteva săptămâni sunt rezonabile. Pentru „negăsit”, doar o oră. Erorile nu se pun în cache.

Dacă vrei să folosești ANAF direct în checkout-ul tău WooCommerce, pluginul nostru gratuit face asta fără cod.

Vrei să discutăm despre proiectul tău?

Spune-ne ce ai de rezolvat. Revenim cu idei concrete și o propunere tehnică, nu cu o ofertă șablon.

sau pe email: contact@maxdev.ro