ZuloOne
English
Sign in
Menu

🧾 Unified Register of Tax Invoices (ЄРПН) — for the developer

Updated

← Localizations

For the user: how to switch it on. For the analyst: the package scope · setting up M.E.Doc.

The LocalizationUkraine package, the IUaEInvoice service. The envelope is the shared TaxDocument of the Tax model. The integration wire is the M.E.Doc REST API, not an invented JSON.

The specification: the method list, a MakeDoc tax-invoice example, a MakeDoc adjustment example, the statuses, GetDocInfo. Swagger lives on the accountant's own machine: http://localhost:63777/Swagger/index.html.

Why not the way ZATCA does it#

KSA has a developer-portal / sandbox. M.E.Doc has no cloud sandbox: REST is a service on the accountant's PC (localhost:63777). Waiting for sandbox.medoc.ua is pointless. A licence that "files to the State Tax Service (ДПС) from this PC" is not enough: REST needs networked M.E.Doc and the "Integration with accounting systems" licence. Without an agent on that PC the host refuses 127.0.0.1 as SSRF.

In KSA the host assembles both the XML and the signature. In Ukraine the XML and the qualified electronic signature (КЕП) stay inside M.E.Doc on the PC: a script cannot do DSTU (ДСТУ) 4145. So the body is an array of MakeDoc cells {TAB, LINE, NAME, VALUE} carrying the names from their own example (FIRM_NAME, TAB1_A13, …), and the form's charCode (J1201010 / F1201010) goes in the query.

Three steps, not one POST#

  1. erpn-send → POST /api/Document/MakeDoc?userLogin&edrpou&shablonType=1&charCode&exDocId. The answer is { "Code": 0, "Name": "CardCode", "Value": "3446267" }. The query is per document, so the service puts the relative path in the X-ZuloOne-RelativePath header. The host strips it off and appends it to the connection's BaseUrl; the header itself never goes on the wire. Without a rebuilt Core the header is sent as it is — M.E.Doc ignores it and the query is never substituted.
  2. erpn-gov → POST /api/SignSendReceive/ToGov?cardCode=. The public FAQ gives no request body: an empty array goes out and the identifier sits in the query. M.E.Doc signs an unsigned document with the certificates already on the PC.
  3. erpn-receipt → first ReceiveCorrespondence (collect the post), then GetDocInfo?exDocId=&cardCode= and GetDocKVT with the same query. SENDSTT / OPERTYPE 9 / 111 → Cleared; 10 / 16 / 112 → Rejected. 7 and 8 ("a receipt has arrived") are not a State Tax Service (ДПС) decision yet. The registration number is REGNUM off the receipt, not the MakeDoc CardCode.

ApplyOutcomesAsync does not set Cleared on a Succeeded MakeDoc: that only means "the document is in M.E.Doc". Cleared is set by the GetDocInfo / GetDocKVT receipt. But ApplyOutcomesAsync does enqueue ToGov — so that job (PullUaTaxInvoiceReceipts) is the switch between "parked in the M.E.Doc journal" and "gone to the State Tax Service (ДПС)". With the job not running, MakeDoc arrives and ToGov is not queued. The "Ask the operator" button calls ResolveInDoubtAsync / GetDocInfo, not ToGov.

InDoubt we neither acknowledge nor retry.

Two form codes#

A tax invoice and an adjustment (розрахунок коригування) carry different charCodes (TaxInvoiceFormCode / TaxInvoiceAdjustmentFormCode) and different MakeDoc cells. Tax invoice (ПН): N11, TAB1_A13. Correction calculation (РК): N15, CORRCMPL (the date/number of the original tax invoice (ПН)), TAB1_A3 / TAB1_A01=1 (a change of quantity). The names come from the official examples, not from the State Tax Service (ДПС) XML.

Login#

The channels are authMode: Login, authHeader: X-API-Key. The host itself issues POST /api/System/v2/login (the FAQ plus the M.E.Doc 11.02.200 update) before MakeDoc / ToGov / GetDocInfo. The script never sees the login: the password is Integration:Credentials, the login is the connection's CredentialRef (the same userLogin MakeDoc already takes).

The body is { "userLogin", "password" }. The public FAQ does not name the keys — userLogin is the same one MakeDoc already uses. The token travels in the X-API-Key header, not as a Bearer. It is cached for about 5 minutes per (BaseUrl, userLogin) pair, so that polling for receipts does not log in once per envelope.

With no password in Integration:Credentials the login is skipped: the channel is left without X-API-Key — which is how the integration tests pass with no live M.E.Doc.

Do not put a ready-made Bearer token in the credential — what goes there is the M.E.Doc account's password.

A live login will not run on the stand until Core is rebuilt: authMode Login is read by the host, not by the script.

Channels#

Channel M.E.Doc method
erpn-send MakeDoc. Key erpn:{uuid}
erpn-gov ToGov once CardCode is known. Key erpn-gov:{uuid}
erpn-receipt GetDocInfo + GetDocKVT (+ ReceiveCorrespondence over the same channel)

All three have connectionTable = TaxAuthorityConnection. A local M.E.Doc: BaseUrl = http://127.0.0.1:63777 and the AgentTag of the agent sitting on that same PC.

Tests: UkraineTaxInvoiceTest. They do not exchange data with a live M.E.Doc.

Same topic for other readers: Analyst · User

Where next#

Was this page helpful?