FacturareIntră în aplicație
API pentru firma taConvenții · pagina 9 din 9

Convenții

Regulile care se aplică fiecărei cereri.

Actualizat la

Adresa și autentificarea

Toate cererile merg la https://facturare.chappa.ro/api/v1, cu antetul Authorization: Bearer chf_…. Fără cheie, sau cu una revocată, răspunsul este 401 invalid_api_key. Cheile și permisiunile au pagina lor.

Formatul

  • JSON în UTF-8, câmpuri în snake_case. Un câmp fără valoare este null, nu lipsește.
  • Datele calendaristice: AAAA-LL-ZZ, în ora României. Momentele: RFC 3339 cu fus orar, de exemplu 2026-10-09T14:02:11+03:00.
  • Sumele: numere, cu două zecimale la totaluri; prețurile unitare și cantitățile pot avea patru.
  • Monedele: coduri ISO 4217 (RON, EUR); țările: ISO 3166-1 (RO).
  • Id-urile sunt numere întregi, cu două excepții: facturile primite (id-ul mesajului din SPV) și evenimentele (evt_…).
  • Câmpurile pe care nu le cunoaștem sunt o eroare (400 unknown_field): o greșeală de scriere nu trece neobservată.

Paginarea

Listele dau cele mai noi înregistrări primele, câte limit (1–100, implicit 25). Pagina următoare o ceri cu starting_after egal cu next_cursor din pagina de acum, cât timp has_more este true:

let after = null, all = [];
do {
  const url = `/api/v1/companies/7/documents?limit=100${after ? `&starting_after=${after}` : ''}`;
  const page = await api(url);
  all.push(...page.data);
  after = page.next_cursor;
} while (after);

Cursorul nu se strică dacă între timp apar înregistrări noi: nu sari peste nimic și nu primești nimic de două ori.

Idempotency-Key

La POST, PATCH și PUT poți trimite antetul Idempotency-Key, un id unic al operației (id-ul comenzii, un UUID). Dacă repeți cererea în 24 de ore cu aceeași cheie și același corp, primești răspunsul primei cereri, cu antetul Idempotent-Replayed: true, fără ca operația să se mai execute: o factură nu se emite de două ori pentru că s-a pierdut un răspuns.

  • Aceeași cheie cu alt corp sau pe altă adresă: 422 idempotency_key_reused.
  • Cât timp prima cerere încă rulează: 409 idempotency_in_progress.
  • Erorile noastre (5xx) nu se păstrează: repetă cererea cu aceeași cheie.
  • Cheile sunt ale cheii API: două chei API diferite nu se încurcă între ele.

Erorile

Orice eroare vine cu codul HTTP potrivit și un corp application/problem+json (RFC 9457). Decizia în programul tău o iei după code, care rămâne același; detail este pentru oameni și se poate schimba.

codeHTTPCând
invalid_api_key401Lipsește antetul Authorization: Bearer chf_…, cheia e greșită sau a fost revocată.
missing_permission403Cheia nu are permisiunea pe firmă, sau rolul contului în firmă nu o mai permite (permission spune care).
forbidden403Acțiunea nu e permisă acestei firme (de exemplu email fără conexiune ANAF).
company_not_found404Firma nu există sau cheia nu ajunge la ea.
not_found404Documentul, clientul sau resursa nu există în firmă.
route_not_found404Adresa nu există în API.
method_not_allowed405Metoda nu e permisă pe adresă.
company_merged410Firma a fost unită cu firma verificată din merged_into; cheia ajunge acolo. Folosește noul id.
bad_request400O regulă a documentului nu e respectată (mesajul spune care).
invalid_json400Corpul nu e JSON valid.
invalid_body400Un câmp are alt tip decât cel așteptat.
unknown_field400Un câmp nu există (param spune care): nu ignorăm greșelile de scriere.
invalid_parameter400Un parametru din adresă nu e valid.
invalid_idempotency_key400Idempotency-Key are 1–255 caractere ASCII vizibile.
not_allowed_in_state409Documentul nu permite acțiunea în starea lui (de exemplu modificarea după ANAF).
conflict409Numărul e deja folosit în serie, sau altă coliziune.
in_flight409Documentul se trimite chiar acum la ANAF; reîncearcă în câteva secunde.
company_not_verified409Firma nu e încă verificată: RO e-Factura și facturile primite cer conectarea la ANAF din aplicație.
anaf_not_connected409Firma verificată nu are o conexiune ANAF activă (a expirat sau a fost revocată).
efactura_not_applicable409Firma nu trimite în RO e-Factura.
idempotency_in_progress409O cerere cu aceeași Idempotency-Key rulează încă.
idempotency_key_reused422Aceeași Idempotency-Key cu alt corp sau altă adresă.
too_large413Corpul depășește 1 MB.
rate_limited429Prea multe cereri; vezi Retry-After.
server_error500Eroare la noi; reîncearcă (cu aceeași Idempotency-Key).
anaf_unavailable502ANAF nu a răspuns; reîncearcă mai târziu.
bnr_unavailable502Cursul BNR pentru data cerută nu a putut fi luat.
mail_failed502Emailul nu a putut fi trimis.
{
  "type": "https://facturare.chappa.ro/dezvoltatori/api-firma/conventii#erori-missing-permission",
  "title": "Permisiune lipsă",
  "status": 403,
  "code": "missing_permission",
  "detail": "Cheia nu are permisiunea „documents:submit” pe această firmă.",
  "request_id": "req_4f1c09a2b7e3d610",
  "permission": "documents:submit"
}

Limitele

O cheie face cel mult 300 de cereri pe minut. Fiecare răspuns spune cât mai ai:

AntetCe este
RateLimit-LimitCererile pe minut.
RateLimit-RemainingCâte mai poți face în minutul acesta.
RateLimit-ResetPeste câte secunde începe minutul următor.

Peste limită primești 429 rate_limited, cu Retry-After în secunde. Dacă ai de emis multe facturi deodată, trimite-le pe rând sau câteva în paralel, nu sute simultan.

Versiunile

/v1 nu se strică: putem adăuga oricând câmpuri noi în răspunsuri, endpointuri noi, tipuri noi de evenimente și valori noi în listele de stări, așa că programul tău trebuie să ignore ce nu cunoaște. O schimbare care ar strica un program existent vine într-o versiune nouă, /v2, anunțată din timp, cu /v1 păstrat în paralel.

Id-ul cererii

Fiecare răspuns are antetul X-Request-Id (și erorile, câmpul request_id). Dacă ne scrii despre o cerere, trimite-ni-l: găsim exact ce s-a întâmplat.

OpenAPI

Contractul complet, cu toate endpointurile și obiectele, este la /api/v1/openapi.json (OpenAPI 3.1), fără autentificare. Permisiunea cerută de fiecare operație este în descrierea ei și în x-permissions.

Obiectele comune

Obiectul Problem

Orice eroare, ca application/problem+json (RFC 9457).

CâmpTipCe este
typestringAdresa explicației erorii în documentație.
titlestringCategoria, după codul HTTP.
statusintegerCodul HTTP, repetat.
codestringCodul stabil al erorii; pe el decide programul tău.
detailstringCe s-a întâmplat, în română, pentru oameni.
request_idstringId-ul cererii (și în antetul X-Request-Id); dă-l când ne scrii.
paramstring sau nullCâmpul sau parametrul greșit, când e unul anume.
permissionstring sau nullPermisiunea lipsă, la missing_permission.
merged_intointeger sau nullFirma în care a fost unită, la company_merged.

Obiectul List

O pagină dintr-o listă, cea mai nouă înregistrare prima.

CâmpTipCe este
objectlistMereu list.
datalistă de objectÎnregistrările paginii.
has_morebooleanMai sunt înregistrări după această pagină.
next_cursorstring sau nullValoarea pentru starting_after la pagina următoare (id-ul ultimei înregistrări).

Obiectul Deleted

Răspunsul unei ștergeri.

CâmpTipCe este
idintegerId-ul șters.
objectstringCe a fost șters.
deletedbooleanMereu true.