e-Transport¶
ETransportClient covers Romania's goods-transport declaration service: filing
declarations and obtaining UIT codes, correcting/deleting/confirming them,
changing vehicles, and reading back notifications and statuses.
Fully translated — no XML in sight¶
Unlike e-Factura (XML pass-through), e-Transport is a full translation of ANAF's schema: there is usually no upstream software producing declaration XML, and ANAF's XSD is small and fully enumerated. The flat models are bidirectional — the same models author a filing and view a parsed one — and cover all four operations:
| Operation | Model |
|---|---|
Declaration (or correction, via correction_of_uit) |
FlatTransport |
| Deletion of a UIT | FlatDeletion |
| Arrival confirmation | FlatConfirmation |
| Vehicle change | FlatVehicleChange |
Enum-coded fields (counties, countries, border points, customs offices, operation types, …) are typed with the enums generated from ANAF's XSD and accept either the ANAF code or the descriptive name.
Authoring and filing a declaration¶
import datetime as dt
from decimal import Decimal
from anafpy.etransport import (
ETransportClient, FlatDeletion, FlatTransport, FlatTransportAddress,
FlatTransportDocument, FlatTransportGood, FlatTransportLocation,
FlatTransportPartner, FlatTransportVehicle,
)
from anafpy.etransport.schema.schema_etr_v2_20230126 import (
CodJudetType, CodScopOperatiuneType, CodTaraType, CodTipOperatiuneType,
TipDocumentType,
)
declaration = FlatTransport(
operation_type=CodTipOperatiuneType.TTN, # domestic transport
partner=FlatTransportPartner(
name="Partener SRL", country=CodTaraType.ROMANIA, code="12345678",
),
vehicle=FlatTransportVehicle(
plate="CJ01ABC", carrier_name="Transport SRL", carrier_code="23456789",
carrier_country=CodTaraType.ROMANIA, transport_date=dt.date(2026, 7, 10),
),
start_location=FlatTransportLocation(address=FlatTransportAddress(
county=CodJudetType.CLUJ, locality="Cluj-Napoca", street="Memorandumului",
)),
end_location=FlatTransportLocation(address=FlatTransportAddress(
county=CodJudetType.MUNICIPIUL_BUCURESTI, locality="Bucuresti",
street="Calea Victoriei",
)),
goods=[FlatTransportGood(
operation_scope=CodScopOperatiuneType.COMERCIALIZARE,
name="Materiale constructii", quantity=Decimal("100"), unit_code="KGM",
gross_weight=Decimal("110"), net_weight=Decimal("100"),
value_ron=Decimal("2500"), tariff_code="6810",
)],
documents=[FlatTransportDocument(
doc_type=TipDocumentType.CMR, date=dt.date(2026, 7, 9), number="FAC-001",
)],
)
async with ETransportClient(provider) as etransport:
result = await etransport.upload_document(declaration, cif="12345678")
print(result.uit) # the UIT code, issued at upload time
# later: delete / confirm / change vehicle on that UIT the same way, e.g.
await etransport.upload_document(FlatDeletion(uit=result.uit), cif="12345678")
upload_document renders the flat model to ANAF's XML and files it in one step.
The pieces are also available separately: build_etransport composes the
generated schema document, render_etransport serializes it, and the plain
upload files ready-made XML bytes.
Reading back¶
get_status/upload_and_wait— track an upload's processing, same shape as e-Factura's.list_notifications— an async iterator over recent notifications (empty window → empty iterator; real errors raise — see the error model).info— active declarations / UIT lookups, returned as anInfoList.read_flat_transport— project any parsed e-Transport document back into the same flat models you author with.
Field-level shape checks¶
The flat models enforce, at construction time, the XSD constraints plus the
unconditional rules of ANAF's e-Transport Schematron — UIT check digits,
gross ≥ net weight, ALTELE requiring a note, and so on. This is data hygiene,
not validation: e-Transport has no standalone validator, ANAF validates on
upload, and the operation-type conditional rules stay ANAF's (they appear only
as field descriptions). There is deliberately no local rule engine.
Presenting the UIT¶
A filing yields a UIT, and the code then has to travel — to the driver, who must
carry it, and often to the partner company. ANAF issues no document for that, so
anafpy[cards] renders two PDFs from a filing you already have:
import datetime as dt
from anafpy.etransport import UitCard, load_cardpdf
card = UitCard.from_upload(
declaration, result, # the flat model + the UploadResult
declarant_name="SC EXEMPLU AGRO SRL",
declarant_code="RO12345678", # printed verbatim, prefix and all
filed_on=dt.date.today(),
)
render = load_cardpdf()
Path("card.pdf").write_bytes(render.render_card(card))
Path("details.pdf").write_bytes(render.render_details(card))
print(card.summary_text()) # the message to send alongside
The card is one page at a phone's own aspect ratio (90×195mm), so the driver
opens it full-screen and the phone is the document: the code set large, a QR
carrying the bare UIT, and the plates and dates a roadside check asks for. The
detail document is A4 and carries the whole filing, goods table included,
for the partner company. Both keep every value as selectable text — that is why
they are PDFs rather than images — and summary_text() is the paste-into-a-chat
fallback for phones whose PDF viewer will not select text.
uit_expiry is never computed: it is ANAF's data_exp_uit, reported by the
info endpoint — pass it verbatim, in whatever shape ANAF returned it. It is
the date from which the UIT counts as expired, not the last valid day, so
the documents print data_exp_uit - 1 under "valabil până la" and mark a lapsed
UIT EXPIRAT from data_exp_uit itself.
Most filings have none to pass, because ANAF scopes info to the transport
organizer — a declarant who is not the carrier reads back nothing. The
documents then fall to the statutory window instead of leaving the driver
with no date: OUG 41/2022 art. 11 counted from the declared transport date, by
anafpy.etransport.validity. It is rendered as an
estimate and never as ANAF's word — amber, captioned VALABIL (ESTIMAT) PÂNĂ
LA, with the rule named in the footer, and PROBABIL EXPIRAT rather than a red
EXPIRAT once past.
card.validity(today) is where the two meet, and the only place to read the
resolved window from:
window = card.validity()
window.source # "anaf" when data_exp_uit was supplied, else "statutory"
window.last_valid_day # last day of use, inclusive
window.first_expired_day # the day it lapses — data_exp_uit's own shape
window.days # the statutory count (5 or 15); None when it is ANAF's
window.expired
ANAF's date always wins when present. Where the sources leave doubt the shorter window is encoded — DIE gets 5, since only DIN is named — because an estimate that runs long puts a driver on the road with a lapsed UIT, while one that runs short only prompts a check.
Both documents are informative — generated locally, not issued by ANAF, and they say so on their face. The QR encodes the raw 16-character UIT and nothing else: there is no official ANAF QR format, so it is a scan-to-copy convenience matching what Romanian invoicing software already prints.
Each shape is its own CLI command and MCP tool (etransport_uit_card /
etransport_uit_details), so a workflow can render either, or both:
anafpy etransport card declaration.xml <UIT> -o card.pdf --expiry 2026-08-15
anafpy etransport card declaration.xml <UIT> -o card.pdf # statutory estimate
anafpy etransport details declaration.xml <UIT> -o details.pdf --note "..."