Erori, limite și reluări
O eroare are altă formă decât un răspuns reușit. Nu vine în data; vine ca application/problem+json, după RFC 9457, cu un type stabil pe care poți ramifica.
{
"type": "urn:fgo:api:v2:problem:validation",
"title": "Cererea nu a trecut validarea.",
"status": 400,
"detail": "Corectati campurile indicate in validation_errors si retrimiteti cererea.",
"instance": "urn:trace:00-8f3a1c2e6b7d4e219c0a2f1d3e4b5a6c-5f2a9c1d3e4b6a7c-01",
"meta": { "timestamp": "2026-09-03T12:00:00+03:00" },
"validation_errors": [
{
"location": "body",
"pointer": "/articole",
"issues": [
{ "code": "invalid_value", "message": "Campul 'articole' trebuie sa contina cel putin un articol." }
]
}
]
}
Ramifică pe type și pe status. title și detail sunt pentru oameni și se pot schimba fără o versiune nouă.
Categoriile
type (sufixul) |
Status | Înseamnă |
|---|---|---|
validation |
400 | cererea e greșită: câmp lipsă, format, valoare din afara nomenclatorului, parametru necunoscut |
invalid-state |
400 | cererea e corectă, dar starea resursei nu permite operația — o factură încasată nu se șterge |
invalid-cursor |
400 | cursorul de paginare nu e al nostru sau nu mai e valabil |
unauthenticated |
401 | cheia lipsește, e expirată sau revocată |
forbidden |
403 | cheia n-are dreptul, abonamentul nu include operația, sau Fgo-Cod-Unic nu e firma cheii |
resource-not-found |
404 | factura, clientul, arhiva nu există — sau sunt ale altei firme, ceea ce arată identic |
url-not-found |
404 | adresa nu există |
unsupported-media-type |
415 | corpul nu e application/json |
rate-limited |
429 | prea multe apeluri; vezi Retry-After |
internal |
500 | eroare internă; trimite-ne valorile headerelor Server-Timing și x-fapi-interaction-id |
maintenance |
503 | mentenanță planificată; API-ul e închis temporar, vezi Retry-After |
validation_errors
Apare numai pe validation și spune exact ce e greșit, câmp cu câmp. Fiecare element are location — body, query, path, header — și, după caz, pointer (JSON Pointer în corp, ca /articole/0/cota_tva) sau name (numele parametrului sau al headerului). issues are codul stabil al problemei și un mesaj.
Codurile din issues[].code sunt extensibile: pot apărea coduri noi. Pentru un cod necunoscut, tratează răspunsul ca eroare de validare și folosește mesajul pentru diagnosticare. Codurile existente sunt:
code |
Înseamnă |
|---|---|
required |
elementul obligatoriu lipsește sau e gol |
invalid_format |
valoarea nu se poate citi în tipul sau forma așteptată: dată, număr, identificator, JSON stricat, tipar nerespectat |
out_of_range |
număr sau lungime în afara limitelor; limitele vin în args: min, max, exclusive, value pentru numere, min_length, max_length, length pentru texte și liste |
subfields_required |
un câmp compus a fost cerut în fields fără subcâmpuri, de exemplu incasari în loc de incasari(suma_incasata) |
invalid_value |
orice altă regulă: valoare din afara nomenclatorului, parametru sau câmp necunoscut |
De exemplu, o serie de storno prea lungă:
{ "code": "out_of_range", "message": "Dimensiunea valorii transmise pentru 'serie_storno' nu trebuie sa depaseasca 50 caractere.", "args": { "max_length": 50, "length": 60 } }
O cerere cu trei câmpuri greșite primește un singur 400 cu trei elemente, nu trei runde de încercări.
Validarea e strictă în amândouă direcțiile: un parametru de query sau o proprietate JSON pe care nu o documentăm e 400, nu e ignorată. Așa afli de o greșeală de tastare din prima, nu după ce filtrul „n-a mers".
motiv
Pe 403 și pe invalid-state, motiv.cod spune de ce:
{
"type": "urn:fgo:api:v2:problem:forbidden",
"status": 403,
"detail": "Nu ai suficiente drepturi, vezi motiv.",
"motiv": { "cod": "drept_lipsa", "mesaj": "Cheia API nu are dreptul de a face aceasta operatie." }
}
motiv.cod |
Răspuns | Înseamnă |
|---|---|---|
drept_lipsa |
403 forbidden |
cheia nu are dreptul cerut sau abonamentul nu include operația |
cod_unic_neasociat |
403 forbidden |
Fgo-Cod-Unic nu corespunde firmei asociate cheii API |
factura_duplicata |
400 invalid-state |
cu verificare_duplicat=true, a fost găsită o factură după id_extern sau, dacă acesta lipsește, după serie și număr; nu se emite o factură nouă |
stare_nepermisa |
400 invalid-state |
regulile de business sau starea resursei nu permit operația; explicația este în motiv.mesaj |
De exemplu, o emitere oprită de verificarea duplicatelor:
{
"type": "urn:fgo:api:v2:problem:invalid-state",
"title": "Starea curenta nu permite operatia",
"status": 400,
"detail": "Factura FGO 1201 exista deja salvata.",
"instance": "urn:trace:00-8f3a1c2e6b7d4e219c0a2f1d3e4b5a6c-5f2a9c1d3e4b6a7c-01",
"meta": { "timestamp": "2026-09-03T12:00:00+03:00" },
"motiv": {
"cod": "factura_duplicata",
"mesaj": "Factura FGO 1201 exista deja salvata."
}
}
Criteriile verificării sunt descrise în Emiterea unei facturi.
Codurile din motiv.cod sunt extensibile. Pentru un cod necunoscut, tratează refuzul folosind status și type, fără să presupui că operația a reușit.
Limitele de apeluri
Fiecare operație acceptă un apel la un interval fix, pe firmă. A doua cerere pe aceeași operație, înainte să treacă intervalul, primește 429. Operațiile diferite nu se influențează: poți citi articolele și gestiunile în aceeași secundă. Intervalele, pe fiecare operație:
| Operație | Interval |
|---|---|
GET /v2/identitate |
1 s |
POST /v2/facturi |
1 s |
GET /v2/facturi |
1 s |
GET /v2/facturi/{serie}/{numar} |
1 s, împărțit cu trimite-in-spv |
GET /v2/facturi/{serie}/{numar}/efactura |
1 s |
POST /v2/facturi/{serie}/{numar}/genereaza-link-de-descarcare |
1 s |
POST /v2/facturi/{serie}/{numar}/genereaza-link-de-plata |
1 s |
PUT /v2/facturi/{serie}/{numar}/awb |
1 s |
POST /v2/facturi/{serie}/{numar}/anuleaza |
1 s |
POST /v2/facturi/{serie}/{numar}/storneaza |
1 s |
POST /v2/facturi/{serie}/{numar}/repune-in-vigoare-factura-stornata |
1 s |
DELETE /v2/facturi/{serie}/{numar} |
1 s |
POST /v2/facturi/{serie}/{numar}/incasari |
1 s |
DELETE /v2/facturi/{serie}/{numar}/incasari |
1 s, împărțit cu ștergerea unei singure încasări |
DELETE /v2/facturi/{serie}/{numar}/incasari/{serie_incasare}/{numar_incasare} |
1 s, împărțit cu ștergerea tuturor încasărilor |
POST /v2/facturi/{serie}/{numar}/trimite-in-spv |
1 s, împărțit cu citirea facturii |
POST /v2/facturi/{serie}/{numar}/trimite-pe-email |
1 s |
GET /v2/facturi/{serie}/{numar}/asociate |
1 s |
GET /v2/facturi/facturi-restante |
1 s |
GET /v2/facturi-furnizori |
1 s |
GET /v2/clienti |
1 s |
GET /v2/clienti/{id_client}/sold |
1 s |
GET /v2/clienti/clienti-cu-restante |
1 s |
GET /v2/furnizori |
1 s |
GET /v2/furnizori/{id_furnizor}/sold |
1 s |
POST /v2/fise-partener |
10 s |
GET /v2/articole |
1 s |
GET /v2/gestiuni |
1 s |
GET /v2/stocuri/stoc-gestiune-articol |
1 s |
GET /v2/stocuri/stoc-gestiune |
1 s |
GET /v2/stocuri/stoc-articol |
1 s |
GET /v2/rapoarte/totaluri-vanzari |
10 s |
GET /v2/rapoarte/vanzari-lunare |
10 s |
GET /v2/rapoarte/vanzari-achizitii-articole |
10 s |
GET /v2/solduri-conturi-bancare |
1 s |
GET /v2/balante/balanta-indicatori-financiari |
10 s |
GET /v2/balante/balanta-contabila |
10 s |
GET /v2/firme/pozitie-anaf |
10 s |
POST /v2/arhive-pdf |
30 s |
POST /v2/arhive-pdf/verifica-limite |
5 s |
GET /v2/arhive-pdf/{id_arhiva} |
5 s |
POST /v2/arhive-pdf/{id_arhiva}/genereaza-link-de-descarcare |
1 s |
nomenclatoarele, de exemplu GET /v2/nomenclatoare/judete |
1 s |
Aceleași intervale sunt scrise și în referință, la răspunsul 429 al fiecărei operații. Listele paginate nu au un interval separat pentru pagini: fiecare pagină e un apel, la o secundă de precedenta.
Limitele se pot modifica. La depășirea limitei, răspunsul indică timpul de așteptare:
HTTP/1.1 429 Too Many Requests
Retry-After: 1
Așteaptă cel puțin numărul de secunde din Retry-After înainte de a reîncerca. Pentru apeluri în lot, respectă intervalul operației și ajustează pauza după acest header.
Ce reîncerci și ce nu
Citirile (GET) se reîncearcă în siguranță. La 5xx, reîncearcă de câteva ori cu pauză crescătoare — o secundă, două, patru — apoi oprește-te.
Scrierile nu se reîncearcă automat, decât acolo unde documentația operației spune că e sigur. Atașarea AWB-ului e idempotentă: același AWB de două ori lasă factura în aceeași stare. Emiterea nu e: fiecare apel reușit produce o factură. Ce faci când nu primești răspuns la emitere e descris în Emiterea unei facturi.
La 429 sau 503 maintenance returnat de FGO API, reîncearcă după intervalul indicat de Retry-After: cererea nu a fost procesată. 503 maintenance înseamnă mentenanță planificată: documentația rămâne disponibilă, API-ul nu. Pentru un alt răspuns 503, inclusiv unul generat de un proxy, nu presupune că cererea nu a fost procesată; aplică regulile de reluare ale operației.
Identificarea unei cereri
Fiecare răspuns conține headerul x-fapi-interaction-id, identificatorul cererii:
x-fapi-interaction-id: 7b0f11d5-6e52-4b57-beca-15f47bf3f808
UUID (numit și GUID) este un identificator de 128 de biți, definit de RFC 9562. Folosește forma cu cratime din exemplu. Poți trimite tu un UUID în acest header; dacă lipsește sau nu este valid, îl generăm noi și îl întoarcem în răspuns. Păstrează-l în logurile tale.
Când semnalezi o problemă suportului, trimite valorile headerelor Server-Timing și x-fapi-interaction-id din răspunsul primit. Dacă nu ai primit niciun răspuns, trimite UUID-ul generat de tine.