Skip to content

Versioning

The version is in the path and nowhere else. There is no version header, no date-pinning, and no account-level version setting.

https://api.fiskhub.com/v1

The promise

/v1 never breaks. Once you are coding against it, changes to /v1 are additive only:

  • new optional request fields, never new required ones;
  • new response fields, never removed or renamed ones;
  • new error code values, never a renamed or repurposed one;
  • new enum members in fields documented as open — such as error.code;
  • new endpoints.

Anything that would break a working integration — a removed field, a renamed field, a narrowed type, a changed default, a status code that means something different — ships as /v2, at a different path, while /v1 keeps working.

This is a commitment about the contract, not about the implementation behind it. FiskHub will change constantly; what /v1 says will not.

Writing a client that survives additive change

Three habits, and additive changes cost you nothing.

Ignore fields you do not recognise. A response gaining a field must not break parsing. Strict schema validation that rejects unknown properties turns every future improvement into an outage.

// Fine: pick what you need, ignore the rest.
const { id, status, jir, zki, qr_payload, number } = await res.json();

Treat an unknown error.code as its type. New codes are added when a new failure mode is worth naming. Fall back to the broader category rather than crashing.

function classify(error) {
switch (error.code) {
case "hr.device_missing_isu":
return alertOperator(error);
case "upstream_unavailable":
return keepSelling();
default:
// Unknown code: fall back to the type, which is a closed set.
return error.type === "validation_error"
? alertOperator(error)
: retryLater(error);
}
}

Branch on status, never on the shape of the response. jir: null is not a missing field and not an error — see handling the no-JIR response.

What will not change under you

These are frozen for the life of /v1:

FrozenWhy it matters
The five invoice statusespending, confirmed, retrying, failed, expired. A sixth would break exhaustive handling.
Every error code in the taxonomyCodes are never renamed or repurposed. Your switch keeps working.
The error body shapetype, code, message, param, upstream, doc_url.
POST /v1/invoices request and response fieldsFrozen the moment the client started building.
Money as a decimal stringIt will never become a JSON number.
Payment methods G, K, O for unattended sales
HTTP 200 for a sale with no JIRThe behaviour this whole contract is built around.

Two things are explicitly not part of the contract, and you must not depend on them:

  • error.message — written for humans and reworded freely. Branch on code.
  • error.upstream — raw detail from the Tax Administration, passed through for debugging. Their codes are theirs and change on their schedule.

The regulation changes; the contract absorbs it

The Croatian fiscalization specification changed four times between June 2025 and July 2026, and will change again. Absorbing that is FiskHub’s job.

When the specification adds a field or a rule, it shows up here as an additive change: a new optional request field, a new error code for a new restrictive rule, a new value in country_data. Your integration keeps working; you adopt the new capability when you need it.

Where a change is genuinely visible — a new mandatory field imposed by the regulation, a deadline like PROD dropping RSA-SHA1 on 31 December 2026 — you get notice before it lands, not after.

Deprecation

If /v2 is ever needed:

  1. /v2 is published with a migration guide naming every difference.
  2. /v1 keeps working. It does not enter maintenance mode on the day /v2 ships.
  3. Any eventual sunset comes with at least 12 months of notice, in writing, and never during a peak trading period.
  4. Endpoints do not disappear from /v1 in the meantime.

No part of /v1 is deprecated today.

Things that will never be added to /v1

Not because they are hard, but because the design removed the need for them:

  • Webhooks. Fiscalization is synchronous — the answer is in the response. Poll GET /v1/invoices/{id} if you want to know that a retrying invoice was later confirmed.
  • A batch endpoint. Each sale is a real-time call because the device needs the result before it can print.
  • eRačun (B2B invoicing) and eIzvještavanje. Different regulations, different scope, not this product.