Quick Start
The path of a document
Section titled “The path of a document”Before the steps — the whole picture in one place. The diagram shows which systems the document passes through and where in that path each address comes in:
| Direction | What happens |
|---|---|
| Outbound document | your product sends POST outbound, we process it and submit it to the tax authority, which delivers it to the recipient |
| Follow-up | the status is read with GET outbound — by docUid or as a list; details under Statuses |
| Inbound document | a document somebody sent to you is read with GET inbound, and the decision goes back with POST decision; details under Inbound documents |
This page takes you from nothing to a submitted document in five steps. Every step carries a
ready-made example: copy the whole of it and replace the two placeholders — <YOUR KEY> with the
key you were issued, and <NAME AND VERSION OF YOUR APPLICATION> with your software’s identifier.
Nothing else changes.
The base address of the demo environment is https://sandbox.api.earhiva.mk, and it appears in
every example below.
1. Request a key for the demo environment
Section titled “1. Request a key for the demo environment”The key for the demo environment is requested from us — it is not created from a screen. Send the
request by e-mail to support@earhiva.mk, with the subject Request for a demo environment key.
The key you receive carries the IntegrationApi scope — without it every call to the addresses
under api/integration/v1 is rejected, even when the key itself is valid.
The request carries two separate lists of details: about you as the integrator, and about the client on whose behalf test data will be sent to UJP. A demo key is tied to a specific taxpayer, so it cannot be issued without the second list. Copy this text and fill both in:
Request for a demo environment key for eArhiva
Integrator details company name: contact person: e-mail: telephone: name of the application that will use the API:
Client details company name: tax number (EDB): contact person: e-mail: telephone:2. Check that the key works
Section titled “2. Check that the key works”GET /api/integration/v1/ping does not touch a single document — it only says whether the key was
accepted. That is the cheapest way to tell “the key is not valid” apart from “the document is
wrong” before sending anything at all.
Both headers are required on every call: X-Api-Key carries the key, and X-Software-Id names
your software. The second is free text — write in the name and version of your application, e.g.
AccountingPro/2.4. That value does not affect what the key is allowed to do; it is there so we
can recognise you in the logs when you come to us with a question.
curl -i -X GET "https://sandbox.api.earhiva.mk/api/integration/v1/ping" \ -H "X-Api-Key: <YOUR KEY>" \ -H "X-Software-Id: <NAME AND VERSION OF YOUR APPLICATION>"On success it returns 200 and a body:
{ "ok": true, "keyValid": true, "serverTime": "2026-08-25T10:14:03.412+02:00"}3. Send a document
Section titled “3. Send a document”The document is sent as UBL 2.1 XML in the body of POST /api/integration/v1/outbound. The
X-Idempotency-Key header is required: it is your own mark for this submission, and it is what
makes a repeated call with the same content return 200 instead of creating a second document.
Download the working example invoice-ubl21-en.xml,
save it as invoice.xml in the folder you run the command from, and run the command unchanged.
It is accepted exactly as it is — the answer is 201 with a docUid, which is the whole point
of this step: it confirms the key, the headers and the shape of the body are right.
curl -i -X POST "https://sandbox.api.earhiva.mk/api/integration/v1/outbound" \ -H "X-Api-Key: <YOUR KEY>" \ -H "X-Software-Id: <NAME AND VERSION OF YOUR APPLICATION>" \ -H "X-Idempotency-Key: quick-start-001" \ -H "Content-Type: application/xml" \ --data-binary @invoice.xml4. Take the identifier
Section titled “4. Take the identifier”On success the HTTP status is 201 Created (the first line curl -i prints:
HTTP/1.1 201 Created), and the body carries docUid — the document’s identifier on our
side. It is the only thing you need for everything that follows: status, file download, history,
cancellation.
Copy the value of docUid from your own response and keep it. Every command from here on,
starting with step 5, carries it in place of the <docUid> placeholder. The value below is an
example from one such response — yours will be different, and only yours works with your document.
{ "docUid": "eah_o_01K3F9YB2M7Q4XVZ8NDHRPTC5E", "status": 210, "receivedAt": "2026-08-25T10:16:41.088+02:00", "alreadyExisted": false}⚠️ The status field in the body is 210 — that is the document’s status, not an HTTP code.
The two numbers live in two different places: 201 is in the response header, 210 is in the body.
Status 210 means the document was received and recorded on our side. The response does not
wait for processing — that starts afterwards, and is followed with step 5.
If the same X-Idempotency-Key is repeated with the same content, 200 comes back with the same
docUid and alreadyExisted: true. That is not an error — that is the protection against double
submission.
5. Check the status
Section titled “5. Check the status”GET /api/integration/v1/outbound/{docUid} returns the document’s current status. In the command
below replace <docUid> with the value you copied in step 4:
curl -i -X GET "https://sandbox.api.earhiva.mk/api/integration/v1/outbound/<docUid>" \ -H "X-Api-Key: <YOUR KEY>" \ -H "X-Software-Id: <NAME AND VERSION OF YOUR APPLICATION>"{ "docUid": "eah_o_01K3F9YB2M7Q4XVZ8NDHRPTC5E", "status": 220, "failureCode": null, "failureMessage": null, "createdAt": "2026-08-25T10:16:41.088+02:00", "partnerEdb": "4030000000000", "documentNumber": "0001/2026", "amount": 1180.00, "currency": "MKD", "ujp": null}Right after submission the document is in status 210 and the fields for partner, number, amount
and currency may be null — they are filled in once processing has read the document. An empty
field there does not mean something failed; that is visible only from status and failureCode.
Every value of status, what it means and what it can move on to, is on the
Statuses page.
Next — the inbound direction
Section titled “Next — the inbound direction”That completes the outbound flow: you send a document and you read its status. The inbound direction — documents someone sends to you — cannot be exercised the same way, because those arrive from the tax authority and the sandbox server never talks to them. Your organisation there has no inbound documents at all.
That is what these two endpoints are for. They answer different questions:
| When you need it | Take |
|---|---|
| Every state at once, repeatably — including the ones you cannot create yourself, such as an expired deadline or a decision already made | A series by scenario |
| One specific document with your content — a file from a real customer, say | One invoice with your own content |
⛔ Both exist only on the sandbox server. In production inbound documents are delivered by the tax authority and cannot be placed from outside.
📌 Read “A series by scenario” first. Without it your organisation on the sandbox has no inbound documents at all, so the Inbound documents page describes something that does not yet exist for you.