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 groups

Item-level vatGroup must be one of the device's VAT groups:

GroupUse
VAT1..VAT16VAT groups programmed on the device
exemptExempt items

The actual percent for each group is not validated against a fixed national list — it is resolved from the printer's own rate table, which e-bon reads live from the device (get_vat_rates / get_vat_capabilities). Rates are never hardcoded.

Any other value is rejected with:

vatGroup must be one of: VAT1..VAT16, exempt

Payment types

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

ValueMeaning
cashCash payment
cardCard payment
checkCheck payment
creditCredit / store account
couponsCoupons
meal-vouchersMeal vouchers (tichete de masă)
value-ticketsValue tickets (bilete valorice)
voucherVoucher (gift voucher etc.)
modern-paymentModern payment (mobile / wallet)
card-advanceCard advance
foreign-currencyForeign currency
bankBank transfer
ext-couponsExternal coupons
packagingPackaging
internal-usageInternal usage
damageDamage / loss
reserved1Reserved 1
reserved2Reserved 2
otherAny other accepted tender

Rejection message:

type must be one of: cash, card, check, credit, coupons, meal-vouchers, value-tickets, voucher, modern-payment, card-advance, foreign-currency, bank, ext-coupons, packaging, internal-usage, damage, reserved1, reserved2, 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].vatGroupRequired, must be a device VAT group (VAT1..VAT16 or exempt).
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].groupRequired, non-empty string — a device VAT group (VAT1..VAT16).
rates[i].rateRequired, non-negative number (>= 0).
rates[i].enabledRequired boolean.
This command does not restrict rates to a fixed national set — it pushes an arbitrary rate set to the device. The device firmware enforces what it will accept. The rate table itself is always read live from the printer (get_vat_rates / get_vat_capabilities); rates are never hardcoded.
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.
oldPasswordOptional; if provided, must be a string. Required by devices reporting passwordChangeRequiresOldPassword.
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].vatGroup", "message": "vatGroup must be one of: VAT1..VAT16, exempt" },
      { "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