Un endpoint pentru fișa firmei, unul pentru căutare, câteva validări și cursul BNR. Cheia în antet, JSON înapoi. Dacă știi curl, știi deja tot.
Bază: https://api-cui.ro/v1. Toate răspunsurile sunt JSON, UTF-8, cu diacritice reale (nu ș). Datele sunt ISO 8601 (2026-09-21).
curl https://api-cui.ro/v1/firma/14399840 -H "X-Api-Key: ck_..."
Cheia o iei din cont, în 30 de secunde. Contul gratuit e o probă de 30 de zile cu 50 de cereri.
Cheia se trimite în antetul X-Api-Key (sau Authorization: Bearer <cheie>). Sunt două feluri de chei:
| tip | prefix | unde | protecție |
|---|---|---|---|
| secretă | ck_ | pe server (ERP, facturare, importuri) | nu o trimite niciodată în URL sau în browser |
| publică | pk_ | în browser (checkout, formulare) | merge doar de pe domeniile aprobate în panou (verificăm Origin); poate fi trimisă și ca ?cheie=pk_... |
| de test | tk_ | dezvoltare, teste automate | nu consumă cotă; date reale pentru CUI 14399840, 13548146, 6859662, 18547290, 47310601 și fictive (deterministe, FIRMA TEST … S.R.L., meta.test: true) pentru orice alt CUI valid |
Răspunsurile pentru cheile publice includ antetele CORS pentru domeniul de origine. Cheile se pot roti din cont: cea nouă apare imediat, cea veche mai merge 24 de ore.
Fișa firmei. {cui} acceptă și forma RO14399840 sau cu spații. Se validează cifra de control înainte de orice.
| câmp | ce e |
|---|---|
cui, cui_ro | numărul; cui_ro are prefixul RO când firma e plătitoare de TVA |
denumire, nr_reg_com, cod_caen, forma_juridica, forma_organizare, forma_proprietate, organ_fiscal, telefon, fax | identificare, așa cum le are ANAF |
stare.activa | true dacă nu e radiată și nu e inactivă fiscal |
stare.text, stare.data_inregistrare, stare.radiata, stare.data_radiere, stare.inactiv_fiscal, stare.data_inactivare, stare.data_reactivare | detaliile stării |
tva.platitor, tva.de_la, tva.pana_la, tva.perioade[] | înregistrarea în scopuri de TVA |
tva.la_incasare (+ _de_la, _pana_la), tva.split | TVA la încasare, split TVA |
efactura, efactura_de_la | înregistrarea în Registrul RO e-Factura |
adresa_sediu, adresa_fiscala | obiecte cu judet, judet_auto, localitate, strada, numar, detalii, cod_postal, cod_judet, cod_localitate, tara și text (pe o linie) |
adresa_anaf | textul brut al ANAF, pentru comparație |
verificat_la | când am citit ultima oară fișa de la ANAF |
meta.ms | cât a durat pe server |
Adaugă, în același răspuns: bilanturi[] (ultimii 3 ani: an, caen, cifra_afaceri, profit_net, venituri, cheltuieli, angajati, datorii, capitaluri, active), registru (cod_j, stare ONRC, data_inmatriculare, euid, web), persoane[] (nume, calitate — administratori, asociați), contacte[] (fel: email / telefon / site, valoare) și bani_publici (achiziții directe și contracte din SEAP, în lei).
curl "https://api-cui.ro/v1/firma/14399840?extins=1" -H "X-Api-Key: ck_..."
Până la 100 de CUI-uri într-o singură cerere — pentru importuri, ERP, verificări periodice. Corp JSON: {"cui": ["14399840", "RO13548146", …], "extins": false}. Răspuns: {"firme": {"14399840": {…}, …}, "lipsa": ["…"], "invalide": ["…"]}. Fiecare CUI găsit consumă o cerere din cotă (cele invalide/lipsă nu); dacă lotul depășește cota rămasă primești 429 cota_insuficienta înainte de a consuma ceva. Disponibil pe planurile plătite.
curl -X POST https://api-cui.ro/v1/firme -H "X-Api-Key: ck_..." -H "Content-Type: application/json" \
-d '{"cui": ["14399840", "13548146", "6859662"]}'
Fără cod: incluzi scriptul cu o cheie publică și marchezi câmpurile cu data-api-cui. Când clientul scrie un CUI valid în câmpul cui, restul se completează.
<script src="https://api-cui.ro/static/precompletare.js" data-cheie="pk_..."></script> <input data-api-cui="cui" placeholder="CUI"> <span data-api-cui-stare></span> <input data-api-cui="denumire"> <input data-api-cui="nr_reg_com"> <input data-api-cui="adresa_sediu.strada_nr"> <input data-api-cui="adresa_sediu.oras"> <input data-api-cui="adresa_sediu.judet"> <input data-api-cui="adresa_sediu.cod_postal"> <input type="checkbox" data-api-cui="tva.platitor"> plătitor de TVA
Valoarea din data-api-cui e calea din răspunsul JSON (adresa_sediu.strada, tva.platitor, cod_caen…) plus trei scurtături: adresa_sediu.strada_nr (stradă + număr), adresa_sediu.oras (localitatea curățată de „Mun.”, „Orș.”) și tva.platitor_text („da”/„nu”). Merge cu <select> (potrivește după valoare sau text) și cu checkbox. Evenimentul api-cui:firma pe document primește tot răspunsul, dacă vrei logică proprie. Cheia publică trebuie să aibă domeniul site-ului în panou.
Gata de copiat. Cheia secretă (ck_) stă pe server, citită din configurare, nu scrisă în cod. Specificația completă e în /openapi.json (OpenAPI 3): o poți importa în Postman, Insomnia sau într-un generator de client.
$ch = curl_init('https://api-cui.ro/v1/firma/' . rawurlencode($cui));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPHEADER => ['X-Api-Key: ' . getenv('APICUI_KEY')],
]);
$firma = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200) {
echo $firma['denumire'], ' · ', $firma['adresa_sediu']['text'];
}
const r = await fetch(`https://api-cui.ro/v1/firma/${encodeURIComponent(cui)}`, {
headers: { 'X-Api-Key': process.env.APICUI_KEY },
});
if (r.ok) {
const firma = await r.json();
console.log(firma.denumire, firma.tva.platitor);
}
import os, requests
r = requests.get(f"https://api-cui.ro/v1/firma/{cui}",
headers={"X-Api-Key": os.environ["APICUI_KEY"]}, timeout=5)
if r.ok:
firma = r.json()
print(firma["denumire"], firma["stare"]["activa"])
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("X-Api-Key", Environment.GetEnvironmentVariable("APICUI_KEY"));
var r = await http.GetAsync($"https://api-cui.ro/v1/firma/{Uri.EscapeDataString(cui)}");
if (r.IsSuccessStatusCode)
{
using var doc = System.Text.Json.JsonDocument.Parse(await r.Content.ReadAsStringAsync());
Console.WriteLine(doc.RootElement.GetProperty("denumire").GetString());
}
Toate exemplele merg la fel cu cheia de test tk_, care nu consumă din cotă.
=CUI()Pui CUI-urile într-o coloană și primești denumirea, adresa, TVA-ul sau orice alt câmp, direct în foaie. Fără supliment de instalat: un script de 20 de rânduri.
1. În foaie: Extensii → Apps Script. 2. Ștergi ce e acolo, lipești codul de mai jos și pui cheia ta. 3. Salvezi și te întorci în foaie.
const CHEIE = 'ck_...'; // cheia ta secretă
/**
* Date despre firmă din CUI, de la api-cui.ro.
* @param {string} cui CUI-ul, cu sau fără RO
* @param {string} camp câmpul din răspuns: denumire (implicit), nr_reg_com,
* adresa_sediu.text, adresa_sediu.judet, tva.platitor, stare.activa, cod_caen...
* @customfunction
*/
function CUI(cui, camp) {
if (!cui) return '';
const cheieCache = 'cui_' + String(cui).replace(/\D/g, '');
const cache = CacheService.getScriptCache();
let text = cache.get(cheieCache);
if (!text) {
const r = UrlFetchApp.fetch('https://api-cui.ro/v1/firma/' + encodeURIComponent(String(cui)),
{ headers: { 'X-Api-Key': CHEIE }, muteHttpExceptions: true });
if (r.getResponseCode() !== 200) return 'eroare ' + r.getResponseCode();
text = r.getContentText();
cache.put(cheieCache, text, 21600); // 6 ore: recalcularea foii nu consumă din cotă
}
const v = (camp || 'denumire').split('.').reduce((o, p) => (o == null ? o : o[p]), JSON.parse(text));
return v !== null && typeof v === 'object' ? JSON.stringify(v) : v;
}
În celule: =CUI(A2) pentru denumire, =CUI(A2; "adresa_sediu.text"), =CUI(A2; "tva.platitor"). Foile în limba română folosesc ; între argumente, cele în engleză ,. Fiecare CUI consumă o cerere o singură dată la 6 ore, oricât ar recalcula foaia.
Atenție: oricine are drept de editare pe foaie poate vedea scriptul, deci și cheia. Pentru foi partajate, fă-ți o cheie separată din cont, ca s-o poți revoca fără să le afectezi pe celelalte.
Toți anii disponibili (din 2011), cu aceleași câmpuri ca la extins. Sursa: Ministerul Finanțelor, date deschise.
Autocomplete după denumire. Minimum 3 litere; ignoră diacriticele, punctuația și cuvintele „SC”, „SRL”, „SA”, „PFA”. Întâi potrivire de prefix (sub 2 ms); dacă nu găsește nimic, caută și în interiorul numelui. limita ≤ 25, implicit 10. doar_active=0 include firmele radiate.
curl "https://api-cui.ro/v1/firme/cauta?q=dante+intern&judet=bucuresti" -H "X-Api-Key: ck_..."
{"rezultate":[{"cui":14399840,"denumire":"DANTE INTERNATIONAL SA","judet":"MUNICIPIUL BUCUREȘTI","localitate":"Sector 6 Mun. București","activa":true,"tva":true}],"meta":{"ms":1.1}}
Câmpuri: cui, denumire, judet, localitate, activa (nu e radiată/inactivă), tva (plătitor). Pentru restul fișei ceri apoi /v1/firma/{cui}. Tabela de căutare se reconstruiește săptămânal.
Validări de format, fără să atingă baza. Nu stocăm nimic din ce ne trimiți aici.
| endpoint | răspuns |
|---|---|
/valideaza/cui/RO14399840 | {"cui":"RO14399840","valid":true,"cui_numeric":14399840} |
/valideaza/cnp/1960101123456 | {"cnp":"…","valid":true,"sex":"M","data_nasterii":"1996-01-01","rezident_strain":false,"cod_judet":"12","judet":"Cluj"} |
/valideaza/iban/RO49AAAA1B31007593840000 | {"iban":"…","valid":true,"banca_cod":"AAAA","tara":"RO"} |
Cursul BNR, cu istoric zilnic din ianuarie 2015; ziua curentă apare după publicarea BNR de la 13:00. Fără parametri: ultima zi publicată, toate monedele. data=2026-09-15 întoarce cursul valabil în acea zi (ultima publicare de dinainte, dacă e weekend). moneda=EUR întoarce doar una. multiplu e 100 pentru HUF, JPY, KRW.
Erorile vin ca {"eroare": {"tip": "...", "mesaj": "...", "status": 4xx}}. Tipurile:
| status | tip | ce înseamnă |
|---|---|---|
| 400 | cui_invalid | nu trece de cifra de control |
| 401 | cheie_lipsa | lipsește antetul |
| 403 | cheie_necunoscuta, origine_nepermisa, cheie_secreta_in_url, cont_inactiv | cheia nu e bună sau nu e folosită unde trebuie |
| 403 | proba_expirata | cele 30 de zile de probă au trecut; alege un plan din cont și cheile pornesc pe loc |
| 403 | domeniu_ocupat | domeniul e legat de alt cont; pe un cont gratuit nu se poate refolosi |
| 404 | in_curs | CUI valid pe care nu-l avem încă; l-am cerut de la ANAF — reîncearcă după reincearca_in_secunde |
| 404 | necunoscut_anaf | ANAF nu are acest CUI (firmă străină, număr greșit) |
| 429 | ritm_depasit, cota_lunara_depasita | prea repede, sau cota lunii e consumată |
json_invalid, lista_lipsa, lista_prea_marePOST /v1/firme: corp JSON greșit, listă goală sau peste 100interogare_scurta, data_invalidadoar_plan_platitfara_curs, moneda_necunoscutacota_insuficientaFiecare răspuns autentificat are antetele X-Cota-Luna (cota planului), X-Cota-Ramasa (cereri rămase, inclusiv creditele), X-Credite (creditele din rezervă), X-Plan și X-Timp-Ms; cheile de test au X-Mod: test. Răspunsurile 429 și 503 (ocupat, rar și de scurtă durată) au Retry-After (secunde). Cererile refuzate (401/403/429) nu consumă cotă. Cota se resetează la 1 ale lunii.
Fiecare răspuns 200 sau 404 consumă o cerere din cota lunară a planului. Când cota s-a terminat, cererile se iau din credite (pachete cumpărate o dată din cont, valabile 12 luni) — dacă ai; altfel primești 429 până la 1 ale lunii sau până treci la un plan mai mare (se aplică pe loc). Contul gratuit e o probă de 30 de zile: după expirare cheile răspund 403 proba_expirata până alegi un plan sau cumperi credite. Un domeniu folosit pe un cont de probă rămâne legat de acel cont (403 domeniu_ocupat de pe alt cont gratuit).
Toate firmele sunt reverificate la ANAF cel puțin săptămânal; fișele servite cu date mai vechi de 7 zile primesc reverificare_ceruta: true și intră la coadă imediat. Firmele noi se descoperă zilnic. Dacă vrei să știi cât de proaspătă e o fișă, uită-te la verificat_la. /v1/stare arată, fără cheie, câte firme avem și ultima verificare.