Practical notes for integrating with EFRIS, the Electronic Fiscal Receipting and Invoicing Solution operated by the Uganda Revenue Authority.
EFRIS answers a rejected submission with a numeric code and a terse sentence that rarely says what to change. This repository documents what those codes actually mean and how to fix them, alongside a Postman collection and worked examples.
Everything here was learned against the live service, not transcribed from the specification — the two disagree in several places, and the places they disagree are exactly where integrations fail.
Free to use. No account, no sign-up.
errors/ |
What each URA rejection means, and the fix |
postman/ |
Postman collections for the EFRIS interfaces |
examples/ |
Submitting an invoice, in curl, JavaScript and Python |
Start here if a document is being rejected right now.
| Code | What it means |
|---|---|
| 15 | The session key expired server-side — not a payload problem |
| 306 | Taxpayer is not registered, or the TIN does not match the device |
| 676 | The product is not registered, or its stock is insufficient |
| 1304 | summary.itemCount counted a discount line as a product |
| 1343 | Line-level tax does not reconcile with the breakdown |
| 1344 | summary.taxAmount must include excise |
| 1345 | summary.grossAmount must exclude excise — the mirror of 1344 |
| 1462 | Excise on a credit note was dropped or mismatched |
| 1526 | Currency or exchange-rate handling on a non-UGX document |
| 2253 | The seller's reference number has already been used |
| 2831 | A tax breakdown row is missing its rate name |
The full index covers the rest.
A short list of behaviours that are not obvious from the specification.
The signature is SHA-1, not SHA-256. RSA with PKCS#1 v1.5 padding, over the content field alone — never the surrounding envelope. A SHA-256 signature is rejected, and signing the envelope fails without explaining why.
Payload encryption is AES-128 in ECB mode. There is no IV to supply. The session key from the T104 handshake arrives doubly wrapped: RSA-encrypted bytes whose plaintext is itself a Base64 string of the key.
A discount is its own line. It cannot be a property of the line it reduces. It must follow that line as a separate negative entry, flagged as the discount, carrying no quantity — a quantity there is rejected outright.
Excise pulls the two summary totals in opposite directions. It is included
in summary.taxAmount and excluded from summary.grossAmount. Getting either
backwards produces 1344 or 1345, with no indication which figure is wrong.
The device number comes from URA. It is issued when a taxpayer's device is registered and cannot be derived from the TIN. A guessed value passes the handshake and then fails on every document.
Every taxpayer certificate has its own password. Set when the key pair was generated. There is no shared or default password, and a mismatch surfaces only as "Invalid password or PKCS12 data".
If you would rather not work through the above:
EFRIS Gateway — a multi-tenant EFRIS platform. Send invoices as ordinary JSON and it handles the handshake, signing, encryption and tax reconciliation, returning the fiscal document number, verification code and QR code. Available as a hosted API, or licensed as complete source to run on your own infrastructure.
efrisapi.com — the same error-code reference as a browsable website, with a quickstart and coverage of all ~70 EFRIS interfaces (T101–T187).
Both are maintained by Midway, Kampala.
If you hit a rejection this does not cover, or find something here that no longer matches the live service, open an issue. URA changes the service without announcing it — T186 and T187 arrived in 2024, T127 and T130 changed in 2025 — so corrections are welcome and useful to everyone integrating.
Documentation and examples are released under CC BY 4.0 — use them anywhere, including commercially, with attribution.
This repository contains documentation only. It is independent software, not affiliated with, endorsed by, or operated by the Uganda Revenue Authority.