# FirmeAPI.ro — API Insolvență — Vânzări și licitații: ghid de implementare pentru agenți AI

> Acest document e scris pentru agenți AI de programare (Claude Code, Cursor, Copilot, Codex, ChatGPT etc.).
> Conține tot ce trebuie pentru o integrare completă, fără întrebări suplimentare către utilizator.
> Versiunea pentru oameni: https://www.firmeapi.ro/info-api/bpi-vanzari

Anunțurile de vânzare din dosarele de insolvență transformate în loturi filtrabile: ce se vinde (mobil, imobil, în bloc, creanțe), prețul de pornire cu moneda și TVA, data, ora și locul licitației, garanția, runda și datele de contact ale lichidatorului. Include și rezultatul: licitații fără oferte, amânate sau adjudecate, cu cumpărătorul și prețul obținut. Sursa: Publicațiile de insolvență (ONRC), extragere automată FirmeAPI. Peste 120.000 de documente legate de vânzări. Județul și localitatea sunt ale debitorului (din registrul firmelor), nu neapărat ale bunului.

## 0. Ce trebuie să livrezi

Integrează în aplicația utilizatorului API-ul Insolvență — Vânzări și licitații FirmeAPI.ro, cap-coadă:

1. **Configurare** — cheia API citită din variabila de mediu `FIRMEAPI_KEY`; adaug-o în `.env.example` (sau echivalent) cu valoare goală. Nu scrie cheia în cod și nu o trimite niciodată în browser.
2. **Client API** — un modul/serviciu în backend cu câte o metodă pentru fiecare endpoint (`bpiVanzari`, `bpiVanzariCauta`), cu tipurile răspunsurilor, tratarea erorilor (secțiunea 4) și reîncercări doar unde e permis.
3. **Cache** — rezultatele păstrate pe cheia cererii (de exemplu 24 de ore pentru datele unei firme). Fiecare cerere reușită consumă credite.
4. **Logica de domeniu** — regulile din secțiunea 5, într-un singur loc, nu împrăștiate prin interfață.
5. **Interfață** (dacă aplicația are UI) — afișarea datelor și a mesajelor de eroare; câmpurile `null` apar ca „nedeclarat” sau „—”, nu ca 0.
6. **Teste** — cu răspunsuri simulate din acest ghid (mock HTTP), care acoperă cazurile din secțiunea 8.

Respectă convențiile proiectului existent (limbaj, framework, structură de foldere, client HTTP deja folosit).

## 1. Autentificare și cereri

- Bază URL: `https://www.firmeapi.ro/api/v1`
- Header de autentificare: `X-API-KEY: <cheie>` (alternativ `Authorization: Bearer <cheie>`).
- Cheia se creează din contul FirmeAPI, pagina „Chei API”.
- Endpoint-urile folosesc **credite premium**, disponibile cu abonament plătit sau pachet premium.
- Trimite `Accept: application/json`. Răspunsurile sunt JSON UTF-8; datele calendaristice au formatul `YYYY-MM-DD`.
- Timeout recomandat: 30 s.

## 2. Endpoint-urile

### 2.1 Vânzările unei firme în insolvență — `GET /bpi/vanzari/{cui}`

Cost: **1 credit fix premium** per cerere reușită (2xx). Erorile nu se taxează.

Toate documentele de vânzare ale debitorului: anunțuri, regulamente, procese-verbale și adjudecări, de la cel mai recent.

| Parametru | Unde | Tip | Obligatoriu | Descriere |
|---|---|---|---|---|
| `cui` | cale | string | da | Codul fiscal (CUI) al firmei, fără RO. Ex.: `24112036`. |

Exemplu: `GET https://www.firmeapi.ro/api/v1/bpi/vanzari/24112036`

Răspuns 200 (răspuns real, scurtat: listele la câteva elemente, fără nume de persoane fizice):

```json
{
    "success": true,
    "cui": "24112036",
    "sursa": "Buletinul Procedurilor de Insolvență (ONRC), extragere automată FirmeAPI",
    "total": 3,
    "rezultate": [
        {
            "cui": "24112036",
            "numar_bpi": "22570/2026",
            "numar_dosar": "2158/100/2025",
            "data": "2026-09-16",
            "titlu_document": "Publicaţie de vânzare – imobil + teren, nr. 717, din data de 15-09-2026, Tribunalul Maramureş, Secţia a II-a Civilă, de Contencios Administrativ şi Fiscal",
            "fel_document": "publicatie",
            "tip_vanzare": "licitatie publica cu strigare",
            "data_licitatie": "2026-09-25",
            "ora": "12:00",
            "loc": "sediul administratorului judiciar",
            "garantie": "10% din valoarea de începere a licitației",
            "lichidator": {
                "nume": "Cabinet Individual de Insolvență Exemplu",
                "telefon": "07xx xxx xxx",
                "email": null,
                "unpir": "RFO I-2136"
            },
            "loturi": [
                {
                    "nr": null,
                    "descriere": "imobil comercial compus din teren în suprafață de 683 mp și construcție Showroom cu suprafața construită de 453 mp, înscris în CF nr. 105046 Baia Mare, nr. cad. 105046, 105036 – teren, respectiv 105046 – C1 – Centrala…",
                    "pret": 459000,
                    "moneda": "EUR",
                    "tva_inclus": true,
                    "fel": "in bloc"
                }
            ],
            "judet": "MARAMUREŞ",
            "localitate": "Sat Şurdeşti Com. Şişeşti",
            "debitor": "RIMINI CONSTRUCT SRL",
            "extracted_at": "2026-09-20T04:59:18Z"
        },
        {
            "cui": "24112036",
            "numar_bpi": "21701/2026",
            "numar_dosar": "2158/100/2025",
            "data": "2026-09-04",
            "titlu_document": "Proces verbal al şedinţei adunării creditorilor, nr. 705, din data de 04-09-2026, Tribunalul Maramureş, Secţia a II-a Civilă, de Contencios Administrativ şi Fiscal",
            "fel_document": "licitatie",
            "lichidator": {
                "nume": "Cabinet Individual de Insolvență Exemplu",
                "telefon": "07xx xxx xxx",
                "email": null,
                "unpir": "RFO I-2136"
            },
            "loturi": [
                {
                    "nr": null,
                    "descriere": "Teren + Showroom din averea debitoarei Rimini Construct SRL",
                    "pret": null,
                    "moneda": null,
                    "tva_inclus": null,
                    "fel": "imobil"
                }
            ],
            "judet": "MARAMUREŞ",
            "localitate": "Sat Şurdeşti Com. Şişeşti",
            "debitor": "RIMINI CONSTRUCT SRL",
            "extracted_at": "2026-09-20T05:24:26Z"
        },
        {
            "cui": "24112036",
            "numar_bpi": "20956/2026",
            "numar_dosar": "2158/100/2025",
            "data": "2026-08-25",
            "titlu_document": "Hotărâre, nr. 684, din data de 21-08-2026, Tribunalul Maramureş, Secţia a II-a Civilă, de Contencios Administrativ şi Fiscal",
            "fel_document": "licitatie",
            "tip_vanzare": "licitatie publica cu strigare",
            "garantie": "10% din pretul de pornire",
            "lichidator": {
                "nume": "Cabinet Individual de Insolvență Exemplu",
                "telefon": "07xx xxx xxx",
                "email": null,
                "unpir": "RFO I-2136"
            },
            "loturi": [
                {
                    "nr": null,
                    "descriere": "Imobil comercial compus din teren în suprafaţă totală de 683 mp (453 mp + 230 mp) şi construcţia C1 - Showroom şi birouri (fosta Centrală termică 35), regim P+Mez, situat în Baia Mare, B-dul Traian nr. 11A, județul Ma…",
                    "pret": 459000,
                    "moneda": "EUR",
                    "tva_inclus": true,
                    "fel": "imobil"
                }
            ],
            "judet": "MARAMUREŞ",
            "localitate": "Sat Şurdeşti Com. Şişeşti",
            "debitor": "RIMINI CONSTRUCT SRL",
            "extracted_at": "2026-09-20T05:32:23Z"
        }
    ]
}
```

| Câmp | Tip | Descriere |
|---|---|---|
| `success` | boolean | true când cererea a reușit. |
| `cui` | string | Valoarea cerută, normalizată. |
| `sursa` | string | Sursa datelor. |
| `total` | integer | Câte rezultate există pentru cheie. |
| `afisate` | integer | Doar când total e mai mare decât limita: câte sunt întoarse. |
| `nota` | string | Doar când rezultatele sunt trunchiate: cum obții restul. |
| `rezultate` | array | Rezultatele, de la cel mai recent. |
| `rezultate[].cui` | string | CUI-ul debitorului ale cărui bunuri se vând. |
| `rezultate[].debitor` | string | Denumirea oficială a debitorului (din registrul firmelor). |
| `rezultate[].numar_bpi` | string | Publicația de insolvență sursă. |
| `rezultate[].are_pdf` | bool | Dacă avem PDF-ul original al publicației. Sursele sunt disponibile pentru publicațiile din 2026; restul se procesează. |
| `rezultate[].sursa_pdf` | string\|null | Adresa de la care se ia PDF-ul original: GET /api/v1/bpi/{cui}/{numar_bpi}/sursa. Costă 3 credite premium per fișier livrat; verificarea de aici nu costă nimic. |
| `rezultate[].numar_dosar` | string\|null | Dosarul de insolvență, când e menționat. |
| `rezultate[].data` | date | Data publicării documentului. |
| `rezultate[].titlu_document` | string | Titlul documentului publicat. |
| `rezultate[].fel_document` | string | Etapa: publicatie (anunțul), licitatie, regulament, proces_verbal (sesiunea a avut loc), adjudecare (bunul s-a vândut). |
| `rezultate[].rezultat` | string\|null | fara_oferte, adjudecat, amanat, anulat. Null la anunțuri, care nu au încă rezultat. |
| `rezultate[].tip_vanzare` | string\|null | Modalitatea, în cuvintele documentului (ex. licitatie publica cu strigare). Nu e normalizat. |
| `rezultate[].data_licitatie` | date\|null | Data la care se ține licitația. |
| `rezultate[].ora` | string\|null | Ora licitației. |
| `rezultate[].loc` | string\|null | Locul licitației, cum e scris. |
| `rezultate[].runda` | integer\|null | Runda licitației (1, 2, …). |
| `rezultate[].garantie` | string\|null | Garanția de participare, cum e scrisă (ex. 10% din prețul de pornire). |
| `rezultate[].pret_dosar` | string\|null | Costul caietului de sarcini. |
| `rezultate[].lichidator` | object\|null | Lichidatorul: nume, telefon, email, unpir (numărul din registrul practicienilor). |
| `rezultate[].loturi` | array | Loturile scoase la vânzare. |
| `rezultate[].loturi[].descriere` | string | Ce se vinde, cum e descris. |
| `rezultate[].loturi[].fel` | string | mobil, imobil, in bloc, creante. |
| `rezultate[].loturi[].pret` | number\|null | Prețul de pornire, NECONVERTIT, în moneda lotului. |
| `rezultate[].loturi[].moneda` | string\|null | RON sau EUR. |
| `rezultate[].loturi[].tva_inclus` | boolean\|null | false când prețul e „+ TVA”. |
| `rezultate[].loturi[].nr` | string\|null | Numărul lotului. |
| `rezultate[].adjudecare` | object\|null | Doar când bunul s-a vândut: cumparator, pret, moneda, data, evaluare. |
| `rezultate[].judet` | string | Județul debitorului. |
| `rezultate[].localitate` | string | Localitatea debitorului. |
| `rezultate[].extracted_at` | datetime | Momentul extragerii. |

### 2.2 Caută bunuri scoase la vânzare — `GET /bpi/vanzari/lista`

Cost: **1 credit / rezultat premium** per cerere reușită (2xx). Erorile nu se taxează.

Vitrina licitațiilor: filtrează după ce se vinde, tipul lotului, preț, monedă, județ, data licitației sau lichidator. Cu doar_active=true primești doar anunțurile la care încă se poate licita. Loturile care se potrivesc sunt întoarse separat în loturi_potrivite.

| Parametru | Unde | Tip | Obligatoriu | Descriere |
|---|---|---|---|---|
| `q` | query | string | nu | Ce se vinde (ex. „teren intravilan”, „excavator”, „hală”). Ex.: `teren intravilan`. |
| `fel` | query | string | nu | Tipul lotului. Valori: `mobil`, `imobil`, `in bloc`, `creante`. |
| `moneda` | query | string | nu | Moneda prețului. Valori: `RON`, `EUR`. |
| `pret_min` | query | number | nu | Preț de pornire minim (în moneda lotului). |
| `pret_max` | query | number | nu | Preț de pornire maxim (în moneda lotului). Ex.: `100000`. |
| `judet` | query | string | nu | Județul debitorului (cu sau fără diacritice). Ex.: `Cluj`. |
| `localitate` | query | string | nu | Localitatea debitorului. |
| `fel_document` | query | string | nu | Etapa vânzării. Valori: `publicatie`, `licitatie`, `regulament`, `proces_verbal`, `adjudecare`. |
| `rezultat` | query | string | nu | Rezultatul sesiunii (doar pe procese-verbale și adjudecări). Valori: `fara_oferte`, `adjudecat`, `amanat`, `anulat`. |
| `lichidator` | query | string | nu | Numele lichidatorului. |
| `cui` | query | string | nu | Doar vânzările unui debitor. |
| `doar_active` | query | boolean | nu | true = doar anunțurile (publicatie/licitatie) fără rezultat, cu licitația azi sau în viitor. Ex.: `true`. |
| `data_de` | query | date (AAAA-LL-ZZ) | nu | De la această dată (data ≥), format AAAA-LL-ZZ. Ex.: `2026-01-01`. |
| `data_pana` | query | date (AAAA-LL-ZZ) | nu | Până la această dată (data ≤), format AAAA-LL-ZZ. Ex.: `2026-12-31`. |
| `licitatie_de` | query | date (AAAA-LL-ZZ) | nu | De la această dată (data_licitatie ≥), format AAAA-LL-ZZ. Ex.: `2026-01-01`. |
| `licitatie_pana` | query | date (AAAA-LL-ZZ) | nu | Până la această dată (data_licitatie ≤), format AAAA-LL-ZZ. Ex.: `2026-12-31`. |
| `pagina` | query | integer | nu | Numărul paginii, de la 1. Ex.: `1`. |
| `per_pagina` | query | integer | nu | Rezultate pe pagină, maxim 20. Ex.: `20`. |
| `ordine` | query | string | nu | Ordinea rezultatelor (vezi valorile permise pe endpoint). Valori: `recente`, `licitatie`. Ex.: `recente`. |

Exemplu: `GET https://www.firmeapi.ro/api/v1/bpi/vanzari/lista?fel=imobil&judet=Cluj&doar_active=true`

Răspuns 200 (răspuns real, scurtat: listele la câteva elemente, fără nume de persoane fizice):

```json
{
    "success": true,
    "sursa": "Buletinul Procedurilor de Insolvență (ONRC), extragere automată FirmeAPI",
    "pagina": 1,
    "per_pagina": 2,
    "total": 1,
    "total_pagini": 1,
    "rezultate": [
        {
            "cui": "199206",
            "numar_bpi": "20537/2026",
            "numar_dosar": "1158/1285/2022",
            "data": "2026-08-18",
            "titlu_document": "Document, nr. 105, din data de 18-08-2026, Tribunalul Specializat Cluj",
            "fel_document": "licitatie",
            "tip_vanzare": "licitatie pe baza de oferte",
            "data_licitatie": "2026-09-29",
            "loc": "ofertele se comunică prin poștă la adresa Târgoviște, str. Calea București, nr. 3A, ap. 2, Dâmbovița sau pe email",
            "pret_dosar": "900,00 lei fără TVA (caietul de sarcini); taxa de participare 500,00 lei (încălțăminte)",
            "lichidator": {
                "nume": "Cabinet Individual de Insolvență Exemplu",
                "telefon": "07xx xxx xxx",
                "email": "lichidator@exemplu.ro",
                "unpir": null
            },
            "loturi": [
                {
                    "nr": null,
                    "descriere": "acțiunile deținute de societatea Clujana SA în Faliment la alți profesioniști",
                    "pret": null,
                    "moneda": null,
                    "tva_inclus": null,
                    "fel": null
                },
                {
                    "nr": null,
                    "descriere": "marca societății Clujana SA, marcă înregistrată",
                    "pret": null,
                    "moneda": null,
                    "tva_inclus": null,
                    "fel": null
                },
                {
                    "nr": null,
                    "descriere": "1.379 perechi de încălțăminte fabricate de Clujana SA",
                    "pret": null,
                    "moneda": null,
                    "tva_inclus": null,
                    "fel": "mobil"
                }
            ],
            "judet": "CLUJ",
            "localitate": "Mun. Cluj-Napoca",
            "debitor": "CLUJANA S.A.",
            "extracted_at": "2026-09-20T05:36:26Z",
            "loturi_potrivite": [
                {
                    "nr": null,
                    "descriere": "Teren intravilan înscris în CF 269868, str. Pâinii, nr. 20, Cluj Napoca, 3.667 mp",
                    "pret": 698380.15,
                    "moneda": "EUR",
                    "tva_inclus": null,
                    "fel": "imobil"
                },
                {
                    "nr": null,
                    "descriere": "Teren intravilan înscris în CF 260663, Cluj Napoca, str. Tăbăcarilor, nr. 9, 323 mp",
                    "pret": 72012.85,
                    "moneda": "EUR",
                    "tva_inclus": null,
                    "fel": "imobil"
                },
                {
                    "nr": null,
                    "descriere": "Teren intravilan înscris în CF 325747, Cluj Napoca, 650 mp",
                    "pret": 56192.5,
                    "moneda": "EUR",
                    "tva_inclus": null,
                    "fel": "imobil"
                }
            ]
        }
    ],
    "cost": {
        "credite_consumate": 1,
        "mod": "per_rezultat",
        "tip": "premium"
    }
}
```

| Câmp | Tip | Descriere |
|---|---|---|
| `success` | boolean | true când cererea a reușit. |
| `sursa` | string | Sursa datelor. |
| `pagina · per_pagina` | integer | Pagina curentă și rezultatele pe pagină. |
| `total · total_pagini` | integer | Totalul rezultatelor care se potrivesc filtrelor și numărul de pagini pe care le poți parcurge. |
| `rezultate` | array | Rezultatele paginii. |
| `rezultate[].cui` | string | CUI-ul debitorului ale cărui bunuri se vând. |
| `rezultate[].debitor` | string | Denumirea oficială a debitorului (din registrul firmelor). |
| `rezultate[].numar_bpi` | string | Publicația de insolvență sursă. |
| `rezultate[].are_pdf` | bool | Dacă avem PDF-ul original al publicației. Sursele sunt disponibile pentru publicațiile din 2026; restul se procesează. |
| `rezultate[].sursa_pdf` | string\|null | Adresa de la care se ia PDF-ul original: GET /api/v1/bpi/{cui}/{numar_bpi}/sursa. Costă 3 credite premium per fișier livrat; verificarea de aici nu costă nimic. |
| `rezultate[].numar_dosar` | string\|null | Dosarul de insolvență, când e menționat. |
| `rezultate[].data` | date | Data publicării documentului. |
| `rezultate[].titlu_document` | string | Titlul documentului publicat. |
| `rezultate[].fel_document` | string | Etapa: publicatie (anunțul), licitatie, regulament, proces_verbal (sesiunea a avut loc), adjudecare (bunul s-a vândut). |
| `rezultate[].rezultat` | string\|null | fara_oferte, adjudecat, amanat, anulat. Null la anunțuri, care nu au încă rezultat. |
| `rezultate[].tip_vanzare` | string\|null | Modalitatea, în cuvintele documentului (ex. licitatie publica cu strigare). Nu e normalizat. |
| `rezultate[].data_licitatie` | date\|null | Data la care se ține licitația. |
| `rezultate[].ora` | string\|null | Ora licitației. |
| `rezultate[].loc` | string\|null | Locul licitației, cum e scris. |
| `rezultate[].runda` | integer\|null | Runda licitației (1, 2, …). |
| `rezultate[].garantie` | string\|null | Garanția de participare, cum e scrisă (ex. 10% din prețul de pornire). |
| `rezultate[].pret_dosar` | string\|null | Costul caietului de sarcini. |
| `rezultate[].lichidator` | object\|null | Lichidatorul: nume, telefon, email, unpir (numărul din registrul practicienilor). |
| `rezultate[].loturi` | array | Loturile scoase la vânzare. |
| `rezultate[].loturi[].descriere` | string | Ce se vinde, cum e descris. |
| `rezultate[].loturi[].fel` | string | mobil, imobil, in bloc, creante. |
| `rezultate[].loturi[].pret` | number\|null | Prețul de pornire, NECONVERTIT, în moneda lotului. |
| `rezultate[].loturi[].moneda` | string\|null | RON sau EUR. |
| `rezultate[].loturi[].tva_inclus` | boolean\|null | false când prețul e „+ TVA”. |
| `rezultate[].loturi[].nr` | string\|null | Numărul lotului. |
| `rezultate[].adjudecare` | object\|null | Doar când bunul s-a vândut: cumparator, pret, moneda, data, evaluare. |
| `rezultate[].judet` | string | Județul debitorului. |
| `rezultate[].localitate` | string | Localitatea debitorului. |
| `rezultate[].extracted_at` | datetime | Momentul extragerii. |
| `cost.credite_consumate · mod · tip` | integer · string | Creditele consumate de cerere, modul de taxare (fix sau per_rezultat) și tipul creditelor (general sau premium). |

Reguli:

- Maximum 20 rezultate pe pagină. Paginile acoperă toate rezultatele care se potrivesc filtrelor; dacă o căutare e prea largă pentru a fi paginată până la capăt, răspunsul spune până unde poți merge.
- Costul se calculează pe numărul de rezultate întoarse; o căutare fără rezultate nu costă nimic.

## 3. Testare

Endpoint-urile acestui API nu au sandbox. Pentru testele automate, folosește răspunsurile din secțiunea 2 ca fixture (mock HTTP pe clientul tău). Pentru o verificare reală, fă o singură cerere cu cheia utilizatorului: erorile și răspunsurile 404 nu consumă credite.

## 4. Erori

Corpul unei erori: `{ "success": false, "error": "...", "code": "...", "message": "..." }` (câmpul `message` poate lipsi). Decide după statusul HTTP și `code`, nu după text. Nicio eroare nu consumă credite.

| HTTP | code | Când apare | Ce face integrarea | Reîncearcă |
|---|---|---|---|---|
| 401 | AUTH_MISSING_KEY, AUTH_INVALID_KEY, AUTH_KEY_EXPIRED | Cheia lipsește, e greșită sau a expirat. | Eroare de configurare: loghează și verifică FIRMEAPI_KEY. | nu |
| 403 | PREMIUM_INSUFFICIENT_CREDITS | Rezultatele paginii costă mai mult decât creditele disponibile. | Răspunsul spune costul cererii și soldul: cere mai puține rezultate (per_pagina mai mic) sau cumpără credite. | nu |
| 403 | PREMIUM_REQUIRES_PAID_SUBSCRIPTION | Contul nu are abonament plătit. | Mesaj către utilizator cu linkul din upgrade_url. | nu |
| 403 | ACCESS_DENIED_IP, ACCESS_DENIED_SCOPE | Cheia e limitată la alte IP-uri sau endpoint-uri. | Eroare de configurare a cheii, din contul FirmeAPI. | nu |
| 403 | MONTHLY_LIMIT_REACHED | Planul gratuit și-a atins limita lunară de cereri. | Mesaj către utilizator cu linkul din upgrade_url. | nu |
| 404 | — | Nu există date pentru cheia cerută. | Afișează „fără date”. Păstrează în cache scurt (de exemplu o oră). | nu |
| 422 | VALIDATION_FAILED | Un filtru invalid sau lipsește un parametru obligatoriu. | Arată eroarea din errors, pe câmp. Validează înainte de cerere. | nu |
| 429 | LIMIT_EXCEEDED_SECOND | Prea multe cereri într-o secundă. | Așteaptă retry_after secunde și reia cererea. | da |
| 429 | LIMIT_EXCEEDED_DAILY, LIMIT_EXCEEDED_MONTHLY | Limita zilnică sau lunară a planului. | Oprește cererile până la retry_after și anunță utilizatorul. | nu |
| 500 | — | Eroare neașteptată pe server. | Reîncearcă de cel mult 3 ori, la 1, 2 și 4 secunde. | da |

## 5. Reguli de domeniu și de integrare

1. Important când interpretezi datele: PDF-ul original al publicației se ia separat, cu GET /api/v1/bpi/{cui}/{numar_bpi}/sursa: 3 credite premium per fișier livrat, fiecare descărcare se taxează. Sursele sunt disponibile pentru publicațiile din 2026; pentru restul, câmpul are_pdf e false și documentele se procesează.
2. Doar anunțurile (publicatie, licitatie) fără rezultat sunt bunuri care încă se pot cumpăra. Un proces-verbal sau o adjudecare nu mai sunt oferte — folosește doar_active=true pentru vitrină.
3. Prețurile nu sunt convertite: 239.210 EUR + TVA rămâne pret=239210, moneda=EUR, tva_inclus=false.
4. Jumătate dintre anunțuri nu au loturi extrase (regulamente fără bunuri sau prețuri exprimate procentual, cu bunurile în anexe).
5. Rândurile fără data_licitatie au momentul licitației necunoscut.
6. Normalizează CUI-ul înainte de cerere: elimină prefixul RO, spațiile și orice caracter care nu e cifră.
7. Un câmp `null` înseamnă că sursa nu are informația: nu îl transforma în 0 sau în șir gol.
8. Apelurile se fac din backend; cheia API nu ajunge niciodată în browser sau în aplicația mobilă.
9. Păstrează rezultatele în cache pe cheia cererii; nu repeta aceeași cerere la fiecare afișare.

## 6. Header-e de răspuns utile

| Header | Conținut |
|---|---|
| `X-Credit-Cost` | Creditele consumate de cerere. |
| `X-Credit-Type` | Tipul creditelor folosite de endpoint. |
| `X-Credit-Balance-General` | Soldul de credite generale. |
| `X-Credit-Balance-{TIP}` | Soldul pentru tipul cererii, când nu e general (de exemplu X-Credit-Balance-ACTIONARI). |
| `X-RateLimit-Limit-Second · -Day · -Month` | Limitele planului. |
| `X-RateLimit-Remaining-Second · -Day · -Month` | Cât a mai rămas din fiecare limită. |

## 7. Implementare de referință

TypeScript / Node 18+ (tradu în limbajul proiectului dacă e altul):

```ts
const BASE = "https://www.firmeapi.ro/api/v1";

export class FirmeApiError extends Error {
  constructor(public status: number, public code: string | null, message: string, public body: unknown) {
    super(message);
  }
}

type Query = Record<string, string | number | boolean | undefined>;

async function call<T>(path: string, query?: Query): Promise<T> {
  const url = new URL(BASE + path);
  for (const [k, v] of Object.entries(query ?? {})) if (v !== undefined) url.searchParams.set(k, String(v));

  for (let attempt = 0; ; attempt++) {
    const res = await fetch(url, { headers: { "X-API-KEY": process.env.FIRMEAPI_KEY ?? "", Accept: "application/json" }, signal: AbortSignal.timeout(30_000) });
    const body: any = await res.json().catch(() => null);
    if (res.ok) return body as T;

    const code = body?.code ?? null;
    const retriable = res.status >= 500 || code === "LIMIT_EXCEEDED_SECOND";
    if (retriable && attempt < 3) {
      const wait = code === "LIMIT_EXCEEDED_SECOND" ? (body?.retry_after ?? 1) * 1000 : 1000 * 2 ** attempt;
      await new Promise((r) => setTimeout(r, wait));
      continue;
    }
    throw new FirmeApiError(res.status, code, body?.message ?? body?.error ?? res.statusText, body);
  }
}

/** 404 = fără date (netaxat): întoarce null în loc de excepție. */
async function orNull<T>(p: Promise<T>): Promise<T | null> {
  try {
    return await p;
  } catch (e) {
    if (e instanceof FirmeApiError && e.status === 404) return null;
    throw e;
  }
}

export const normalizeCui = (input: string) => input.replace(/^\s*RO/i, "").replace(/\D/g, "");

// Câte o funcție pentru fiecare endpoint (tipează răspunsurile după câmpurile din secțiunea 2).
export const bpiVanzari = (cui: string) => orNull(call<any>(`/bpi/vanzari/${normalizeCui(cui)}`));
export const bpiVanzariCauta = (query: Query = {}) => call<any>(`/bpi/vanzari/lista`, query);
```

Echivalent PHP (Laravel):

```php
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;

// config/services.php: 'firmeapi' => ['key' => env('FIRMEAPI_KEY')]
$res = Http::withHeaders(['X-API-KEY' => config('services.firmeapi.key')])->acceptJson()
    ->timeout(30)
    ->retry(3, fn (int $attempt) => 1000 * 2 ** ($attempt - 1),
        fn ($e) => $e instanceof RequestException && $e->response->serverError(), throw: false)
    ->get('https://www.firmeapi.ro/api/v1/bpi/vanzari/24112036');

if ($res->status() === 404) return null;   // fără date, netaxat
$res->throw();                              // restul erorilor: vezi secțiunea 4
$data = $res->json();
```

Echivalent Python:

```python
import os, time, requests

BASE = "https://www.firmeapi.ro/api/v1"

def call(path: str, params: dict | None = None):
    for attempt in range(4):
        r = requests.get(BASE + path, params=params, timeout=30,
                         headers={"X-API-KEY": os.environ["FIRMEAPI_KEY"], "Accept": "application/json"})
        if r.status_code == 404:
            return None                      # fără date, netaxat
        retriable = r.status_code >= 500 or (r.status_code == 429 and r.json().get("code") == "LIMIT_EXCEEDED_SECOND")
        if retriable and attempt < 3:
            time.sleep(r.json().get("retry_after", 1) if r.status_code == 429 else 2 ** attempt)
            continue
        r.raise_for_status()
        return r.json()
```

## 8. Teste de acceptanță

1. `bpiVanzari` cu cererea `/bpi/vanzari/24112036` întoarce 200 și se parsează în câmpurile din secțiunea 2 (fixture).
2. `bpiVanzariCauta` cu cererea `/bpi/vanzari/lista?fel=imobil&judet=Cluj&doar_active=true` întoarce 200 și se parsează în câmpurile din secțiunea 2 (fixture).
3. Un CUI scris `RO 12345678` e normalizat la `12345678` înainte de cerere.
4. Un răspuns 404 devine „fără date” (null sau listă goală), fără excepție.
5. Un 503 sau 500 e reîncercat de cel mult 3 ori; un 401 sau 403 nu e reîncercat.
6. Un 429 `LIMIT_EXCEEDED_SECOND` așteaptă `retry_after` secunde și reia cererea.
7. A doua cerere identică vine din cache, fără cerere HTTP.

## 9. Alternativa fără cod: MCP

Dacă utilizatorul vrea ca un asistent AI să interogheze datele direct (nu o integrare în aplicație), folosește serverul MCP: `https://www.firmeapi.ro/api/mcp` cu header-ul `Authorization: Bearer <cheie>`. Tool-uri: `bpi_vanzari`, `bpi_vanzari_cauta`, cu aceleași costuri. Ghid pe fiecare client: https://www.firmeapi.ro/mcp

## Legături

- Pagina API-ului: https://www.firmeapi.ro/info-api/bpi-vanzari
- Acest ghid (Markdown): https://www.firmeapi.ro/info-api/bpi-vanzari.md
- Specificația OpenAPI: https://www.firmeapi.ro/api/mcp/openapi.json
- Documentația generală: https://www.firmeapi.ro/docs
- Rezumat pentru LLM-uri: https://www.firmeapi.ro/llms.txt
