Autentifică-te în aplicație
Cu contul firmei tale, pe efactura.docuhelp.ro.
Ghid de integrare pentru sisteme externe — WordPress/WooCommerce, OpenCart, ERP-uri proprii. Caută sau creează clienți, creează proforme și facturi cu TVA calculat automat, transformă o proformă în factură, descarcă PDF-ul sau XML-ul UBL trimis la ANAF. Iar când ANAF răspunde, afli printr-un webhook semnat — fără interogare periodică.
Format JSON · URL de bază https://efactura.docuhelp.ro/api/v2/ · Autentificare cu cheie API · Versiune document 2.0 · English version (PDF)
API v2 e separat de API v1
API v1 (/api/v1/invoice/add) e deja folosit de alte integrări și rămâne neschimbat — cele două nu se afectează reciproc și pot fi folosite în paralel. Pentru integrarea de bază, cu chei de acces și jurnal de trafic, vezi pagina API și integrări.
Nou în versiunea 2.0 a documentației
Webhook-uri pentru statusul SPV — nu mai e nevoie de interogare periodică. Plus POST /invoices/{id}/send-spv pentru retrimiterea unei facturi existente, GET /invoices cu filtre pentru reconciliere și GET /invoices/{id}/xml pentru XML-ul UBL trimis la ANAF. Câmpurile vechi rămân neschimbate — integrările existente nu trebuie modificate.
Fiecare cerere către /api/v2/ trebuie să conțină Authorization: Bearer <cheia_dvs_api>.
Cu contul firmei tale, pe efactura.docuhelp.ro.
Introdu o denumire pentru cheie (ex: „Magazin WordPress”) și apasă Generează cheie nouă.
Cheia completă e afișată o singură dată, imediat după generare. Serverul reține doar un hash SHA-256, nu valoarea în clar — dacă ai pierdut-o, generezi una nouă și o revoci pe cea veche.
Fiecare cheie are un buton Revocă. Revocarea e imediată și ireversibilă: orice integrare care mai folosește acea cheie primește 403 Forbidden începând cu următoarea cerere.
| Aspect | Detalii |
|---|---|
| Format | Request și response: JSON (Content-Type: application/json), cu excepția /pdf (răspuns application/pdf) și /xml (răspuns application/xml). |
| URL de bază | https://efactura.docuhelp.ro/api/v2/ |
| ID-uri | Toate ID-urile (client, proformă, factură, webhook) sunt șiruri scurte (ex: a1B2c3D4e5F6), nu numere — folosite exact cum sunt returnate. |
| Sume | Prețurile din linii sunt fără TVA (nete). TVA-ul și totalul se calculează automat pe server. Orice total trimis de client e ignorat. |
| Date | Format YYYY-MM-DD (ex: 2026-08-24). |
| Valute | RON, EUR, USD, HUF. |
| Cod HTTP | Semnificație |
|---|---|
| 400 | Corp cerere JSON invalid/lipsă. |
| 401 | Lipsește header-ul Authorization. |
| 403 | Cheie API invalidă, revocată sau expirată. |
| 404 | Resursa (client/proformă/factură/webhook) nu există sau nu aparține firmei cheii folosite. |
| 409 | Conflict — ex: număr de factură/proformă deja folosit (cerere concurentă); proformă deja convertită în factură; factură deja acceptată în SPV. |
| 413 | Corpul cererii depășește 1 MB. |
| 422 | Date invalide (câmp obligatoriu lipsă, valoare greșită). |
| 429 | Prea multe cereri — încetinește (limitare pe firmă, pe endpoint). |
| 500 | Eroare internă server. |
Corpul răspunsului de eroare are mereu forma: {"error": "mesaj explicativ"}.
Verifică dacă cheia API e validă.
Seriile de facturare configurate pentru firmă, pentru a alege una la creare (sau lași serverul să aleagă prima serie configurată).
Următorul număr disponibil pentru o serie (informativ — numărul real se alocă automat, atomic, la creare).
Caută clienți după nume sau CIF. Se poate folosi și ?cif=.
Creează un client nou, sau — dacă există deja un client cu același CIF la firma ta — returnează clientul existent (fără duplicat). Singurul câmp obligatoriu pentru un client nou e name.
Dacă ai deja client.id dintr-un apel anterior, îl poți trimite direct: {"client":{"id":"kL9mN2pQ7rS1"}}.
Creează o proformă nouă.
Datele proformei, respectiv PDF-ul ei (generat la prima cerere, apoi servit din cache).
Linkul pdf_url din răspunsuri e semnat și valabil 30 de zile — îl poți pune direct într-un email sau în contul clientului, fără header de autentificare. PDF-urile nu pot fi afișate în <iframe> de pe alt domeniu (X-Frame-Options: SAMEORIGIN) — deschide-le în tab nou.
Transformă o proformă existentă în factură (de exemplu după confirmarea plății comenzii). O proformă poate fi convertită o singură dată.
Corpul cererii e opțional ({} e valid); câmpuri acceptate: date, series, payby, send_spv. Răspunsul are aceeași structură ca POST /api/v2/invoices, plus converted_from_proforma_id.
Creează direct o factură (fără să treacă prin proformă).
Starea curentă a facturii, inclusiv statusul real al trimiterii în SPV, respectiv PDF-ul ei.
Listează facturile emise, pentru reconciliere din magazin — util dacă sistemul tău a pierdut ID-urile. Fiecare element din items are exact aceeași structură ca GET /api/v2/invoices/{id}.
| Parametru | Descriere |
|---|---|
from, to | Interval de date (YYYY-MM-DD), inclusiv. |
series | Doar o anumită serie. |
spv | pending, accepted, rejected sau not_sent — filtrează după statusul SPV curent. |
limit, offset | Paginare. limit implicit 50, maxim 100. |
XML-ul UBL 2.1 exact așa cum a fost (sau ar fi) trimis la ANAF — pentru contabilitățile care arhivează documentul original, nu PDF-ul randat. Dacă XML-ul nu a fost încă generat, se generează la cerere.
Trimite în SPV o factură deja creată: fie ca reîncercare după o urcare eșuată (token ANAF expirat, ANAF indisponibil), fie ca trimitere ulterioară a unei facturi create fără send_spv.
Răspunsul 202 vine imediat și înseamnă „pus la trimis”, nu „acceptat de ANAF” — rezultatul real ajunge prin webhook sau la următorul GET /api/v2/invoices/{id}. Endpointul refuză cu 409 dacă factura e deja acceptată de ANAF sau dacă există deja o încărcare în curs. Dacă a fost respinsă, reîncercarea se face explicit, cu ?force=1, după corectarea datelor.
Înregistrează o adresă la care îți trimitem un POST semnat la fiecare schimbare, respectiv listează webhook-urile existente cu starea ultimei livrări (last_status, last_error, fail_count) și lista completă de evenimente disponibile.
Șterge webhook-ul și livrările lui aflate în coadă, respectiv trimite o livrare de probă sincron și returnează codul HTTP primit de la tine — așa depanezi o integrare fără să aștepți un eveniment real.
Detalii complete — evenimente, structura livrării, verificarea semnăturii, reîncercări — în secțiunea 07.
| Câmp | Tip | Oblig. | Descriere |
|---|---|---|---|
name | text | da | Denumire produs/serviciu. |
description | text | nu | Descriere suplimentară (linia a 2-a pe factură). |
unit | text | nu | Unitate de măsură, cod UN/CEFACT (implicit H87 = bucată, dacă lipsește). Un cod nerecunoscut nu dă eroare — se afișează ca atare pe document. |
qty | număr | da | Cantitate. Poate fi negativă (linie de corecție/storno). |
price | număr | da | Preț unitar fără TVA. |
vat_rate | număr | nu | Cotă TVA în procente. Ignorată dacă firma nu e plătitoare de TVA. |
vat_category | text | nu | S, Z, E, AE, K, G sau O. Dacă lipsește, se deduce din vat_rate. |
cpv | text | nu | Cod CPV, dacă e relevant. |
Categorii TVA: S = cotă standard/redusă, Z = cotă zero, E = scutit, AE = taxare inversă, K = livrare intracomunitară, G = export, O = în afara sferei TVA. Maximum 500 de linii pe document.
| Câmp | Descriere |
|---|---|
id | ID-ul unui client existent. Dacă e trimis, celelalte câmpuri sunt ignorate. |
name | Singurul câmp obligatoriu pentru un client nou. |
cif | CIF/CUI firmă, fără prefixul RO. Lipsa lui înseamnă persoană fizică. |
cnp | CNP persoană fizică, 13 cifre — opțional. |
address, city, subdivision, country | Adresa, orașul, județul (format RO-XX) și codul ISO de țară (implicit RO). Recomandate dacă factura va fi trimisă în SPV. |
email, phone, contact_person, reg_com | Opționale. |
currency | Valuta implicită a clientului, implicit RON. |
Un cif care există deja la clienții firmei tale reutilizează clientul existent — nu creează duplicate. Fără cif (persoane fizice), fiecare cerere poate crea un client nou — nu există deduplicare după nume sau CNP.
"send_spv": true, restul e asincron — în două etape.Răspunsul 201 ajunge imediat, cu spv.status = "not_sent" („pus la trimis”). Apoi, în câteva secunde, factura ajunge la ANAF și primește un index de încărcare — statusul devine pending. ANAF o validează și răspunde accepted sau rejected abia după minute sau ore.
Nu aștepta rezultatul final în câteva secunde: nu depinde de noi, ci de ANAF. Recomandat: înregistrează un webhook și primești ambele schimbări automat, în loc să interoghezi periodic.
Necesită ca firma să aibă deja conectat contul ANAF (OAuth) din interfața web. Fără conectare, trimiterea eșuează — factura în sine nu e afectată, iar cu POST /invoices/{id}/send-spv se poate reîncerca după configurare.
not_sent, pending, accepted sau rejected. Cel mai simplu câmp de folosit — câmpurile sent și accepted de mai jos rămân pentru compatibilitate.true dacă există o încercare de trimitere înregistrată.true dacă ANAF a acceptat factura.message.În loc să interoghezi GET /api/v2/invoices/{id} până când ANAF răspunde, înregistrezi o adresă la care îți trimitem un POST semnat de fiecare dată când se schimbă ceva. O singură cerere, o singură dată, la instalare.
| Eveniment | Când se emite |
|---|---|
invoice.spv.sent | Factura a ajuns la ANAF și are index de încărcare. Încă „în prelucrare”. |
invoice.spv.accepted | OK-ul final: ANAF a validat factura. |
invoice.spv.rejected | ANAF a respins factura; erorile sunt în spv.message. |
invoice.spv.failed | Urcarea în sine a eșuat (nu există index de încărcare) — ex: token ANAF expirat. |
invoice.created | Factură creată prin API v2 (direct sau din proformă). |
proforma.created | Proformă creată prin API v2. |
secret e afișat o singură dată, ca și cheia API — cu el verifici semnătura fiecărei livrări, deci salvează-l în configurarea integrării. Dacă îl pierzi, reînregistrezi același URL: secretul se rotește și primești unul nou. Câmpul events e opțional; dacă lipsește, primești toate evenimentele.
https://, cu certificat valid.localhost, 127.0.0.1, 10.x, 192.168.x, 169.254.x) sunt respinse la înregistrare — serverul nostru face cererea, deci o adresă internă ar fi o breșă de securitate.2xx.Adresa ta e publică, deci oricine ar putea trimite un POST acolo. Verifică întotdeauna semnătura înainte de a modifica ceva.
Semnătura se calculează peste șirul "{timestamp}.{corpul brut}", cu secret-ul primit la înregistrare. Folosește corpul brut, exact cum a venit — nu îl re-serializa din JSON, pentru că ordinea cheilor s-ar putea schimba și semnătura nu ar mai corespunde. Compară cu hash_equals(), nu cu ==.
2xx dacă ai primit livrarea. Orice altceva (sau un timeout de 15 secunde) înseamnă eșec.event_id — folosește-l pentru deduplicare, ca să nu procesezi de două ori același eveniment.2xx și atunci când nu recunoști factura — poate fi emisă din aplicație, nu din magazin. Un răspuns de eroare ar declanșa reîncercări inutile timp de 32 de ore."test": true în corp și o factură fictivă — confirmă-le, dar nu modifica nicio comandă pe baza lor.O singură dată, la instalare: POST /webhooks. După aceea statusul SPV al fiecărei facturi ajunge singur la tine.
1. Caută clientul după CIF: GET /clients?cif=...
2. Dacă nu există, creează-l: POST /clients
3. Creează factura direct: POST /invoices, cu "send_spv": true dacă vrei trimitere automată
4. Descarci PDF-ul din pdf_url și îl atașezi la emailul de confirmare.
5. Statusul SPV vine singur, prin webhook — invoice.spv.sent, apoi accepted sau rejected. Fără webhook, îl citești cu GET /invoices/{id}.
1. La plasarea comenzii: POST /proformas.
2. Trimiți clientului PDF-ul proformei (pdf_url) ca instrucțiuni de plată.
3. La confirmarea plății (manual sau prin webhook propriu): POST /proformas/{id}/convert — proforma devine factură, fără să retastezi nimic.
202 de la send-spv ca pe o confirmare ANAF — înseamnă doar că trimiterea a fost pusă la rând.Adu-ne un exemplu de comandă din sistemul tău — ne uităm împreună la ce trimiți și cât de aproape ești de un apel funcțional. Pentru un conector construit complet la cerere, vezi dezvoltarea personalizată.
Fără obligații · Răspundem în aceeași zi lucrătoare