e-bon
e-bon.ro
Architecture

Command payload validation

Validation rules e-bon applies to every command request before it reaches your fiscal device — VAT rates, payment types, reversal reasons, items-vs-payments balance, and the error response you get when a payload is rejected.

Every command you send to the e-bon API is validated before it reaches the device. If the payload fails validation, the request is rejected with HTTP 400 and a structured list of field-level errors — no command is queued, no receipt is printed.

This page documents the rules so you can build payloads that pass on the first try.

Validate against Romanian fiscal constants

Three fixed value sets are enforced on receipt-related commands.

VAT rates

Item-level vatRate must be one of the rates currently valid in Romania:

RateUse
0Exempt items
9Reduced rate (food, books, medicines)
11Reduced rate (HoReCa, accommodation)
21Standard rate

Any other value is rejected with:

vatRate must be one of: 0, 9, 11, 21

Payment types

Each entry in payments[] must declare a type from this set:

ValueMeaning
cashCash payment
cardCard payment
voucherMeal voucher, gift voucher, etc.
otherAny other accepted tender

Rejection message:

type must be one of: cash, card, voucher, other

Reversal reasons

When issuing a reversal receipt, reason must be one of:

ValueWhen to use
operator_errorCashier mistake on the original receipt
refundCustomer returned goods or cancelled service
tax_base_reductionTax base correction (e.g. subsequent discount)

Rejection message:

reason must be one of: operator_error, refund, tax_base_reduction

Balance items against payments

For receipts that carry both items and payments, the totals must match:

sum(items[i].quantity * items[i].price)  ==  sum(payments[i].amount)

Both sides are rounded to 2 decimals. A drift of up to ±0.01 RON is tolerated to absorb floating-point rounding — it is not a discount allowance.

If the totals don't match, the request is rejected with a message like:

Payment total (99.99) does not match items total (100.00)
This rule applies to every print_receipt request. It also applies to print_reversal_receiptwhen you include payments — see the asymmetry note below.

Apply per-command-type rules

The following commands carry payloads that are validated field-by-field. Commands not listed here either take no payload or only require their shape to match the request body schema.

The most demanding payload. Combines item validation, payment validation, and the items-vs-payments balance check.

FieldRule
itemsRequired array, non-empty.
items[i].nameRequired, non-empty string.
items[i].quantityRequired, positive number (> 0).
items[i].priceRequired number.
items[i].vatRateRequired, must be a Romanian VAT rate.
paymentsRequired array, non-empty.
payments[i].typeRequired, must be a valid payment type.
payments[i].amountRequired, positive number (> 0).
Items vs paymentsTotals must match within ±0.01 RON.

void_receipt

FieldRule
receiptIdRequired, non-empty string.

cash_in / cash_out

Both commands share the same validator.

FieldRule
amountRequired, positive number (> 0).
descriptionOptional; if provided, must be a string.

set_datetime

FieldRule
datetimeOptional; if provided, must be an ISO-8601 string.

non_fiscal_receipt

FieldRule
linesRequired array with at least one entry; every element must be a string.
headerOptional; if provided, must be a string.
FieldRule
logoRequired, non-empty string.

set_vat_rates

FieldRule
ratesRequired array with at least one entry.
rates[i].nameRequired, non-empty string.
rates[i].percentageRequired, non-negative number (>= 0).
This command does not restrict percentages to the Romanian VAT set — it pushes an arbitrary rate set to the device. The device firmware enforces what it will accept.
FieldRule
headerRequired array of strings.
footerRequired array of strings.

Empty arrays are accepted.

set_operator

FieldRule
operatorIdRequired positive integer (>= 1).
nameRequired, non-empty string.
passwordOptional; if provided, must be a string.
FieldRule
uniqueSaleNumberRequired, non-empty string.
originalReceiptNumberRequired, non-empty string.
originalReceiptDateTimeRequired, must be a date string parsable as ISO-8601.
fiscalMemorySerialNumberRequired, non-empty string.
originalZReportNumberOptional; if provided, must be a string.
reasonRequired, must be a valid reversal reason.
itemsRequired array, non-empty (same per-item rules as print_receipt).
paymentsOptional. If provided, every entry follows the same rules as print_receipt, and the items-vs-payments balance check applies.
Items are required on both. Payments differ.
  • On print_receipt, payments is required.
  • On print_reversal_receipt, payments is optional — some reversals (for example, correcting a misprint that never collected money) carry no payment leg at all.
The items-vs-payments balance rule applies to both, but on a reversal it only runs when payments is present and non-empty.

Handle the validation error response

When a payload fails validation, the API returns HTTP 400 with this body:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid command payload",
    "details": [
      { "field": "items[0].vatRate", "message": "vatRate must be one of: 0, 9, 11, 21" },
      { "field": "payments", "message": "Payment total (99.99) does not match items total (100.00)" }
    ]
  }
}

details lists every field that failed, in the order they were checked. To localise the error in your own UI, map on field + code rather than the message text — message strings are English-only and may evolve.

The dedicated reversal endpoint uses the same shape, but its top-level message is "Invalid reversal receipt payload" instead of "Invalid command payload".

Where to next