Оди на содржината

Процес: влезни е-фактури

Оваа страница те води од нула до влезна е-фактура што е преземена, потврдена и одлучена. Не треба да знаеш ништо однапред: секој чекор кажува што правиш, ти дава готова команда, покажува каков одговор да очекуваш и што да направиш ако добиеш нешто друго.

Сè се случува на sandbox серверот (https://sandbox.api.earhiva.mk). Таму документите за проба ги создаваш ти, со една команда — не чекаш вистинска фактура од партнер.

Влезната фактура оди во спротивна насока од излезната, и ти си оној што одлучува:

#КадеШто се случува
1Испраќачотја издава фактурата и ја праќа преку УЈП
2УЈПја заведува и ни ја предава
3eArhiva.mkја зема, ја чува и ти ја нуди преку API
4Твојот софтверја повлекува, ја потврдува како преземена и одлучува — прифаќа или одбива

⛔ Одлуката има рок. Секоја фактура носи decisionDeadline; по тој ден УЈП ја прифаќа сама, а одлука пратена потоа се одбива. Тоа е единствениот дел од овој тек што не може да се повтори.

ШтоЗошто
клуч за sandboxистиот како за излезните; ако веќе го имаш, продолжи на чекорот 2
команда curlза повиците; ја има на Windows 10 и 11, macOS и Linux
jq или кој било преглед на JSONодговорите тука се JSON, не XML

  1. Ако веќе имаш клуч од процесот за излезни фактури, прескокни го овој чекор — истиот клуч ги отвора и двете насоки.

    Ако немаш, побарај го по е-пошта на support@earhiva.mk, со наслов Барање за пристап кон sandbox серверот, и наведи: име на компанијата, лице за контакт, е-пошта, телефон и име на апликацијата што ќе го користи API.

  2. Барање · ова го извршуваш ти
    curl -i "https://sandbox.api.earhiva.mk/api/integration/v1/ping" \
    -H "X-Api-Key: <ВАШИОТ КЛУЧ>" \
    -H "X-Software-Id: <ИМЕ И ВЕРЗИЈА НА ВАШАТА АПЛИКАЦИЈА>"

    Првиот ред мора да е HTTP/1.1 200 OK, а телото {"ok": true, "keyValid": true, …}.

    ДобивашШто значиШто правиш
    401 со EARH-1001клучот не е препишан точно, или е истеченкопирај го повторно, без празни места
    400 со EARH-1008го нема заглавието X-Software-Idдодај го — задолжително е во секој повик
  3. На sandbox ти ги создаваш влезните фактури. Една команда прави серија од петнаесет, по една за секое сценарио — со содржина, без содржина, со поминат рок, со веќе потврдена одлука:

    Барање · ова го извршуваш ти
    curl -i -X POST "https://sandbox.api.earhiva.mk/api/integration/v1/sandbox/seed" \
    -H "X-Api-Key: <ВАШИОТ КЛУЧ>" \
    -H "X-Software-Id: <ИМЕ И ВЕРЗИЈА НА ВАШАТА АПЛИКАЦИЈА>" \
    -H "Content-Type: application/json" \
    -d '{"seed": 21, "reset": true}'
    Одговор од API-то · скратен
    {
    "seed": 21,
    "created": 15,
    "deleted": 15, // reset ги тргна претходно создадените
    "documents": [
    { "scenario": "full", "docUid": "eah_i_…", "euid": "SEED-21-full",
    "expect": "format=original и format=pdf враќаат 200; format=ubl враќа 404" },
    { "scenario": "deadlinePassed", "docUid": "eah_i_…", "euid": "SEED-21-deadlinePassed",
    "expect": "POST decision враќа 409 EARH-3006 — рокот поминал" }
    // … уште тринаесет
    ]
    }

    📌 seed е број на серијата, не случајност. Ист број дава иста серија — исти износи, исти датуми, исти испраќачи. Затоа тест напишан денес важи и утре. Изостанат број значи 1.

    📌 Читај го expect. За секој документ стои што точно треба да се случи со него — по тоа пишуваш тврдење во тестот, наместо да погодуваш.

    ДобивашШто значиШто правиш
    400 со EARH-2030име на сценарио што не постоиодговорот ги набројува дозволените — земи од таму
    404 со EARH-1009ја повикуваш адресата надвор од sandboxоваа команда постои само на sandbox серверот

    Ако наместо серија ти треба една фактура со твоја содржина — свој број, износи и PDF — тоа е POST sandbox/inbound.

  4. onlyNotImported=true ги дава само оние што сè уште не си ги потврдил — тоа е списокот што твојот софтвер го обработува:

    Барање · ова го извршуваш ти
    curl -i "https://sandbox.api.earhiva.mk/api/integration/v1/inbound?onlyNotImported=true&pageSize=100" \
    -H "X-Api-Key: <ВАШИОТ КЛУЧ>" \
    -H "X-Software-Id: <ИМЕ И ВЕРЗИЈА НА ВАШАТА АПЛИКАЦИЈА>"
    Одговор од API-то · еден ред од списокот
    {
    "items": [
    {
    "docUid": "eah_i_01M2AA6FNY8D2Y6Z7NXEGZ4810",
    "receivedAt": "2026-09-12T10:02:01.534351",
    "senderName": "Ветерник ДООЕЛ",
    "senderEdb": "MK4080012345678",
    "ackedAt": null, // сè уште не е потврдена
    "status": 120,
    "rejectionReason": null,
    "decisionDeadline": "2026-09-27"
    }
    ],
    "totalCount": 20,
    "page": 1,
    "pageSize": 100,
    "totalPages": 1,
    "nextFrom": null
    }

    Копирај го docUid — секој нареден чекор го бара.

    Ако сакашДодај
    само од еден испраќач&senderEdb=MK4080012345678
    само од одреден датум&dateFrom=2026-09-01T00:00:00Z
    поголема страница&pageSize=500 — тоа е и највисоката дозволена
    ДобивашШто значиШто правиш
    400 со EARH-3005побара страница поголема од 500одговорот ја именува дозволената
    400 со EARH-2020параметар што не може да се прочитаdetail го именува — на пример датум во погрешен облик
  5. Барање · ова го извршуваш ти
    curl -i "https://sandbox.api.earhiva.mk/api/integration/v1/inbound/<docUid>" \
    -H "X-Api-Key: <ВАШИОТ КЛУЧ>" \
    -H "X-Software-Id: <ИМЕ И ВЕРЗИЈА НА ВАШАТА АПЛИКАЦИЈА>"
    Одговор од API-то · вредностите се пример
    {
    "docUid": "eah_i_01M2AA6F71MZ3MPCEPTYBADT6H",
    "receivedAt": "2026-09-12T10:02:01.057626",
    "senderName": "Модрина ДОО",
    "senderEdb": "MK4030995155258",
    "ackedAt": null,
    "documentNumber": "021-001",
    "amount": 83252.79,
    "currency": null,
    "status": 120,
    "rejectionReason": null,
    "decisionDeadline": "2026-09-27" // ⛔ прочитај го ПРЕД да одлучуваш
    }
    ПолеШто прави со него твојот софтвер
    status110 примена, 120 во обработка, 140 одобрена, 150 одбиена
    decisionDeadlineденот до кој одлуката мора да падне; по него УЈП прифаќа сама
    rejectionReasonтекст, само кај одбиена фактура
    ackedAtкога ти си ја презел; null значи дека сè уште не си

    Непознат docUid враќа 404 со EARH-3001 — истиот одговор како за документ на друга организација, намерно.

  6. Содржината се бара изречно, со format:

    Барање · ова го извршуваш ти
    curl -i "https://sandbox.api.earhiva.mk/api/integration/v1/inbound/<docUid>/file?format=original" \
    -H "X-Api-Key: <ВАШИОТ КЛУЧ>" \
    -H "X-Software-Id: <ИМЕ И ВЕРЗИЈА НА ВАШАТА АПЛИКАЦИЈА>" \
    -o dokument.bin
    formatШто враќа
    originalфајлот онака како што пристигнал — секогаш достапен кога документот воопшто носи содржина
    pdfсамо ако со документот дошол PDF
    ublсамо ако документот пристигнал како UBL XML

    Одговорот носи Content-Disposition со име изведено од docUid.

    ДобивашШто значиШто правиш
    404 со EARH-3002тој облик не постои за овој документпроба со original; ако и тој враќа 3002, документот е без содржина
    400 со EARH-2020го нема formatдодај го — не се подразбира
    409 со EARH-9003обликот постоел, а содржината не е достапнапиши на поддршката и прати го docUid; останатите податоци се читаат нормално
  7. Потврдата кажува дека документот е влезен во твојот систем. По неа тој излегува од списокот со onlyNotImported=true:

    Барање · ова го извршуваш ти
    curl -i -X POST "https://sandbox.api.earhiva.mk/api/integration/v1/inbound/<docUid>/ack" \
    -H "X-Api-Key: <ВАШИОТ КЛУЧ>" \
    -H "X-Software-Id: <ИМЕ И ВЕРЗИЈА НА ВАШАТА АПЛИКАЦИЈА>"
    Одговор од API-то
    { "docUid": "eah_i_01M2AA6F…", "ackedAt": "2026-09-12T10:02:19.162282" }

    📌 Повторена потврда не е грешка и не го поместува времето — го враќа она од првиот пат. Значи слободно повикај ја повторно ако не си сигурен дали првиот повик поминал.

    ⛔ Потврдата не е одлука. Таа кажува „го презедов”, не „се согласувам”. Одлуката е чекорот 8, и само таа стигнува до УЈП.

  8. Телото носи accepted, и само кај одбивање уште причина и коментар:

    Прифаќање · ова го извршуваш ти
    curl -i -X POST "https://sandbox.api.earhiva.mk/api/integration/v1/inbound/<docUid>/decision" \
    -H "X-Api-Key: <ВАШИОТ КЛУЧ>" \
    -H "X-Software-Id: <ИМЕ И ВЕРЗИЈА НА ВАШАТА АПЛИКАЦИЈА>" \
    -H "Content-Type: application/json" \
    -d '{"accepted": true}'
    Одбивање · причината е шифра од шифрарникот
    curl -i -X POST "https://sandbox.api.earhiva.mk/api/integration/v1/inbound/<docUid>/decision" \
    -H "X-Api-Key: <ВАШИОТ КЛУЧ>" \
    -H "X-Software-Id: <ИМЕ И ВЕРЗИЈА НА ВАШАТА АПЛИКАЦИЈА>" \
    -H "Content-Type: application/json" \
    -d '{"accepted": false, "rejectReasonCode": "O-1", "comment": "Погрешен износ во ставката"}'

    Шифрите за причина се читаат од шифрарникот — не ги впишувај од глава:

    Барање · ова го извршуваш ти
    curl -s "https://sandbox.api.earhiva.mk/api/integration/v1/codelists/reject-reasons" \
    -H "X-Api-Key: <ВАШИОТ КЛУЧ>" \
    -H "X-Software-Id: <ИМЕ И ВЕРЗИЈА НА ВАШАТА АПЛИКАЦИЈА>"
    Одговор од API-то · скратен
    {
    "type": "reject-reasons",
    "items": [
    { "code": "O-1", "name": "Погрешно пресметан ДДВ (несоодветна даночна основа, ДДВ стапка, …)" },
    { "code": "O-4", "name": "Прометот не е извршен" }
    ]
    }

    Ако е во ред, одговорот е 200:

    Одговор од API-то · одлуката е запишана
    {
    "docUid": "eah_i_01M2AA6F…",
    "accepted": true,
    "confirmedByTaxAuthority": false, // УЈП сè уште не ја потврдил
    "confirmedAt": null,
    "failureCode": null
    }

    📌 200 значи „запишано кај нас”, не „потврдено кај УЈП”. Пријавувањето натаму оди по него; дали УЈП веќе го потврдил стои во confirmedByTaxAuthority. Состојбата ја читаш со GET inbound/{docUid}: по прифаќање статусот станува 140, по одбивање 150 и rejectionReason го носи текстот на причината.

    Ако не е:

    ДобивашШто значиШто правиш
    400 со EARH-2022телото не е употребливо: одбивање без причина или без коментар, прифаќање што носи причина или коментар, или неактивна причинаdetail кажува кое од четирите; поправи го телото
    409 со EARH-3006предоцна е: рокот поминал, или одлуката е веќе потврдена кај УЈПнема што да се поправи — состојбата не се враќа
    404 со EARH-3001нема таков документпровери го docUid
    409 со EARH-9002одлуката не може да се пренесе поради нешто кај насне е грешка во барањето; повтори подоцна, а ако се задржи — пиши на поддршката

    ⛔ Празно тело значи одбивање без причина, не „нема одлука” — и затоа враќа EARH-2022. Полето accepted е задолжително.

  9. За редовна работа не го повлекувај целиот список. Со by=updated одговорот носи nextFrom — почетна точка за следниот повик:

    Прв повик
    curl -s "https://sandbox.api.earhiva.mk/api/integration/v1/inbound?by=updated&pageSize=500" \
    -H "X-Api-Key: <ВАШИОТ КЛУЧ>" -H "X-Software-Id: <ВАШАТА АПЛИКАЦИЈА>"
    Следен повик · со вредноста од nextFrom
    curl -s "https://sandbox.api.earhiva.mk/api/integration/v1/inbound?by=updated&dateFrom=2026-09-12T09:57:08.4498392%2B02:00&pageSize=500" \
    -H "X-Api-Key: <ВАШИОТ КЛУЧ>" -H "X-Software-Id: <ВАШАТА АПЛИКАЦИЈА>"

    📌 by=created не враќа nextFrom — таму стои null. Инкрементално повлекување е ветено само со by=updated: документ примен пред месец дена чија состојба се сменила вчера влегува во второто, не во првото.

    ⚠️ Границата е вклучителна: последниот документ од претходниот повик може да се појави и во следниот. Тоа е намерно — подобро еден повторен ред отколку испуштен.


Ако чекорот 8 е исполнет, твојот софтвер ги повлекува влезните е-фактури, ја презема содржината и одлучува по нив. Петте повици — GET inbound, GET inbound/{docUid}, GET inbound/{docUid}/file, POST ack и POST decision — се целото што ти треба. Во продукција се менува само адресата и клучот, а документите доаѓаат од вистински испраќачи наместо од sandbox/seed.

Кога ти требаОди на
статусите на влезен документ и премините меѓу нивВлезни документи
серија документи по сценарија, со објаснување за секоеСерија по сценарија
една фактура со твоја содржина и свој PDFЕдна фактура со своја содржина
значењето на секоја шифра EARH-xxxxКаталог на грешки
праќање фактури наместо примањеПроцес: излезни е-фактури