Convenții
Regulile care se aplică fiecărei cereri.
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 estenull, nu lipsește. - Datele calendaristice:
AAAA-LL-ZZ, în ora României. Momentele: RFC 3339 cu fus orar, de exemplu2026-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.
| code | HTTP | Când |
|---|---|---|
invalid_api_key | 401 | Lipsește antetul Authorization: Bearer chf_…, cheia e greșită sau a fost revocată. |
missing_permission | 403 | Cheia nu are permisiunea pe firmă, sau rolul contului în firmă nu o mai permite (permission spune care). |
forbidden | 403 | Acțiunea nu e permisă acestei firme (de exemplu email fără conexiune ANAF). |
company_not_found | 404 | Firma nu există sau cheia nu ajunge la ea. |
not_found | 404 | Documentul, clientul sau resursa nu există în firmă. |
route_not_found | 404 | Adresa nu există în API. |
method_not_allowed | 405 | Metoda nu e permisă pe adresă. |
company_merged | 410 | Firma a fost unită cu firma verificată din merged_into; cheia ajunge acolo. Folosește noul id. |
bad_request | 400 | O regulă a documentului nu e respectată (mesajul spune care). |
invalid_json | 400 | Corpul nu e JSON valid. |
invalid_body | 400 | Un câmp are alt tip decât cel așteptat. |
unknown_field | 400 | Un câmp nu există (param spune care): nu ignorăm greșelile de scriere. |
invalid_parameter | 400 | Un parametru din adresă nu e valid. |
invalid_idempotency_key | 400 | Idempotency-Key are 1–255 caractere ASCII vizibile. |
not_allowed_in_state | 409 | Documentul nu permite acțiunea în starea lui (de exemplu modificarea după ANAF). |
conflict | 409 | Numărul e deja folosit în serie, sau altă coliziune. |
in_flight | 409 | Documentul se trimite chiar acum la ANAF; reîncearcă în câteva secunde. |
company_not_verified | 409 | Firma nu e încă verificată: RO e-Factura și facturile primite cer conectarea la ANAF din aplicație. |
anaf_not_connected | 409 | Firma verificată nu are o conexiune ANAF activă (a expirat sau a fost revocată). |
efactura_not_applicable | 409 | Firma nu trimite în RO e-Factura. |
idempotency_in_progress | 409 | O cerere cu aceeași Idempotency-Key rulează încă. |
idempotency_key_reused | 422 | Aceeași Idempotency-Key cu alt corp sau altă adresă. |
too_large | 413 | Corpul depășește 1 MB. |
rate_limited | 429 | Prea multe cereri; vezi Retry-After. |
server_error | 500 | Eroare la noi; reîncearcă (cu aceeași Idempotency-Key). |
anaf_unavailable | 502 | ANAF nu a răspuns; reîncearcă mai târziu. |
bnr_unavailable | 502 | Cursul BNR pentru data cerută nu a putut fi luat. |
mail_failed | 502 | Emailul 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:
| Antet | Ce este |
|---|---|
RateLimit-Limit | Cererile pe minut. |
RateLimit-Remaining | Câte mai poți face în minutul acesta. |
RateLimit-Reset | Peste 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âmp | Tip | Ce este |
|---|---|---|
type | string | Adresa explicației erorii în documentație. |
title | string | Categoria, după codul HTTP. |
status | integer | Codul HTTP, repetat. |
code | string | Codul stabil al erorii; pe el decide programul tău. |
detail | string | Ce s-a întâmplat, în română, pentru oameni. |
request_id | string | Id-ul cererii (și în antetul X-Request-Id); dă-l când ne scrii. |
param | string sau null | Câmpul sau parametrul greșit, când e unul anume. |
permission | string sau null | Permisiunea lipsă, la missing_permission. |
merged_into | integer sau null | Firma în care a fost unită, la company_merged. |
Obiectul List
O pagină dintr-o listă, cea mai nouă înregistrare prima.
| Câmp | Tip | Ce este |
|---|---|---|
object | list | Mereu list. |
data | listă de object | Înregistrările paginii. |
has_more | boolean | Mai sunt înregistrări după această pagină. |
next_cursor | string sau null | Valoarea pentru starting_after la pagina următoare (id-ul ultimei înregistrări). |
Obiectul Deleted
Răspunsul unei ștergeri.
| Câmp | Tip | Ce este |
|---|---|---|
id | integer | Id-ul șters. |
object | string | Ce a fost șters. |
deleted | boolean | Mereu true. |