Docs / Quickstart
Quickstart
From nothing to a fiscalised invoice. Work against the URA sandbox until the output is right — documents submitted to production are real tax records.
What you need from URA
- A TIN, and the device number registered against it
(URA's convention is
<TIN>_02for a first web-service device). - A PKCS#12 certificate (
.pfx) and its password.
You do not need to apply for an appId. It names the
integration channel, not you — AP04 for system-to-system, the same
for everyone. This trips people up because it looks like a credential.
Install
pip install -r requirements.txt
Python 3.9 or newer. The client itself needs only requests and
cryptography.
Connect
import os, logging
from efris import EfrisClient, EfrisConfig, EfrisError
logging.basicConfig(level=logging.INFO)
client = EfrisClient(EfrisConfig(
tin=os.environ["EFRIS_TIN"],
cert_path=os.environ["EFRIS_CERT_PATH"],
cert_password=os.environ["EFRIS_CERT_PASSWORD"],
legal_name="Your Company Ltd", # appears on the invoice
test_mode=True, # URA sandbox
))
print(client.get_server_time())
Always start with get_server_time(). It is unsigned and
unencrypted, so if it succeeds and everything else fails, the problem is the certificate
rather than connectivity or your TIN. That one check separates the two most common
first-day failures.
Fiscalise an invoice
Give the library plain line data and it computes the tax-inclusive breakdown:
document = client.build_invoice_payload(
"INV-2026-0042",
[
{"item_code": "CHAIR-001", "item_name": "Office Chair",
"quantity": 2, "unit_price": 150000, "tax_rate": 18},
{"item_code": "DESK-004", "item_name": "Desk 120cm",
"quantity": 1, "unit_price": 420000, "tax_rate": 18},
],
customer_name="Acme Wholesalers Ltd",
customer_tin="2000000000",
buyer_type="0", # 0 B2B, 1 B2C, 2 foreigner, 3 B2G
)
result = client.upload_invoice(document)
That produces a summary of 720000.00 gross, 610169.49 net
and 109830.51 tax — VAT-inclusive, reconciling the way URA recomputes
it.
Two line shapes, and they behave differently. Plain shape
(quantity, unit_price, tax_rate) is calculated for
you. EFRIS shape (qty, unitPrice, total,
tax) is taken as authoritative and is not recalculated — so
supplying qty and unitPrice without total would
otherwise fiscalise a zero-value invoice. The library refuses that rather than filing a
wrong record.
Handle rejections
try:
client.upload_invoice(document)
except EfrisError as exc:
print(exc.code, exc.message) # branch on the CODE, never the text
if exc.code == "1345":
... # summary.grossAmount includes excise
URA rewords messages between releases, so the number is the stable contract. See the error reference for what each one means.
Serving several taxpayers
Nothing is global. Build one config per taxpayer from wherever you keep credentials, and cache the client — a cold one performs a key exchange on its first call:
clients = {}
def for_tenant(tenant):
if tenant.id not in clients:
clients[tenant.id] = EfrisClient(EfrisConfig(
tin=tenant.tin,
device_no=tenant.device_no,
cert_path=tenant.cert_path,
cert_password=tenant.cert_password,
legal_name=tenant.legal_name,
test_mode=tenant.sandbox,
))
return clients[tenant.id]
Each holds its own session key, so tenants never share cryptographic state. Certificates are per-taxpayer and must be stored per-taxpayer.
Going to production
- Set
test_mode=Falseonly once sandbox output is verified correct. - TLS verification is disabled for the sandbox only, because URA's test certificate does not validate. Production is always verified.
- Keep the
.pfxoutside your repository and out of container images.
Next
- Protocol guide — what happens underneath
- Interface coverage — every T-code
- Error codes — when URA says no
The EFRIS API Kit handles this case already — it is one of the rejections the library was calibrated against. This page stays free either way.