e-bon
e-bon.ro
Referință API

Referință erori

Catalogul canonic al fiecărei erori pe care e-bon o poate returna — coduri la nivel HTTP din API și coduri FiscalError de la aparat — cu pași de recuperare per cod.

Referință erori

Această pagină este indexul canonic al fiecărei erori pe care platforma e-bon o poate expune unui integrator. Folosește-o ca singura sursă de adevăr când implementezi tratarea erorilor în POS-ul tău, în back-office sau în propriile unelte construite peste @e-bon/sdk.

Există două familii distincte de erori, care trăiesc la straturi diferite:

  • Erorile HTTP sunt eșecuri la nivelul protocolului, returnate direct de API-ul REST e-bon. Le vezi chiar pe răspuns — un status non-2xx cu un corp JSON — pentru eșecuri de transport, autentificare, autorizare, validare și limitare de rată. Nu implică deloc imprimanta.
  • Codurile FiscalError sunt eșecuri pe partea de aparat. Cererea HTTP care a trimis comanda poate să fi reușit cu un 202 Accepted; eșecul s-a întâmplat ulterior, pe AMEF sau în driver-ul fiscal. Vezi codurile fiscale în interiorul rezultatului comenzii returnat de GET /api/v1/commands/{id} (în câmpul error), în payload-urile evenimentelor command.failed și — când folosești @e-bon/sdk — sub forma unei excepții tipate FiscalError.

Ambele familii respectă aceeași regulă: ramifică pe șirul code, nu pe mesajul lizibil. Mesajele se pot reformula; codurile sunt stabile.

Plicul de eroare

Fiecare răspuns HTTP de eroare — fie că vine din validare, autentificare, logica de business sau un crash netratat — respectă același plic JSON încapsulat:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      { "path": "items[0].vatRate", "message": "vatRate must be one of: 0, 9, 11, 21" }
    ]
  }
}
  • error.code — un identificator stabil de tip text din catalogul de mai jos. Mereu prezent.
  • error.message — un mesaj lizibil în limba engleză. Util pentru loguri; nu îl analiza programatic.
  • error.details — opțional. Apare la VALIDATION_ERROR (erori per câmp de la Zod, sub formă de { path, message }[]) și la câteva alte coduri care au nevoie de payload structurat.

Un răspuns tipic 400 VALIDATION_ERROR complet:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      { "path": "items.0.vatRate", "message": "vatRate must be one of: 0, 9, 11, 21" },
      { "path": "items.0.unitPrice", "message": "Number must be greater than 0" }
    ]
  }
}
Răspunsul 429 RATE_LIMIT_EXCEEDED returnat de limitatorul global este emis în prezent ca obiect plat — { "code": "RATE_LIMIT_EXCEEDED", "message": "...", "status": 429 } — în loc de plicul standard. Verifică pe câmpul code; tratează ambele forme ca echivalente până când API-ul este normalizat.

Coduri de eroare HTTP

Douăsprezece coduri stabile. Fiecare răspuns non-2xx de la https://api.e-bon.ro poartă unul dintre ele în error.code (sau în code pentru excepția 429 de mai sus).

UNAUTHORIZED

HTTP 401. Cererea a ajuns la API, dar nu s-a putut rezolva nicio credențială validă — antet x-api-key / Authorization lipsă, cheie malformată, cheie necunoscută sau cheia a fost revocată ori marcată inactivă.

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Missing or invalid API key"
  }
}

Ce ai de făcut

  • Confirmă că cererea poartă fie x-api-key: ebon_live_..., fie Authorization: Bearer ebon_live_....
  • Verifică în Portal că cheia nu a fost revocată sau dezactivată (Setări → Chei API).
  • Asigură-te că lovești mediul corect — cheile de producție nu funcționează pe staging și invers.
  • Rotește cheia dacă suspectezi o scurgere și actualizează depozitul de secrete.

TOKEN_EXPIRED

HTTP 401. Token-ul JWT de acces prezentat unui endpoint de tip Portal a expirat. Se aplică doar la autentificarea JWT, nu la cheile API (care nu expiră).

{
  "error": {
    "code": "TOKEN_EXPIRED",
    "message": "Access token expired"
  }
}

Ce ai de făcut

  • Apelează POST /api/v1/auth/refresh cu token-ul de refresh pentru a obține un token de acces nou.
  • Reia cererea originală cu antetul Authorization: Bearer <accessToken> actualizat.
  • Dacă și endpoint-ul de refresh returnează 401, forțează utilizatorul să se autentifice din nou.
  • Pentru integrările neinteractive, preferă o cheie API — aceasta nu expiră.

FORBIDDEN

HTTP 403. Autentificarea a reușit, dar entitatea autentificată nu are permisiunea sau rolul necesar pentru acest endpoint sau această resursă.

{
  "error": {
    "code": "FORBIDDEN",
    "message": "Insufficient permissions for this operation"
  }
}

Ce ai de făcut

  • Verifică permisiunile configurate ale cheii API față de referința Autentificare.
  • La autentificare JWT, confirmă că rolul utilizatorului în organizație (Owner / Admin / Member) acoperă acțiunea.
  • Reemite cheia API cu permisiunile corecte dacă a fost creată cu un set prea restrâns.
  • Asigură-te că resursa (aparat, bon, comandă) chiar aparține organizației pe care credențiala o acoperă.

VALIDATION_ERROR

HTTP 400. Corpul cererii sau parametrii din URL au eșuat la validarea schemei Zod. details este o listă de intrări { path, message }, una per câmp eșuat.

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      { "path": "items.0.vatRate", "message": "vatRate must be one of: 0, 9, 11, 21" }
    ]
  }
}

Ce ai de făcut

  • Iterează prin error.details și prezintă fiecare pereche path / message operatorului sau dezvoltatorului.
  • Corectează câmpul greșit local; nu relua orbește — cererea va eșua la fel.
  • Verifică pagina resursei relevante (Bonuri, Comenzi, Dispozitive) pentru forma exactă acceptată.
  • Pentru eșecuri de validare ale Idempotency-Key, regenerează o cheie care respectă [a-zA-Z0-9_-]{1,128}.

BAD_REQUEST

HTTP 400. Cererea este malformată într-un mod care nu este strict o încălcare de schemă — JSON invalid, Content-Type nesuportat, corp prea mare sau o precondiție de endpoint pe care schema nu o poate exprima.

{
  "error": {
    "code": "BAD_REQUEST",
    "message": "Request body is not valid JSON"
  }
}

Ce ai de făcut

  • Confirmă că corpul este JSON valid în UTF-8 și că Content-Type: application/json este trimis la fiecare POST / PATCH / PUT.
  • Verifică dimensiunea payload-ului față de limitele documentate per endpoint.
  • Citește atent error.message — de obicei numește precondiția specifică ce a eșuat.
  • Nu relua fără să schimbi cererea; acest cod nu este tranzitoriu.

NOT_FOUND

HTTP 404. Resursa adresată nu există pentru organizația credențialei. Fie ID-ul este greșit, fie resursa a fost ștearsă, fie aparține altei organizații decât cea pe care o acoperă credențiala.

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Device not found"
  }
}

Ce ai de făcut

  • Confirmă că ID-ul este bine format și se potrivește cu prefixul așteptat al resursei (dev_, cmd_, rec_, rep_, key_).
  • Listează colecția părinte (GET /api/v1/devices, GET /api/v1/commands) ca să verifici că resursa încă există.
  • Asigură-te că nu interoghezi cu o credențială delimitată la altă organizație.
  • Dacă resursa a fost ștearsă, recreează-o sau elimină referința expirată din cache-ul tău local.

CONFLICT

HTTP 409. Operațiunea este incompatibilă cu starea curentă a resursei — de exemplu anularea unei comenzi deja completed sau failed, sau actualizarea unui webhook care a fost dezactivat.

{
  "error": {
    "code": "CONFLICT",
    "message": "Command is already completed and cannot be cancelled"
  }
}

Ce ai de făcut

  • Reia resursa (GET /api/v1/commands/{id}, GET /api/v1/devices/{id}) și inspectează status înainte de a relua acțiunea.
  • Decide dacă conflictul este acceptabil (operațiunea s-a întâmplat deja) sau dacă trebuie să iei o altă acțiune.
  • Nu relua orbește — vei primi același 409. Starea trebuie să se schimbe mai întâi.
  • Pentru operațiuni idempotente, preferă fluxul cu Idempotency-Key, ca o întrerupere de rețea să nu apară ca CONFLICT.

UNPROCESSABLE_ENTITY

HTTP 422. Cererea a fost validă sintactic și a trecut validarea de schemă, dar starea de business a sistemului o respinge — de exemplu încercarea de a tipări un storno mai mare decât bonul original.

{
  "error": {
    "code": "UNPROCESSABLE_ENTITY",
    "message": "Refund amount exceeds the original receipt total"
  }
}

Ce ai de făcut

  • Citește error.message pentru regula de business specifică ce a eșuat.
  • Reinterogă resursele asociate (bonul original, starea curentă a aparatului) și ajustează cererea.
  • Nu relua fără să schimbi payload-ul.
  • Dacă regula nu se potrivește cu așteptările tale, verifică pagina de referință a resursei pentru ultimele constrângeri.

RATE_LIMIT_EXCEEDED

HTTP 429. Ai depășit o fereastră de limitare a ratei. Răspunsul include un antet Retry-After (secunde, întreg) care îți spune exact cât trebuie să aștepți. Atenție: acest cod ajunge în prezent ca obiect plat, nu sub formă de plic încapsulat.

HTTP/1.1 429 Too Many Requests
Retry-After: 47
RateLimit-Limit: 150
RateLimit-Remaining: 0
RateLimit-Reset: 47
Content-Type: application/json

{
  "code": "RATE_LIMIT_EXCEEDED",
  "message": "Too many requests, please try again later.",
  "status": 429
}

Ce ai de făcut

  • Așteaptă valoarea din Retry-After (secunde) înainte de următoarea încercare — niciodată mai devreme.
  • Adaugă deasupra un backoff exponențial, plafonat la un maxim rezonabil, în caz că 429 se repetă.
  • Distribuie traficul în rafale pe lățimea ferestrei de limitare în loc să-l tragi tot dintr-o dată.
  • Dacă atingi des limitele, discută cu echipa e-bon sau grupează operațiunile în mai puține cereri; vezi Limite de rată.

TIER_LIMIT_EXCEEDED

HTTP 403. Planul de abonament al organizației nu permite această acțiune — de exemplu încercarea de a crea mai mult de două aparate pe planul Free, sau crearea unei chei API pe planul Free în general.

{
  "error": {
    "code": "TIER_LIMIT_EXCEEDED",
    "message": "Free plan is limited to 2 devices"
  }
}

Ce ai de făcut

  • Verifică tabelul de restricții pe planuri pentru limitele aplicate planului tău.
  • Fă upgrade planul organizației din pagina de facturare a Portalului ca să ridici limita.
  • Șterge aparatele sau cheile nefolosite înainte să creezi altele noi, dacă nu vrei să faci upgrade.
  • Acest cod este permanent pentru planul curent; nu relua fără să schimbi starea.

INTERNAL_ERROR

HTTP 500. O eroare netratată pe server. Cererea nu a produs rezultatul dorit, iar serverul a logat eșecul cu un ID de cerere pentru investigare.

{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An internal error occurred"
  }
}

Ce ai de făcut

  • Pentru operațiuni idempotente (și pentru toate POST-urile care poartă un Idempotency-Key), reia o singură dată după un backoff scurt.
  • Capturează ID-ul cererii din propriile loguri (și din antetele răspunsului dacă e prezent), ca echipa de suport să poată corela.
  • Nu presupune că operațiunea s-a întâmplat sau nu — reia resursa pentru a afla.
  • Dacă eroarea persistă, deschide un tichet de suport cu ID-ul cererii, ID-ul comenzii și pașii de reproducere.

SERVICE_UNAVAILABLE

HTTP 503. Serverul refuză temporar cererile — de obicei un deploy în desfășurare, indisponibilitatea unei dependențe sau o fereastră de mentenanță forțată.

{
  "error": {
    "code": "SERVICE_UNAVAILABLE",
    "message": "Service temporarily unavailable, please try again shortly"
  }
}

Ce ai de făcut

  • Reia cu backoff exponențial — pornește de la 1s și dublează până la un plafon rezonabil.
  • Refolosește același Idempotency-Key, ca duplicatele să fie absorbite de cache odată ce serviciul revine.
  • Verifică pagina de status e-bon sau canalul tău de suport înainte de a ridica o alertă.
  • Nu transforma asta în alertă la prima apariție; indisponibilitatea tranzitorie este așteptată în timpul deploy-urilor.

Coduri de eroare fiscale / de aparat (FiscalError)

Douăzeci și cinci de coduri grupate în șase familii după prima cifră. Apar în interiorul rezultatelor de comandă (error.code la GET /api/v1/commands/{id}), în payload-urile evenimentelor command.failed și sub forma câmpului code de pe excepția tipată FiscalError aruncată de @e-bon/sdk.

Forma FiscalError din SDK:

class FiscalError extends Error {
  code: ErrorCode       // unul din codurile de mai jos
  message: string
  retryable: boolean    // precalculat prin isRetryable(code)
  commandId?: string
  deviceId?: string
}

Doar cinci coduri sunt reluabile: E101 ConnectionTimeout, E102 ConnectionLost, E500 TimeoutCommand, E501 TimeoutResponse, E502 TimeoutConnection. Restul sunt netranzitorii și cer o acțiune — pe payload-ul cererii, pe imprimantă sau de la un tehnician de service.

E1xx Erori de conexiune

Eșecuri la deschiderea sau menținerea unei conexiuni la nivel de transport între aplicația mobilă E-BON și imprimanta AMEF. Verifică întotdeauna endpoint-ul de transport configurat (host și port TCP, împerechere Bluetooth, cablu USB, port serial) înainte de orice altceva.

E100 ConnectionRefused

  • Reluabil: nu.
  • Semnificație: imprimanta a refuzat activ conexiunea TCP / Bluetooth / USB / serială.
  • Recuperare: verifică transportul — host-ul și portul TCP, împerecherea Bluetooth, cablul USB sau portul serial — și confirmă că imprimanta este pornită și nu este deja folosită de un alt controller.

E101 ConnectionTimeout

  • Reluabil: da.
  • Semnificație: tentativa de conectare a expirat înainte ca imprimanta să răspundă.
  • Recuperare: reia cu backoff exponențial (flag-ul retryable din SDK este deja true pentru acest cod); dacă continuă să eșueze, verifică transportul — accesibilitatea host-ului TCP, împerecherea Bluetooth, cablul USB, portul serial — pentru o legătură picată.

E102 ConnectionLost

  • Reluabil: da.
  • Semnificație: o conexiune deja stabilită s-a întrerupt în timpul operațiunii.
  • Recuperare: reia cu același Idempotency-Key; dacă pierderea se repetă, verifică transportul (TCP, Bluetooth, USB, port serial) pentru o legătură fizică instabilă.

E103 ConnectionNotFound

  • Reluabil: nu.
  • Semnificație: endpoint-ul de transport configurat nu poate fi atins deloc.
  • Recuperare: verifică configurația de transport pe aparat — host-ul și portul TCP, împerecherea Bluetooth, calea dispozitivului USB, numele portului serial — și confirmă că imprimanta este pe aceeași rețea sau magistrală.

E2xx Erori de protocol

Eșecuri la nivelul protocolului — imprimanta a răspuns, dar octeții nu se aliniază cu protocolul configurat. Verifică dacă protocolul selectat pentru AMEF în Portal corespunde firmware-ului și modelului real al imprimantei.

E200 ProtocolMismatch

  • Reluabil: nu.
  • Semnificație: imprimanta a răspuns, dar protocolul ei nu corespunde celui configurat pentru aparat.
  • Recuperare: verifică potrivirea de protocol/firmware între AMEF-ul configurat și imprimanta reală; reconfigurează aparatul cu protocolul corect în Portal.

E201 ProtocolUnsupported

  • Reluabil: nu.
  • Semnificație: comanda nu este suportată de protocolul acestei imprimante.
  • Recuperare: verifică potrivirea de protocol/firmware între AMEF-ul configurat și imprimantă; alege o variantă de comandă pe care protocolul imprimantei o suportă sau actualizează firmware-ul imprimantei.

E202 ProtocolChecksumError

  • Reluabil: nu.
  • Semnificație: un cadru a fost primit, dar a eșuat la validarea sumei de control.
  • Recuperare: verifică potrivirea de protocol/firmware între AMEF-ul configurat și imprimantă; asigură-te că niciun alt program nu vorbește cu imprimanta pe același transport în același timp.

E203 ProtocolFramingError

  • Reluabil: nu.
  • Semnificație: un cadru malformat a ajuns la nivelul protocolului.
  • Recuperare: verifică potrivirea de protocol/firmware între AMEF-ul configurat și imprimantă; verifică dacă cablajul sau legătura radio este suficient de stabilă pentru a livra cadre complete.

E3xx Erori fiscale

Eșecuri venite din logica fiscală a imprimantei — hârtie, limită zilnică, bonuri deschise, memorie fiscală. Acestea cer un operator pe loc sau o intervenție de service, nu încă un tur prin rețea.

E300 FiscalMemoryFull

  • Reluabil: nu.
  • Semnificație: memoria fiscală (Memorie Fiscală) a imprimantei este plină.
  • Recuperare: contactează service-ul pentru memorie fiscală plină — necesită intervenție de service autorizat; aparatul nu mai poate tipări bonuri fiscale până când nu este înlocuită.

E301 FiscalReceiptOpen

  • Reluabil: nu.
  • Semnificație: există deja un bon deschis pe imprimantă.
  • Recuperare: roagă operatorul să închidă bonul deschis (finalizare sau anulare) pe imprimantă, apoi retrimite comanda.

E302 FiscalNoReceiptOpen

  • Reluabil: nu.
  • Semnificație: comanda necesită un bon deschis, dar niciunul nu este deschis.
  • Recuperare: deschide întâi un bon (SDK-ul / API-ul expune comanda dedicată), apoi reia comanda de linie.

E303 FiscalDailyLimitReached

  • Reluabil: nu.
  • Semnificație: imprimanta a atins limita fiscală zilnică și nu mai poate tipări bonuri astăzi.
  • Recuperare: roagă operatorul să ruleze un raport Z (închidere de zi) pe imprimantă, apoi reia — contoarele zilnice se resetează.

E304 FiscalHardwareError

  • Reluabil: nu.
  • Semnificație: o eroare hardware pe imprimantă — hârtie, cap, capac, sertar sau similar.
  • Recuperare: roagă operatorul să schimbe rola de hârtie, să închidă capacul sau să elimine blocajul; dacă defectul persistă, contactează service-ul.

E4xx Erori de validare

Driver-ul a respins payload-ul comenzii înainte de a trimite ceva spre imprimantă. Sunt deterministe — același payload va eșua la fel până când îl schimbi.

E400 ValidationInvalidPayload

  • Reluabil: nu.
  • Semnificație: payload-ul comenzii a fost respins de driver.
  • Recuperare: corectează payload-ul cererii — recitește referința comenzii și ajustează corpul, apoi retrimite.

E401 ValidationMissingField

  • Reluabil: nu.
  • Semnificație: un câmp obligatoriu din payload lipsește.
  • Recuperare: corectează payload-ul cererii adăugând câmpul obligatoriu; verifică referința comenzii pentru lista completă a câmpurilor obligatorii.

E402 ValidationInvalidAmount

  • Reluabil: nu.
  • Semnificație: o valoare monetară este în afara intervalului legal.
  • Recuperare: corectează payload-ul cererii — limitează sumele la intervalul documentat și verifică unitățile (lei, bani, întreg vs. zecimal).

E403 ValidationInvalidVatRate

  • Reluabil: nu.
  • Semnificație: cota de TVA nu este una dintre cele patru cote legale din România (0, 9, 11, 21).
  • Recuperare: corectează payload-ul cererii — alege una dintre 0, 9, 11, 21; respinge orice altă valoare la nivelul POS-ului tău înainte de trimitere.

E5xx Erori de timeout

Comanda a fost trimisă, dar nu s-a încheiat în fereastra așteptată. Toate codurile E5xx sunt reluabile.

E500 TimeoutCommand

  • Reluabil: da.
  • Semnificație: comanda nu s-a încheiat în limita de timp pentru execuție.
  • Recuperare: reia cu backoff exponențial; respectă același Idempotency-Key, ca o primă încercare reușită memorată pe server să nu fie reexecutată.

E501 TimeoutResponse

  • Reluabil: da.
  • Semnificație: imprimanta a început comanda, dar nu a returnat un răspuns la timp.
  • Recuperare: reia cu backoff exponențial; respectă același Idempotency-Key și verifică hârtia tipărită ca să confirmi dacă comanda chiar s-a executat.

E502 TimeoutConnection

  • Reluabil: da.
  • Semnificație: s-a produs un timeout la nivelul conexiunii cu imprimanta.
  • Recuperare: reia cu backoff exponențial; respectă același Idempotency-Key și, dacă timeout-ul se repetă, verifică și transportul (TCP, Bluetooth, USB, serial).

E9xx Erori necunoscute

Eșecuri neclasificate sau neașteptate la nivelul driver-ului. Scapă din categoriile de mai sus și au nevoie de investigare.

E900 Unknown

  • Reluabil: nu.
  • Semnificație: o eroare driver neclasificată.
  • Recuperare: deschide un tichet de suport cu ID-ul cererii și ID-ul comenzii eșuate, ca echipa e-bon să poată investiga.

E901 InternalError

  • Reluabil: nu.
  • Semnificație: o eroare internă neașteptată în stratul de driver.
  • Recuperare: deschide un tichet de suport cu ID-ul cererii și ID-ul comenzii eșuate; capturează modelul aparatului și versiunea de firmware dacă le ai.

E902 UnexpectedResponse

  • Reluabil: nu.
  • Semnificație: imprimanta a returnat un răspuns pe care driver-ul nu l-a putut interpreta.
  • Recuperare: deschide un tichet de suport cu ID-ul cererii și ID-ul comenzii eșuate; include modelul AMEF, ca driver-ul să fie învățat noua formă de răspuns.

Model universal pentru SDK

Fiecare loc de catch din codul tău poate folosi aceeași formă: prinde ambele familii de erori, reia doar când err.retryable este true (pentru eșecurile fiscale) sau când Retry-After este setat (pentru limitele HTTP), refolosește același Idempotency-Key la reîncercare.

import { EBonApiError, FiscalError, isRetryable } from '@e-bon/sdk';

try {
  await client.commands.send(body);
} catch (err) {
  if (err instanceof FiscalError && err.retryable) {
    // E101, E102, E500, E501, E502 — reia cu același Idempotency-Key.
    return await client.commands.send(body);
  }

  if (err instanceof EBonApiError && err.status === 429 && err.retryAfter) {
    await sleep(err.retryAfter * 1000);
    return await client.commands.send(body);
  }

  throw err;
}
isRetryable(code) este re-exportat din @e-bon/sdk dacă primești un cod printr-un canal exterior (un webhook, o coloană de bază de date) și ai nevoie de același răspuns fără să construiești un FiscalError.

Vezi și

  • Prezentare generală API — tabelele de sinteză pentru ambele familii de erori, plus limite de rată, idempotență și restricții pe planuri.
  • Erori SDK — formele claselor FiscalError și EBonApiError, re-exporturile și modelul recomandat de reîncercare în detaliu.
  • Depanare — diagnostic ghidat pe simptome pentru cele mai comune probleme de integrare.