e-bon
e-bon.ro
TypeScript SDK

Errors

The FiscalError and EBonApiError exception classes, the ErrorCode enum and RETRYABLE_ERRORS set re-exported from @e-bon/sdk, and a recommended retry pattern.

Errors

Every failure that surfaces out of @e-bon/sdk is an instance of one of two concrete classes — EBonApiError for HTTP-level failures, FiscalError for printer-side fiscal failures. There are no plain Error throws and no untyped unknown rejections from the SDK itself.

For the full catalogue of HTTP and FiscalError codes with per-code recovery steps, see /en/api/errors.

EBonApiError

EBonApiError wraps any non-2xx HTTP response from https://api.e-bon.ro. It is also thrown for transport-level failures — request timeouts (the SDK aborts via AbortController after timeout milliseconds, default 30000) and fetch errors — with status: 0 and a synthetic code of TIMEOUT or NETWORK_ERROR.

class EBonApiError extends Error {
  status: number              // HTTP status, or 0 for transport errors
  code: string                // matches the HTTP error catalogue (UNAUTHORIZED, VALIDATION_ERROR, …)
  message: string
  details: ApiErrorDetails[]  // populated on VALIDATION_ERROR and a few others
  retryAfter?: number         // seconds — populated on 429 from the Retry-After header
}

Branch on status and code rather than parsing the message:

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

try {
  await client.receipts.create(body);
} catch (err) {
  if (err instanceof EBonApiError) {
    if (err.code === 'VALIDATION_ERROR') {
      for (const detail of err.details) {
        console.warn(`${detail.field ?? '<root>'}: ${detail.message}`);
      }
      return;
    }
    if (err.status === 429 && err.retryAfter) {
      await sleep(err.retryAfter * 1000);
      return;
    }
  }
  throw err;
}

The full catalogue of HTTP-level code values lives on the API overview › HTTP error code catalogue.

FiscalError

FiscalError represents a failure that originated at the printer or the fiscal driver. The SDK constructs and attaches it to every command.failed event payload, and resource methods that resolve a fiscal command synchronously (for example a wrapper that polls until completion) throw it directly.

The class has exactly four typed fields beyond the inherited message:

class FiscalError extends Error {
  code: ErrorCode             // one of the fiscal codes E100…E901
  message: string
  retryable: boolean          // precomputed via isRetryable(code)
  commandId?: string          // the command that failed, when known
  deviceId?: string           // the device that emitted the failure, when known
}

The constructor accepts { code, message, commandId?, deviceId? }; retryable is derived inside the constructor from isRetryable(code), so you never need to compute it yourself.

The full ErrorCode enum and the per-code retryable mapping are documented on the API overview › Fiscal / device error codes.

Import the error helpers from @e-bon/sdk

@e-bon/sdk re-exports the three pieces you need to work with fiscal errors without pulling @e-bon/types in directly:

import { ErrorCode, RETRYABLE_ERRORS, isRetryable } from '@e-bon/sdk';
  • ErrorCode — the enum of fiscal error codes (E100E901). Use it for switch statements and for typed comparisons against FiscalError.code.
  • RETRYABLE_ERRORS — the canonical set of ErrorCode values that are safe to retry. Mirrors the retryable column in the API overview table.
  • isRetryable(code) — the helper that backs FiscalError.retryable. Useful when you receive a code through an out-of-band channel (a webhook, a database row) and want the same answer.

Retry transient failures safely

Combine FiscalError.retryable with the Idempotency-Key header on the underlying HTTP request to retry transient fiscal failures without risking a double print:

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

async function sendWithRetry(
  client: EBonClient,
  body: SendCommandBody,
  idempotencyKey: string,
  maxAttempts = 3,
) {
  let attempt = 0;
  let delay = 1_000;

  while (true) {
    attempt += 1;
    try {
      return await client.commands.send(body);
    } catch (err) {
      // Fiscal failure on the printer.
      if (err instanceof FiscalError && err.retryable && attempt < maxAttempts) {
        await sleep(delay);
        delay *= 2;
        continue;
      }

      // HTTP-level rate limit. Honour Retry-After.
      if (err instanceof EBonApiError && err.status === 429 && err.retryAfter) {
        await sleep(err.retryAfter * 1000);
        continue;
      }

      throw err;
    }
  }
}

Passing the same idempotencyKey on retries is what makes this safe — the server returns the cached response from the original attempt instead of executing the command twice. See API overview › Idempotency for the full rules.

Only retry when err.retryable is true. Codes such as FiscalMemoryFull (E300), FiscalDailyLimitReached (E303) and FiscalHardwareError (E304) need an operator on-site, not another network round-trip.

Where to next