e-bon
e-bon.ro
API reference

Request tracing & logging

How to trace a single HTTP call from your client to e-bon — the X-Request-Id round-trip, the error envelope shape, ready-made client recipes, and a bug-report checklist that turns minute-long investigations into seconds.

Request tracing & logging

This page is for everyone who calls the e-bon HTTP API — POS partners, accounting integrations, third-party dashboards. It explains the one correlation header you need (X-Request-Id), what every error response looks like, and how to file a bug report we can act on quickly.

TL;DR — every response carries an X-Request-Id header. If you send one, we echo it back. If you don't, we mint a UUID v4. Quote that ID in every bug report and we'll find your call in our logs in seconds.

Send a request with a trace ID

The X-Request-Id header travels with every request and every response.

DirectionBehaviour
Request X-Request-Id (case-insensitive)Optional. If you send a non-empty value, we use it verbatim — no validation, no length cap, no character filtering.
Response X-Request-IdAlways present. We echo your value back if you sent one, otherwise we return the UUID v4 we generated for you.

A few things to fix in your mental model up front:

  • The header is always echoed. There is no opt-out — even health-check responses carry it.
  • Headers are case-insensitive on the wire; treat the response header as case-insensitive when reading it.
  • We do not validate the inbound value. Pick something sensible: stick to ASCII, keep it under ~256 chars, and prefer UUIDs or your own correlation IDs (e.g. a database PK from your queue table). Do not put PII in the header — it ends up in our logs.
  • If you omit the header, we mint a fresh UUID v4. Collisions are not a concern.
Our server-side logs carry your X-Request-Id, which is how support traces your call back to the exact line in our system.

Understand why this header matters

Three concrete reasons to care:

  1. Bug reports. When you tell us "we got a 500 from POST /api/v1/receipts last Tuesday around 14:30 UTC", we can find that request — in principle — but it takes minutes per call. When you tell us the X-Request-Id, we find it in seconds. See the bug-report checklist below.
  2. Correlating client and server logs. Log the X-Request-Id from the response on every non-2xx outcome (and ideally on every call, sampled). When a partner dashboard emits a Sentry event with the request ID attached, your engineers and ours are looking at the same line.
  3. Tracing retries. If you re-use the same X-Request-Id on a retry, every attempt shows up in our logs against the same correlation key. This is observability only — re-using the ID does not trigger any deduplication. If you need safe retries with replay, that is the job of Idempotency-Key, a separate header (see the comparison table and the Idempotency reference).

Read the error envelope

Every non-rate-limit error response uses the same JSON shape:

{
  "error": {
    "code": "STRING_CODE",
    "message": "Human-readable explanation",
    "details": "optional, type varies by code"
  }
}

Three branches produce that envelope:

BranchTriggerStatusBody
ValidationYour request body or query failed schema validation400code: VALIDATION_ERROR, message: 'Request validation failed', details: [{path, message}, …]
Domain errorA typed application error was raised (e.g. not-found, conflict, forbidden)mapped per codecode: err.code, message: err.message, details if set
UnhandledAnything else (uncaught exception)500code: INTERNAL_ERROR, message: 'An internal error occurred'

The redaction on the unhandled branch matters: in production the message is always 'An internal error occurred'. The original exception message stays in our server logs, keyed by your X-Request-Id. That is exactly the scenario where you need the header in your bug report.

The 429 rate-limit envelope does NOT use this shape. The RATE_LIMIT_EXCEEDED body is a flat {code, message, status} object at the top level, not wrapped in {error: {…}}. See Rate limits. Match on the top-level code for 429s.

Skip health-check noise in your logs

The four health endpoints — /health, /ready, /healthz, /livez — do not appear in our access log. They still execute, they still return X-Request-Id, they just don't produce a log line (otherwise Kubernetes liveness/readiness probes would drown the log). See Health & meta for their contracts.

Copy a client recipe

Three short reference clients showing the header round-trip and the bug-report capture pattern. None are production-ready (no retries, no metrics) — they exist to get you started.

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] request failed', {
      method: options.method ?? 'GET',
      url,
      status: res.status,
      sentRequestId: requestId,
      receivedRequestId: echoed,
      errorCode: body?.error?.code ?? body?.code,
    });
  }
  return res;
}

File a useful bug report

When something is broken in production, send us this checklist. Half the items are obvious; the request ID is the one that turns a 30-minute investigation into a 30-second one.

HTTP method and URL

POST /api/v1/receipts, GET /api/v1/devices/dev_abc/commands?status=pending. Include the query string; exclude any secrets in the path.

Status code and response body

The exact error.code, error.message and (if present) error.details from the JSON envelope. For 429s, the flat {code, message, status} body. Quote it verbatim — do not paraphrase.

The X-Request-Id from the response (required)

The single most useful field. Without it we are searching by timestamp + method + status across every replica's logs. With it, one grep.

Approximate timestamp in UTC

To the minute is enough. Helps when the request ID is missing or when you are reporting a burst of failures.

Redacted request body

The shape, not the secrets. Strip Authorization, x-api-key, JWT bearers, customer PII. We do not capture request bodies in our access log, so we cannot reconstruct yours.

Your client's view

What you sent (method/URL/headers, redacted), what you expected, what you got. If you are retrying with the same Idempotency-Key, say so — Idempotency-Key cache replays look identical to the first call on the wire.

For broader debugging context, the Troubleshooting guide covers integration-level failure modes (auth issues, webhook delivery, device pairing) that often manifest as confusing API errors.

Idempotency-Key vs X-Request-Id

The two headers are independent and complementary. Using both on a retry is correct.

AspectIdempotency-KeyX-Request-Id
PurposeSafe-retry semantics — the second call returns the cached response of the firstObservability — correlate client logs with server logs
Server behaviourFirst request runs; subsequent identical-key requests replay the cached bodyEvery request runs normally; the value is logged and echoed
ScopePOST /api/v1/commands and POST /api/v1/receipts onlyEvery HTTP request, every endpoint
Default if omittedNo caching; the request runs as a fresh POSTServer mints a UUID v4
Retention24h per {orgId}_{key} (see Idempotency)Never persisted by the API — lives only in the request log line
ValidationRegex ^[a-zA-Z0-9_-]+$, 1–128 chars; 400 VALIDATION_ERROR on mismatchNone — accepted verbatim
Deduplicates calls?Yes — re-running with the same key returns the cached resultNo — for tracing only
When to re-useOn retry of a non-idempotent POST you want to be safe to retryOn retry of any call when you want the retry traceable to the original ID

The combined recipe for a retry of a fiscal receipt POST: keep the same Idempotency-Key (so the second call replays), keep the same X-Request-Id (so both attempts thread together in our logs), and let the API do the rest.

Continue exploring

  • API overview — the front door to the API surface.
  • API errors — the full code catalogue and their HTTP status mappings.
  • Idempotency — the safe-retry contract; pairs with X-Request-Id on every retry.
  • Rate limits — the 429 envelope is FLAT, not the wrapper documented above.
  • Health & meta — the four silent paths (/health, /ready, /healthz, /livez).
  • Troubleshooting — common integration failure modes.