invalid_request
HTTP 400, 404 or 409 type: validation_error | not_found | conflict{ "error": { "type": "validation_error", "code": "invalid_request", "message": "total must be a decimal with exactly 2 places, e.g. \"12.00\". Received \"12.5\".", "param": "total", "upstream": null, "doc_url": "https://docs.fiskhub.com/errors/invalid_request" }}What happened
The catch-all code, used when a request is wrong in a way that has no more specific name. The
type and param narrow it down, and message names the actual problem.
Nothing was fiscalized and nothing was stored.
Common causes on POST /v1/invoices:
| Cause | param |
|---|---|
total sent as a JSON number (4.5) instead of a string ("4.50") | total |
An amount with one decimal or three ("4.5", "4.500") | total |
occurred_at without an explicit UTC offset | occurred_at |
| A required field missing | that field |
A tax line missing rate, base or amount | vat.0.rate etc. |
currency other than EUR | currency |
special_purpose longer than 1000 characters | special_purpose |
Elsewhere in the API it also covers:
| Situation | HTTP |
|---|---|
| No such invoice, device or company | 404 |
| A duplicate OIB, device label or ISU number at one premises | 409 |
A premises_code that is not 1–20 letters and digits | 400 |
Attempting to change an immutable field — a company’s oib, a device’s company_id or premises_code | 400 |
Retrying a confirmed invoice | 409 |
| A malformed CSV or certificate upload | 400 |
Should you retry?
Not unchanged. The request is wrong and will keep being wrong. Fix what param and message
point at, then send again — with the same Idempotency-Key, since nothing was fiscalized.
The fix
Read param, then message. Between them they name the field and what is wrong with it.
The two that catch integrators most often:
Money is a string with exactly two decimals.
{ "total": 4.5 } // wrong — a JSON number{ "total": "4.5" } // wrong — one decimal{ "total": "4.50" } // rightBinary floating point cannot represent cents exactly, and the ZKI is a hash over the concatenated
string form — 4.5 and 4.50 produce different, and therefore wrong, protection codes.
Time carries an explicit offset.
{ "occurred_at": "2026-08-27 10:32:11" } // wrong — not ISO 8601{ "occurred_at": "2026-08-27T10:32:11" } // wrong — no offset{ "occurred_at": "2026-08-27T10:32:11+02:00" } // rightWithout an offset the sale moment is ambiguous, and the sale moment determines both the fiscal record and the 30-day deadline.
A note on generic 404s and 409s
Some not_found and conflict responses carry this code rather than a specific one, because the
condition is ordinary rather than diagnostic — an id that does not exist, a label already taken.
Read type to tell them apart, and message for the detail.