e-bon
e-bon.ro
API reference

API overview

How the e-bon REST API is shaped — base URL, versioning, error envelope, rate limits, idempotency and tier gating — for POS partners and integrators.

API overview

The e-bon API is a JSON-over-HTTPS REST API for POS systems and back-office integrations that need to dispatch fiscal receipts, manage AMEF devices and read fiscal reports without implementing each printer's native protocol themselves. It is the same API the e-bon Portal and the E-BON mobile app use internally.

Architecture in one breath

The API is command-queue oriented. A POS issues a fiscal command (for example print this receipt) over REST; the server queues it and dispatches it over a WebSocket to the E-BON mobile app sitting next to the printer; the app translates the command into the printer's native protocol; the result flows back through the same chain and is then available either by polling the command or by listening on the events WebSocket.

POS  ──HTTPS──▶  api.e-bon.ro  ──WSS──▶  E-BON mobile app  ──TCP/BT/USB──▶  AMEF printer
                                                                                  │
POS  ◀─────── poll / events WSS ───────  e-bon server  ◀────── result ─────────────┘

The practical consequence: most fiscal operations are asynchronous. POST /api/v1/commands returns immediately with status: "pending" and you either poll GET /api/v1/commands/{id} or subscribe to command.completed / command.failed events.

Base URL

EnvironmentBase URL
Productionhttps://api.e-bon.ro
Staginghttps://api-staging.e-bon.ro

All examples in the docs use the production base URL.

Versioning

The API is currently on v1. Every routed endpoint is prefixed with /api/v1/:

GET /api/v1/devices HTTP/1.1
Host: api.e-bon.ro
Authorization: Bearer ebon_live_<orgId>_<32-hex>
Accept: application/json

There is no minor-version negotiation — the version is in the path. Breaking changes ship under a new prefix (/api/v2/...); additive, backwards-compatible changes ship in place under /api/v1/.

Content type

  • Request bodies: application/json (UTF-8). Send Content-Type: application/json on every POST / PATCH / PUT.
  • Responses: application/json (UTF-8) for everything except the JE XML download endpoint, which returns application/xml as an attachment.
  • All timestamps in request / response bodies are ISO 8601 strings (for example "2026-04-09T08:10:00.000Z").

Error envelope

Every error response — whether from validation, auth, business logic or an unhandled crash — follows the same shape:

{
  "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 — a stable string identifier from the catalogue below. Always present.
  • error.message — a human-readable message in English. Suitable for logging; do not parse.
  • error.details — optional. Present on validation failures (Zod field errors) and on a handful of other errors that need structured payloads. Shape depends on the error code.
The 429 RATE_LIMIT_EXCEEDED response from the global rate limiter is currently emitted as a flat object ({ "code": "RATE_LIMIT_EXCEEDED", "message": "...", "status": 429 }) instead of the wrapped { "error": { ... } } envelope. Treat both shapes as equivalent until the API is normalised; match on the code field, not on the wrapper.

HTTP error code catalogue

Looking for per-code recovery steps? See the dedicated errors reference at /en/api/errors.

These are the codes returned in error.code by the e-bon HTTP API. They are the wire-level codes — distinct from the device / fiscal-driver ErrorCode enum (E100E901) returned inside command results and SDK exceptions, documented in the next section.

CodeHTTPWhen you see it
UNAUTHORIZED401Missing or invalid API key / JWT, or the key is inactive.
TOKEN_EXPIRED401JWT access token expired — refresh it.
FORBIDDEN403Authenticated, but the key's scopes do not cover this endpoint.
VALIDATION_ERROR400Body / query failed Zod validation. details lists { path, message } per field.
BAD_REQUEST400Malformed request that is not specifically a schema violation.
NOT_FOUND404Addressed resource (device, command, receipt, report, key) does not exist for your organization.
CONFLICT409Operation conflicts with current state (for example cancelling a command that is already completed).
UNPROCESSABLE_ENTITY422Request was understood but cannot be processed in the current business state.
RATE_LIMIT_EXCEEDED429You exceeded the rate limit window. See Rate limits.
TIER_LIMIT_EXCEEDED403Your organization's plan does not allow this action (for example creating an API key on the Free plan).
INTERNAL_ERROR500Unhandled server error. Includes a request ID in logs; safe to retry idempotent operations after a backoff.
SERVICE_UNAVAILABLE503Server temporarily refusing requests (deploys, dependency outages). Retry with exponential backoff.

Fiscal / device error codes (FiscalError)

Looking for per-code recovery steps? See the dedicated errors reference at /en/api/errors.

When a fiscal command fails on the printer, the failure surfaces in the command's error field on GET /api/v1/commands/{id} and — if you use the official @e-bon/sdk — as a FiscalError exception with this shape:

class FiscalError extends Error {
  code: ErrorCode       // one of the codes below
  message: string
  retryable: boolean    // true if it is safe to retry the same command
  commandId?: string
  deviceId?: string
}

The full ErrorCode enum from @e-bon/types, with the retryable flag derived from RETRYABLE_ERROR_CODES:

CodeNameRetryableMeaning
E100ConnectionRefusednoPrinter refused the TCP / Bluetooth / USB / serial connection.
E101ConnectionTimeoutyesConnection attempt timed out before the printer answered.
E102ConnectionLostyesExisting connection dropped mid-operation.
E103ConnectionNotFoundnoThe configured transport endpoint cannot be reached at all.
E200ProtocolMismatchnoThe printer answered, but its protocol does not match the configured one.
E201ProtocolUnsupportednoCommand not supported by this printer's protocol.
E202ProtocolChecksumErrornoFrame received but failed checksum validation.
E203ProtocolFramingErrornoMalformed frame at the protocol layer.
E300FiscalMemoryFullnoThe printer's fiscal memory (Memorie Fiscală) is full — service required.
E301FiscalReceiptOpennoA receipt is already open on the printer; close it before retrying.
E302FiscalNoReceiptOpennoThe command requires an open receipt but none is open.
E303FiscalDailyLimitReachednoThe printer reached its per-day fiscal limit; close the day with a Z report.
E304FiscalHardwareErrornoPrinter hardware error (paper, head, cover, drawer, etc.).
E400ValidationInvalidPayloadnoCommand payload rejected by the driver.
E401ValidationMissingFieldnoA required payload field is missing.
E402ValidationInvalidAmountnoAn amount field is outside the legal range.
E403ValidationInvalidVatRatenoVAT rate is not one of the four legal Romanian rates (0, 9, 11, 21).
E500TimeoutCommandyesCommand did not complete within the per-command timeout.
E501TimeoutResponseyesPrinter started the command but did not return a response in time.
E502TimeoutConnectionyesConnection-level timeout while talking to the printer.
E900UnknownnoUnclassified driver error.
E901InternalErrornoUnexpected internal error in the driver layer.
E902UnexpectedResponsenoPrinter returned a response the driver could not interpret.

When retryable is true, the same command can be safely re-sent (use the same Idempotency-Key to avoid duplicate prints if your business logic requires it).

Rate limits

Rate limiting is applied at the edge by express-rate-limit and is keyed primarily by the first 20 characters of the API key (so all requests from one POS integration share one bucket); when no API key is present the client IP address is used instead, with proper IPv6 subnet masking.

Defaults (configurable per environment via the RATE_LIMIT_* and AUTH_RATE_LIMIT_* env vars on the server):

LimiterDefault maxWindowKeyApplies to
Global15010 minAPI key prefix (20 chars) or IPEvery request to the API
Auth endpoints3010 minClient IP/api/v1/auth/* (register, login, refresh)
Command submission5010 minAPI key prefix (20 chars) or IPPOST /api/v1/commands and equivalents

Every response carries the standard IETF draft-7 rate-limit headers:

HeaderMeaning
RateLimit-LimitMaximum requests allowed in the current window.
RateLimit-RemainingRequests remaining in the current window.
RateLimit-ResetSeconds until the window resets.

When you trip a limit, the response is 429 Too Many Requests and includes a Retry-After header (integer seconds) telling you exactly how long to wait before the next attempt:

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
}

Implement exponential backoff and respect Retry-After; do not hammer the API after a 429.

Idempotency

For the full contract — caveats around request-body fingerprinting, concurrent requests with the same key, cached error responses and key-generation patterns — see the dedicated Idempotency reference.

POST /api/v1/commands and POST /api/v1/receipts accept the Idempotency-Key header. Use it on every retryable mutation so a network blip does not result in a double-printed receipt.

  • Allowed format: 1 to 128 characters, [a-zA-Z0-9_-] only.
  • Scope: per organization. Internally the cache key is {orgId}_{idempotencyKey}.
  • TTL: 24 hours. Replays within the window return the original cached response (same status, same body) without re-running the operation.
  • Validation failure: an out-of-format key returns 400 VALIDATION_ERROR.
POST /api/v1/commands HTTP/1.1
Host: api.e-bon.ro
Authorization: Bearer ebon_live_<orgId>_<32-hex>
Content-Type: application/json
Idempotency-Key: order-12345-attempt-1

{ "deviceId": "dev_abc123", "type": "print_receipt", "payload": { "...": "..." } }

A common pattern is {logical-operation-id}-attempt-{n} so explicit retries get their own cache entry while transparent retries (timeouts, network errors) reuse the original.

Pagination

List endpoints use cursor-based pagination with stable ordering on createdAt (newest first by default). The shape is:

{
  "receipts": [ /* up to `limit` items */ ],
  "hasMore": true,
  "lastId": "rec_001"
}

Pass lastId back as startAfter to fetch the next page:

GET /api/v1/receipts?startAfter=rec_001&limit=50 HTTP/1.1

Common parameters across list endpoints:

ParameterTypeDefaultMeaning
limitint50Page size, 1100.
startAfterstringCursor (the previous page's lastId).
sortBystringcreatedAtSort field (varies by endpoint; receipts also accept total).
sortOrderstringdescasc or desc.

Some endpoints also accept resource-specific filters (status, type, deviceId, from, to, etc.) — see the per-resource pages.

Plan / tier enforcement

Some operations are gated by the organization's subscription plan. The server enforces these limits at the middleware layer and returns 403 TIER_LIMIT_EXCEEDED when they are crossed:

PlanMax devicesAPI key creation
free2not allowed
pro10allowed
enterpriseunlimitedallowed

The Free plan blocks POST /api/v1/devices once you reach 2 devices, and blocks creating API keys entirely. Upgrade in the Portal billing page to lift the limits.

Authentication, briefly

The API supports two auth modes:

  • API key — for POS systems and back-office integrations. Send x-api-key: ebon_live_... or Authorization: Bearer ebon_live_....
  • JWT — for the e-bon Portal and the mobile app. Standard register / login / refresh flow under /api/v1/auth/*.

Most resource endpoints (/devices, /commands, /receipts, /reports) accept either mode through a combined-auth middleware, which is why the same endpoints power both POS integrations and the Portal UI. Full details live on the Authentication page.

Webhooks

In addition to the polling and events-WebSocket models, e-bon delivers webhook callbacks for receipt, command, device and report events. The full event catalogue, payload schemas and HMAC signature verification are documented on the Webhooks reference page; configure subscriptions in the Portal under Settings → Webhooks.

Where to next

  • Authentication — API key format, scopes, generating a key, auth errors and curl examples.
  • Authentication endpoints — wire-level reference for the five /api/v1/auth/* routes (register, login, forgot-password, refresh, logout).
  • Receipts, Reports and Devices — endpoint references for the core fiscal surfaces.
  • Webhooks, Billing and App instances — outbound events, subscription management and controller-app status.
  • Commands — queue fiscal commands against an AMEF, poll for completion and cancel pending ones.
  • API keys — programmatic management of API keys (list, create, update, revoke) for Owners and Admins.
  • Errors reference — per-code recovery steps for every HTTP and FiscalError code.
  • Idempotency reference — the Idempotency-Key header in depth: TTL, replay matrix, caveats and key-generation patterns.
  • Postman collection — import the official collection (77+ requests across 12 folders) and explore the API interactively.
  • Health, identity & meta endpoints — liveness/readiness probes, identity introspection, and the public OpenAPI spec.