🧾 Unified Register of Tax Invoices (ЄРПН) — for the developer
Updated
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#
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 theX-ZuloOne-RelativePathheader. The host strips it off and appends it to the connection'sBaseUrl; 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.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.erpn-receipt→ firstReceiveCorrespondence(collect the post), thenGetDocInfo?exDocId=&cardCode=andGetDocKVTwith the same query.SENDSTT/OPERTYPE9 / 111 → Cleared; 10 / 16 / 112 → Rejected. 7 and 8 ("a receipt has arrived") are not a State Tax Service (ДПС) decision yet. The registration number isREGNUMoff 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