Changelog
The changes in this version of the API, with the date they became available on the demo environment and the date they were deployed to production.
| Change | Demo | Production |
|---|---|---|
| The issuer's details are no longer your job: whatever the document does not carry, we fill in from your organization. The tax number, the VAT number and the address of the seller (street, city, postal code) are taken from the master data of the organization this key belongs to, before the invoice leaves for the tax authority. So cac:AccountingSupplierParty may carry nothing but a name — as in the working example — and the invoice now reaches the tax authority. ⛔ Until now such a document was accepted with 201 and then stopped: first at 250 with EARH-4004 and failedStep ConvertFormat, and once that was cleared, at 250 with EARH-4001 and SIX findings, all about the seller (sellerTin, street, postal code, city, the seller group, and sellerVatNumber). The published specification said all along that the issuer is optional, which held only for acceptance. ⚠️ We fill in only what is EMPTY: a value the document carries is not overwritten. But the issuer's master data is settled by the tax authority's registry — the name and address are aligned with the registered ones before submission, and that was already the case. If you do write an issuer tax number, it must be your organization's; another one is still refused with EARH-2007 in the same call. ⛔ With this, EARH-4004 stops being the outcome of missing issuer details: that code is left for a body that cannot be read (for example with no cac:InvoiceLine at all). No existing field, status or code changes meaning. | 13.09.2026 | the date has not been decided yet |
| ⛔ CHANGE OF BEHAVIOUR DURING PROCESSING: the recipient no longer has to be a registered partner beforehand. An unknown recipient is REGISTERED from the invoice itself — from the name, tax number and address it carries — and the processing goes on to UJP. The same holds in the other direction: the sender of an inbound invoice is registered on arrival, which is also the real order of things — an inbound invoice arrives without your software having any code for that company. Until now a document for an unregistered recipient ended at 250 with EARH-4005, which was the usual outcome of a FIRST attempt for every new integrator. From now on EARH-4005 (and EARH-5020 in validationErrors) is left only for the case where registering is not possible: a malformed tax number that acceptance let through, or a partner that was deleted in the organization — a deleted record is not brought back to life automatically. NEW ENDPOINT GET partners: the organization's partners, with edb, name, address, city, postalCode, country, email, origin and changedAt per row. origin says where the record came from — OutboundInvoice, InboundInvoice, Manual, Erp, or Unknown for an older record. changedSince (yyyy-MM-dd or a full timestamp) returns only what changed, and nextFrom in the answer is the starting point for the next call, just like on the document lists. Paging is page and pageSize, at most 500 — more is refused with EARH-3005. ⛔ The date is read strictly as ISO: 10.09.2026 is refused with EARH-2020, not interpreted. Code lists gain erpCodes per entry — the codes from YOUR software that the organization has mapped to that UJP code, an empty list when there is none. That is how you check in advance whether one of your values will pass, most importantly for the document type: the 380 in the UBL is YOUR code, not a UJP code. New as well: cbc:InvoiceTypeCode (on a CreditNote: cbc:CreditNoteTypeCode) and both cac:Country/cbc:IdentificationCode are finally read from the UBL as your own ERP codes and go through the organization's mapping — until now the UBL path did not fill the document type at all, so for an organization in Block mode no document from the integration API could be submitted. No existing field, status or code changes meaning. | 13.09.2026 | the date has not been decided yet |
| The two places where the answer stayed silent now say why. NEW FIELD failedStep on GET outbound/{docUid}: the name of the step that failed — ConvertFormat, ResolvePartner, UjpEFaktura and so on — filled only on a document that failed. ⛔ This is what tells apart the two meanings of EARH-4004: a failure in ConvertFormat means the document cannot be read as it was sent, so the cause is in your body, while a failure in a later step is usually on our side. Until now the catalogue claimed the cause was ALWAYS ours and sent you to support for your own error. Read the field, but do not branch a state machine on it: the list of steps may gain new members. NEW CODE EARH-5020 inside validationErrors: the recipient is not registered as a partner in the organization. That is the only reason behind EARH-4005, and until now validationErrors was EMPTY on that outcome, so the reason could only be read in the web application — an automated service could say nothing but "refused, sign in and look". ⚠️ This means validationErrors is no longer tied to EARH-4001 alone; every entry is still read by its own code. Code lists gain validFrom and validTo per entry, and a new optional parameter asOf (yyyy-MM-dd) — GET codelists/tax-indicators?asOf=2026-02-18. ⛔ That is needed because on submission the validity is judged against the date of the INVOICE, not against today: an invoice dated in the past can be refused over a code that is valid now, and until now there was no way to check that in advance. An omitted asOf means today, as before; a date that cannot be read is refused with EARH-2020. No existing field, status or code changes its meaning. | 12.09.2026 | the date has not been decided yet |
| ⛔ CHANGE OF INTAKE BEHAVIOUR: a document whose issuer is not your organization is now refused straight away. NEW CODE EARH-2007 (400): the tax number written in cac:AccountingSupplierParty is not the organization this key belongs to. Until now such a document was accepted, got a docUid, lived in the system and went on to the tax authority — where it was rejected, so the reason showed up only as status 250 with EARH-4002, hours later and in the wrong place. From now on the refusal happens in the same call and no document is created. ⛔ An absent issuer is NOT an error: the field is still not required, the sender is resolved from the key, and the sample in this guide does not carry it. Only a tax number that is present and DIFFERENT is refused. The form with and without the MK prefix is the same taxpayer — MK4030995155258 and 4030995155258 both pass, so you do not have to change the form your own database holds. If you send for several organizations, use each one's own key. No existing field, status or code changes its meaning. | 12.09.2026 | the date has not been decided yet |
| Answers that used to fall outside the contract now carry a code, and received documents can finally be read in full. ⛔ NEW CODE EARH-2020 (400): a parameter that cannot be read — a required one missing (for example format on the file download), a date that is not a date, or a value outside the allowed set. Until now such a request returned a body with NO code field, so there was nothing to branch on. The detail field names the parameter. This also means ?status=999 no longer passes silently: it used to return 200 with an empty list, which looked exactly like "no such documents", and is now EARH-2020 listing the allowed statuses. An unexpected error is now always EARH-9000 with eventId, instead of the raw shape it used to carry (errorRef, errorCode, message). ⛔ POST outbound now accepts text/xml, application/octet-stream and a request with no Content-Type: until now only application/xml got through and everything else returned 404 EARH-1009 "this endpoint does not exist in this version" — a message that sent integrators looking in the wrong place. The shape of the body is still judged by EARH-2001 and EARH-2002. GET inbound/{docUid}/file finally returns content for invoices fetched from the tax authority — format=original was published as always available, yet returned 404 EARH-3002 for every such invoice. Statuses 140 (approved) and 150 (rejected) are now visible on a received document as soon as the decision is recorded, together with rejectionReason on rejected ones; until now the document stayed at 120 whatever was decided. receivedAt carries a real time — on invoices fetched from the tax authority it read 0001-01-01T00:00:00. The history of a submitted document no longer repeats 230 twice and ends with 270 once the document is archived, exactly as GET outbound/{docUid} says. Cancelling now really stops: POST outbound/{docUid}/cancel in status 210 or 220 halts the processing, and an invoice the tax authority has already accepted is refused with 409 EARH-3003 instead of being reported as 260 when it is not. No existing field changes its meaning. | 12.09.2026 | the date has not been decided yet |
| The response also carries the QR code link UJP returned. New field qrLink in the ujp block — on POST outbound and on GET outbound/{docUid} — right after euid, and ujpQrLink per row in the GET outbound list. It is exactly the link UJP returned when it accepted the invoice, the same one we print as the QR code on our own PDF. If you print the invoice in your own PDF, print this link as its QR code. ⛔ Do not build it from euid: the address differs per environment and is set by UJP. It is empty until UJP has accepted the invoice, and stays empty on a rejected one. With Prefer: wait the same response carries both euid and qrLink. No existing field, code or status has changed. | 11.09.2026 | the date has not been decided yet |
| The POST outbound response carries the UJP ID, and the call can wait for the outcome. New field ujp in the response — euid is the identifier UJP assigned to the invoice, state, code and message are its answer, checkedAt is when it was last checked. All of them are empty until UJP has answered. Also new is failureCode in the same response. The optional header Prefer: wait=N (N up to 30 seconds) holds the response until our processing has finished submitting to UJP — not until UJP decides anything further, and not until archiving and delivery finish. The response carries the Preference-Applied header with the wait actually applied. Without Prefer the response comes back immediately with status 210, as before; if the wait runs out before the outcome, read the state with GET outbound/{docUid}. The GET outbound list gains ujpEuid per row. ⛔ A document whose processing failed BEFORE the submission to UJP now ends at 250 with a failureCode — until now it stayed at 210 forever and could not be told apart from a document still being processed. No existing field, code or status changes its meaning. | 11.09.2026 | the date has not been decided yet |
| GET outbound/{docUid}/file and GET inbound/{docUid}/file now return a proper error when something is wrong. ⛔ Until now, asking for a representation the document does not have returned 406 Not Acceptable with an EMPTY body instead of 404 EARH-3002: the endpoint advertised only application/octet-stream, so the response carrying the code — which is JSON — could not be served. The caller was left with an HTTP status that says nothing about the cause. From now on both endpoints return 404 with EARH-3001 (no such document) or EARH-3002 (the document did not arrive in that representation), exactly as the description already said. New is EARH-9003 (409): the file record exists and claims the content was stored, but the content is gone — neither locally nor in cloud storage. ⛔ This is NOT EARH-3002: that code means the document never arrived in that representation, which is an orderly state; this one means the representation existed and its content was lost. Until now that case came out as a raw 500 in a shape that is not part of the contract (errorRef, errorCode, message) and with no code to branch on. Everything else about such a document — status, amounts, history — still reads normally. No existing field, code or status has changed. | 02.09.2026 | the date has not been decided yet |
| Inbound invoices for testing are now filled with one call. POST sandbox/seed creates a series of inbound documents, one per scenario, and the response says what each one stands for — so your test can assert rather than guess. The same seed always produces the same series: same amounts, same dates, same senders, same deadlines. An omitted seed means 1, not a random number — a first call you cannot reproduce would defeat the point. reset deletes the invoices this endpoint created earlier and nothing else, so an automated run can start from the same state every time; a document placed through sandbox/inbound is left alone. scenarios narrows the series; omitting it means all of them, and an unknown name is refused with EARH-2030, the response listing the allowed ones. ⛔ The three that end in 409 EARH-3006 matter most — deadlinePassed, alreadyAccepted, alreadyRejected. Those states come about with the passage of time or through an action that has already happened, so they cannot be created through sandbox/inbound, and that is exactly why software which has not met them in testing meets them first in production. The rest cover content (full, payloadOnly, pdfOnly, noContent), an acknowledged receipt (acknowledged), a deadline expiring today (deadlineToday), amounts (zeroVat, creditNote, largeAmount) and filters (secondSender, olderThanWeek, updatedRecently). The names are stable and can be written into a test, but reading the list from the response is better — the test then stays correct when we add a scenario. ⛔ Does not exist in production: there the endpoint returns EARH-1009. No existing field, code or status has changed. | 02.09.2026 | the date has not been decided yet |
| The whole path can now be exercised on the demo server, without a single document leaving for the tax authority. New: POST sandbox/inbound places a received document into your own organisation through the same intake path a real one takes, so GET inbound, GET inbound/{docUid}/file and POST inbound/{docUid}/decision can be tried end to end; the only required field is documentNumber, and sending the same euid again returns the existing document instead of creating a second one. A submitted document stops here after the UJP message has been composed and validated, and gets a new status 225 — validated, not submitted. That is NOT a failure and carries no failureCode; a document that fails validation still gets 250 with EARH-4001. The composed message is available from GET outbound/{docUid}/file?format=ujp-json. A decision on a received document is likewise recorded on our side but not reported to the tax authority. ⛔ None of this exists in production: there POST sandbox/inbound returns EARH-1009, and status 225 never occurs. Also new is the validationErrors field on GET outbound/{docUid}: when failureCode is EARH-4001, it carries the individual reasons with codes EARH-5001 to EARH-5012 — that code says WHAT happened, these say WHERE. Branch on each entry's code; the path field points at the field to look at and is NOT part of the contract. Code EARH-2030 (400) is added for an unusable body sent to sandbox/inbound. No existing field, code or status has changed. | 01.09.2026 | the date has not been decided yet |
| ⛔ PUBLISHED LIMIT CHANGED: traffic is measured per key, and the number is no longer a flat 300 for everyone. Until now the limit was counted per network address, which meant two keys behind one address shared a quota while one key from two addresses got double — the opposite of what "Limits and rules" publishes. From now on the partition is the key itself, and the value comes from the organisation's plan: Starter 60, Professional 300, Enterprise unlimited, Free no integration access. The owner of the key may lower the limit further for that key; the smaller of the two applies. This also introduces TWO NEW CODES, both 403 and both on EVERY call, not on an exhausted quota: EARH-1010 the organisation's plan does not carry integration access, and EARH-1011 the key has been switched off by its owner. ⛔ Neither of them carries Retry-After — waiting does not change the state, so retrying in a loop will not succeed. EARH-1006 (429) is unchanged and still carries Retry-After. | 29.08.2026 | the date has not been decided yet |
| ⛔ VALUES CHANGED: the status codes are renumbered. From now on the first digit says the direction and the last two the step in the life cycle: received documents carry 1xx, submitted ones 2xx. Submitted 10, 20, 30, 40, 50, 60, 70 become 210, 220, 230, 240, 250, 260, 270; the meanings are unchanged. Received 110, 120, 140, 150 stay as they are, and the reserved 160 is split into 170 archived and 180 final document received from the tax authority. With that the same step carries the same ending in both directions (140 ↔ 240, 150 ↔ 250), and none of our codes collides any longer with the tax authority's own list, which is two-digit. This is the ONLY departure from the rule that an existing value never changes meaning, and it was made before the first key for the demo environment was issued — precisely so it would not be paid for later. | 29.08.2026 | the date has not been decided yet |
| A decision on a received document can be made through the API. POST inbound/{docUid}/decision accepts or rejects a received document. A rejection carries both a reason code from the reject-reasons code list and a comment; an acceptance carries neither. The decision is recorded before it is reported onward, so a successful answer means it has been taken — whether the tax authority has already confirmed it is in the confirmedByTaxAuthority field. Read decisionDeadline before you decide: after that day the tax authority accepts the document on its own, and a decision sent afterwards is rejected. | 29.08.2026 | the date has not been decided yet |
| Received documents carry a state and a rejection reason. GET inbound and GET inbound/{docUid} return status with a numeric code (110 received, 120 in processing, 140 approved, 150 rejected), and rejectionReason comes with a document that was rejected. Both lists also return nextFrom — the starting point for the next call, so you can pull only what has changed. No existing field has been altered. | 28.08.2026 | the date has not been decided yet |
| Received documents carry the deadline for a decision. GET inbound and GET inbound/{docUid} return decisionDeadline — the day by which the document must be approved or rejected, where such a deadline exists. The value is a calendar date, without a time and without a time zone, and it stays visible after the document has been decided. After that day passes, the tax authority accepts the document on its own. | 28.08.2026 | the date has not been decided yet |