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

Trasare și jurnalizare a cererilor

Cum să urmărești un singur apel HTTP de la clientul tău până la e-bon — turul antetului X-Request-Id, forma plicului de eroare, rețete client gata făcute și o listă de raport de bug care transformă investigațiile de minute în secunde.

Trasare și jurnalizare a cererilor

Această pagină este pentru oricine apelează API-ul HTTP e-bon — parteneri POS, integrări de contabilitate, dashboard-uri terțe. Explică singurul antet de corelare de care ai nevoie (X-Request-Id), cum arată fiecare răspuns de eroare și cum să depui un raport de bug pe care îl putem rezolva rapid.

Pe scurt — fiecare răspuns poartă un antet X-Request-Id. Dacă trimiți unul, îl returnăm. Dacă nu, generăm un UUID v4. Citează acel ID în fiecare raport de bug și găsim apelul tău în jurnalele noastre în câteva secunde.

Trimite o cerere cu un ID de trasare

Antetul X-Request-Id călătorește cu fiecare cerere și fiecare răspuns.

DirecțieComportament
Cerere X-Request-Id (insensibil la majuscule)Opțional. Dacă trimiți o valoare nevidă, o folosim ca atare — fără validare, fără limită de lungime, fără filtrare de caractere.
Răspuns X-Request-IdMereu prezent. Returnăm valoarea ta dacă ai trimis una, altfel returnăm UUID-ul v4 generat pentru tine.

Câteva lucruri de fixat din start în modelul tău mental:

  • Antetul este mereu returnat. Nu există opt-out — chiar și răspunsurile de health-check îl poartă.
  • Antetele sunt insensibile la majuscule la nivel de protocol HTTP; tratează antetul de răspuns ca insensibil la majuscule când îl citești.
  • Nu validăm valoarea de intrare. Alege ceva rezonabil: rămâi la ASCII, sub ~256 de caractere și preferă UUID-uri sau ID-urile tale de corelare (de ex. o cheie primară din tabela ta de coadă). Nu pune date personale în antet — ele ajung în jurnalele noastre.
  • Dacă omiți antetul, generăm un UUID v4 proaspăt. Coliziunile nu sunt o îngrijorare.
Jurnalele noastre server-side poartă X-Request-Id-ul tău, prin care echipa de suport îți urmărește apelul înapoi la linia exactă din sistem.

Înțelege de ce contează acest antet

Trei motive concrete pentru care te interesează:

  1. Rapoarte de bug. Când ne spui „am primit un 500 de la POST /api/v1/receipts marți pe la 14:30 UTC”, putem găsi acea cerere — în principiu — dar durează minute per apel. Când ne spui X-Request-Id-ul, o găsim în câteva secunde. Vezi lista de raport de bug mai jos.
  2. Corelarea jurnalelor client și server. Jurnalizează X-Request-Id-ul din răspuns la fiecare rezultat non-2xx (și, ideal, la fiecare apel, eșantionat). Când un dashboard partener emite un eveniment Sentry cu ID-ul cererii atașat, inginerii voștri și ai noștri se uită la aceeași linie.
  3. Trasarea încercărilor repetate. Dacă reutilizezi același X-Request-Id la o încercare repetată, fiecare încercare apare în jurnalele noastre sub aceeași cheie de corelare. Aceasta este doar observabilitate — reutilizarea ID-ului nu declanșează nicio deduplicare. Dacă ai nevoie de încercări repetate sigure cu redare, asta este treaba lui Idempotency-Key, un antet separat (vezi tabelul de comparație și referința de idempotență).

Citește plicul de eroare

Fiecare răspuns de eroare care nu este de plafon de rată folosește aceeași formă JSON:

{
  "error": {
    "code": "COD_STRING",
    "message": "Explicație lizibilă pentru oameni",
    "details": "opțional, tipul variază în funcție de cod"
  }
}

Trei ramuri produc acel plic:

RamurăDeclanșatorStatusCorp
ValidareCorpul cererii sau query-ul nu a trecut validarea de schemă400code: VALIDATION_ERROR, message: 'Request validation failed', details: [{path, message}, …]
Eroare de domeniuA fost ridicată o eroare tipizată de aplicație (de ex. negăsit, conflict, interzis)mapat după codcode: err.code, message: err.message, details dacă este setat
NetratatOrice altceva (excepție necapturată)500code: INTERNAL_ERROR, message: 'An internal error occurred'

Redactarea din ramura netratată contează: în producție message-ul este întotdeauna 'An internal error occurred'. Mesajul original al excepției rămâne în jurnalele noastre server, indexat după X-Request-Id-ul tău. Acesta este exact scenariul în care ai nevoie de antet în raportul tău de bug.

Plicul de plafon de rată 429 NU folosește această formă. Corpul RATE_LIMIT_EXCEEDED este un obiect plat {code, message, status} la nivelul de top, neîncapsulat în {error: {…}}. Vezi Plafon de rată. Verifică pe code-ul de la nivelul de top pentru 429-uri.

Ignoră zgomotul endpoint-urilor de health-check

Cele patru endpoint-uri de health — /health, /ready, /healthz, /livez — nu apar în access log-ul nostru. Se execută în continuare, returnează în continuare X-Request-Id, doar nu produc o linie de jurnal (altfel sondele de liveness/readiness Kubernetes ar îneca jurnalul). Vezi Stare și meta pentru contractele lor.

Copiază o rețetă client

Trei clienți de referință scurți care arată turul antetului și șablonul de captură pentru raportul de bug. Niciunul nu este pregătit pentru producție (fără încercări repetate, fără metrici) — există ca să-ți dea un punct de plecare.

import { randomUUID } from 'node:crypto';

async function callWithTrace(url, options = {}) {
  const requestId = randomUUID();
  const headers = { ...(options.headers ?? {}), 'X-Request-Id': requestId };
  const res = await fetch(url, { ...options, headers });
  const echoed = res.headers.get('x-request-id');
  if (!res.ok) {
    const body = await res.json().catch(() => null);
    console.error('[ebon] cerere eșuată', {
      method: options.method ?? 'GET',
      url,
      status: res.status,
      sentRequestId: requestId,
      receivedRequestId: echoed,
      errorCode: body?.error?.code ?? body?.code,
    });
  }
  return res;
}

Depune un raport de bug util

Când ceva e stricat în producție, trimite-ne lista asta. Jumătate dintre puncte sunt evidente; ID-ul cererii este cel care transformă o investigație de 30 de minute într-una de 30 de secunde.

Metoda HTTP și URL-ul

POST /api/v1/receipts, GET /api/v1/devices/dev_abc/commands?status=pending. Include query string-ul; exclude orice secret din cale.

Codul de status și corpul răspunsului

error.code-ul exact, error.message și (dacă e prezent) error.details din plicul JSON. Pentru 429-uri, corpul plat {code, message, status}. Citează-l ca atare — nu parafraza.

X-Request-Id-ul din răspuns (obligatoriu)

Cel mai util singur câmp. Fără el căutăm după timestamp + metodă + status în jurnalele fiecărei replici. Cu el, un singur grep.

Timestamp aproximativ în UTC

Până la minut este suficient. Ajută atunci când ID-ul cererii lipsește sau când raportezi un val de eșecuri.

Corpul cererii redactat

Forma, nu secretele. Elimină Authorization, x-api-key, JWT-uri Bearer, datele personale ale clienților. Nu capturăm corpurile cererilor în access log-ul nostru, așa că nu îl putem reconstrui pe al tău.

Perspectiva clientului tău

Ce ai trimis (metodă/URL/antete, redactat), ce te așteptai să primești, ce ai primit. Dacă reîncerci cu același Idempotency-Key, spune asta — redarea din cache-ul Idempotency-Key arată identic cu primul apel la nivel de protocol.

Pentru un context mai larg de depanare, Ghidul de depanare acoperă moduri de eșec la nivel de integrare (probleme de autentificare, livrare de webhook-uri, împerechere de dispozitive) care apar adesea ca erori API derutante.

Idempotency-Key vs X-Request-Id

Cele două antete sunt independente și complementare. A le folosi pe amândouă la o încercare repetată este corect.

AspectIdempotency-KeyX-Request-Id
ScopSemantică de încercare repetată sigură — al doilea apel returnează răspunsul memorat al primuluiObservabilitate — corelează jurnalele client cu jurnalele server
Comportament serverPrima cerere rulează; cererile ulterioare cu aceeași cheie redau corpul memoratFiecare cerere rulează normal; valoarea este jurnalizată și returnată
Domeniu de aplicareDoar POST /api/v1/commands și POST /api/v1/receiptsFiecare cerere HTTP, fiecare endpoint
Implicit dacă e omisFără cache; cererea rulează ca un POST proaspătServerul generează un UUID v4
Reținere24h per {orgId}_{key} (vezi Idempotență)Niciodată persistat de API — trăiește doar în linia de jurnal a cererii
ValidareRegex ^[a-zA-Z0-9_-]+$, 1–128 caractere; 400 VALIDATION_ERROR la nepotrivireNiciuna — acceptat ca atare
Deduplică apeluri?Da — reluarea cu aceeași cheie returnează rezultatul memoratNu — doar pentru trasare
Când să-l reutilizeziLa reîncercarea unui POST non-idempotent pe care îl vrei sigur la reîncercareLa reîncercarea oricărui apel când vrei reîncercarea trasabilă spre ID-ul original

Rețeta combinată pentru reîncercarea unui POST de bon fiscal: păstrează același Idempotency-Key (ca al doilea apel să redea), păstrează același X-Request-Id (ca ambele încercări să se înlănțuie în jurnalele noastre) și lasă API-ul să facă restul.

Continuă explorarea

  • Prezentare API — ușa din față a suprafeței API.
  • Erori API — catalogul complet de code-uri și mapările lor de status HTTP.
  • Idempotență — contractul de încercare repetată sigură; se asociază cu X-Request-Id la fiecare reluare.
  • Plafon de rată — plicul 429 este PLAT, nu wrapper-ul documentat mai sus.
  • Stare și meta — cele patru căi tăcute (/health, /ready, /healthz, /livez).
  • Depanare — moduri comune de eșec la nivel de integrare.