e-Transport API¶
The client, its value types, and the bidirectional flat models. The generated
XSD models (anafpy.etransport.schema) are intentionally not documented here —
their nomenclature enums (CodJudetType, CodTaraType, …) mirror ANAF's code
lists one-to-one.
anafpy.etransport.client ¶
Async client for the RO e-Transport web services (ETRANSPORT/ws/v1).
Design mirrors anafpy.efactura.client with four key differences:
1. Upload path embeds standard, cif, and versiune as path segments
(POST /upload/ETRANSP/{cif}/{versiune}), not query params; the body must be the
declaration XML (application/xml — there is no JSON request format).
2. Responses are JSON, not e-Factura's XML <header> (per the vendored swagger
specs); errors ride an Errors[{errorMessage}] array, including lista's
no-results note.
3. Status uses a path param (GET stareMesaj/{id_incarcare}), not a query param.
4. No download step — the UIT code is returned in the upload response; state is
tracked via lista / stareMesaj.
ETransportClient ¶
ETransportClient(provider: TokenProvider, *, environment: Environment = PROD, http: AsyncClient | None = None, timeout: float = 60.0)
Bases: HttpClientBase
Talks to ANAF e-Transport over OAuth2.
Construct with an authenticated :class:~anafpy.auth.provider.TokenProvider; the
client owns an httpx2.AsyncClient (unless one is injected — it must then
carry :class:~anafpy.auth.oauth.AnafAuth and a non-empty base_url;
an empty one raises :class:~anafpy.exceptions.AnafConfigError, since
injected clients are never mutated) and should
be used as an async context manager so it is closed cleanly.
upload
async
¶
upload(xml: str | bytes, *, cif: str, version: int = 2) -> UploadResult
Submit a transport declaration XML for processing.
Returns an :class:UploadResult whose upload_id feeds get_status and
uit is the transport declaration code returned on acceptance. A document
rejected at submission comes back with accepted is False and errors
populated — not as an exception.
upload_document
async
¶
upload_document(document: FlatSubmission, *, cif: str, version: int = 2) -> UploadResult
Compose a flat e-Transport document and file it — no XML handling needed.
Accepts any of the four flat documents (a :class:FlatTransport
declaration/correction, :class:FlatDeletion, :class:FlatConfirmation, or
:class:FlatVehicleChange from :mod:anafpy.etransport.models), renders it
to the ANAF declaration XML with cod_declarant taken from cif (unless
the document sets declarant_code itself), and uploads it. One call, one
result-or-raise, same as :meth:upload.
get_status
async
¶
get_status(upload_id: str) -> MessageStatus
Poll the processing state for an upload_id (index_incarcare).
list_notifications ¶
list_notifications(*, days: int, cif: str) -> AsyncIterator[Notification]
Iterate transport notifications from the last days (1-60) for cif.
Yields each :class:Notification; an empty window yields nothing. The
lista endpoint is not paginated, so this is a single request under the hood.
Consume with async for. Raises :class:AnafConfigError for a bad days
(eagerly), and :class:AnafResponseError if ANAF reports a genuine list error
(a benign "no notifications" note yields an empty iterator instead).
info
async
¶
info(*, organizer_cui: str, declarant_cui: str | None = None, uit: str | None = None, declarant_ref: str | None = None) -> InfoList
Look up active notifications where organizer_cui (ANAF: cui_op)
is the transport organizer.
A benign "no results" note comes back as an empty :class:InfoList with
error carrying ANAF's wording; a genuine query error (missing SPV
rights, daily limit, unknown CUI) raises :class:AnafResponseError —
the same split the list endpoints apply.
upload_and_wait
async
¶
upload_and_wait(xml: str | bytes, *, cif: str, version: int = 2, timeout: float = 300.0, initial_wait: float = 2.0, max_wait: float = 30.0) -> MessageStatus
Upload, then poll get_status until a terminal state or timeout.
Raises :class:TimeoutError if the declaration is still processing when the
budget is exhausted. A nok or rejected result is returned, not raised.
anafpy.etransport.models ¶
Value types returned by :class:anafpy.etransport.client.ETransportClient.
Key differences from e-Factura:
- Responses are JSON, not e-Factura's XML <header> (per the vendored swagger
specs); the private _*Envelope models validate the wire shapes.
- UploadResult carries both upload_id (index_incarcare) and uit
(the transport declaration code returned at upload time — no separate download step).
- MessageStatus has no download_id; the UIT is already in UploadResult.
- Notification mirrors the richer JSON returned by lista/{zile}/{cif}, and
InfoList / InfoItem cover the transporter-lookup endpoint; both read ANAF's
terse wire names via aliases and expose descriptive field names instead.
- The flat models are bidirectional — like e-Factura's authoring models, the
Flat* shapes here both view a parsed declaration
(:func:read_flat_transport) and author one (:func:build_etransport /
:func:render_etransport): e-Transport has no upstream authoring software the way
invoicing does, and ANAF's XSD is small and fully enumerated, so anafpy translates
the whole schema instead of passing XML through.
FlatSubmission ¶
FlatSubmission = FlatTransport | FlatDeletion | FlatConfirmation | FlatVehicleChange
The four documents that can be filed with e-Transport.
MessageState ¶
Bases: StrEnum
Terminal/non-terminal states reported by stareMesaj.
UploadResult ¶
Bases: BaseModel
Outcome of POST /upload/ETRANSP/{cif}/{versiune}.
upload_id (index_incarcare) feeds get_status; uit is the transport
declaration code available immediately on acceptance; there is no separate download.
MessageStatus ¶
Bases: BaseModel
Outcome of GET stareMesaj/{id_incarcare}.
NotificationMessage ¶
Bases: _AliasedReadModel
One entry in a notification's mesaje array.
Notification ¶
Bases: _AliasedReadModel
One entry from GET lista/{zile}/{cif}.
Field names follow the flat-model vocabulary (operation_type, carrier_*,
plate, ...); ANAF's wire names remain as aliases.
Location ¶
Bases: _AliasedReadModel
A loc_start or loc_final from an info record.
InfoItem ¶
Bases: _AliasedReadModel
One record from GET info?cui_op=....
InfoList ¶
Bases: BaseModel
Response from GET info?cui_op=....
error carries ANAF's benign "no results" note (with items empty);
genuine query errors are raised by the client, never returned here.
FlatTransportPartner ¶
Bases: BaseModel
Commercial partner of the transport.
FlatTransportVehicle ¶
Bases: BaseModel
Vehicle, carrier, and transport-date details.
FlatTransportAddress ¶
Bases: BaseModel
A national address at one end of the road route.
FlatTransportLocation ¶
Bases: BaseModel
One end of the road route: exactly one of border_point (PTF),
customs_office (BV), or a national address.
Which one ANAF expects depends on the operation type: a domestic TTN uses
addresses at both ends; inbound operations (AIC/LHI/SCI/IMP/DIN) start at a
border point or customs office, outbound ones (LIC/LHE/SCE/EXP/DIE) end at
one; a customs office is only valid at the start of an import (IMP) or the
end of an export (EXP). ANAF validates all of that on upload.
FlatTransportGood ¶
Bases: BaseModel
One transported-goods line.
FlatTransportDocument ¶
Bases: BaseModel
A transport document reference (CMR, invoice, ...).
FlatPriorNotification ¶
Bases: BaseModel
A reference to a prior declaration (notificareAnterioara).
FlatTransport ¶
Bases: _FlatSubmissionBase
An e-Transport declaration in an easy-to-read shape.
The same model authors a declaration (see :func:build_etransport /
:func:render_etransport, or the client's upload_document) and views a
parsed one (:func:read_flat_transport). Set correction_of_uit to file it
as a correction of an already-issued UIT.
FlatDeletion ¶
Bases: _FlatSubmissionBase
Deletion (stergere) of an issued UIT.
FlatConfirmation ¶
Bases: _FlatSubmissionBase
Confirmation (confirmare) of an issued UIT.
FlatVehicleChange ¶
Bases: _FlatSubmissionBase
Vehicle change (modifVehicul) on an issued UIT.
parse_uit_expiry ¶
parse_uit_expiry(value: str) -> date
Parse ANAF's data_exp_uit in every shape the endpoint emits.
The info swagger's own example returns it as a timestamp
("2024-06-29T00:00:00"), so the value a caller copies out of a lookup is
not a bare ISO date — accepting only YYYY-MM-DD would reject ANAF's own
documented output. Compact YYYYMMDD is taken too, the form ANAF uses for
dates elsewhere on this service.
Raises:
| Type | Description |
|---|---|
AnafConfigError
|
when the value matches none of the three shapes. |
parse_etransport_document ¶
parse_etransport_document(xml: bytes) -> ETransport | None
Parse e-Transport wire XML into its :class:ETransport model, or None when
the bytes are not a parseable declaration. Never raises on bad input.
read_flat_transport ¶
read_flat_transport(doc: ETransport) -> FlatSubmission
Translate a parsed :class:ETransport document to its flat model.
Full translation: every XSD field maps onto the flat shape (only the schema's
unused xs:any extension hooks are not carried). Raises :class:ValueError
(or a pydantic ValidationError) for a document that carries none of the four
operations or otherwise cannot be represented.
build_etransport ¶
build_etransport(document: FlatSubmission, *, declarant_code: str | None = None) -> ETransport
Compose a flat document into the :class:ETransport wire model.
cod_declarant comes from the model's declarant_code or the
declarant_code argument (typically the upload CIF); setting both to
different values raises :class:AnafConfigError, as does setting neither.
render_etransport ¶
render_etransport(document: FlatSubmission, *, declarant_code: str | None = None) -> bytes
Compose a flat document and serialize it to upload-ready UTF-8 XML bytes.
anafpy.etransport.card ¶
UIT presentation artifacts: the driver card and the detail document.
An e-Transport 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 this, so :class:UitCard gathers what a filing already knows and
:mod:anafpy.etransport.cardpdf renders two PDFs from it:
- the card, one page at a phone's own aspect ratio (90x195mm), 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, A4, carrying the whole filing for the partner company or the caller's own file.
Both are informative: they are generated locally, are not issued by ANAF,
and say so on their face. Both also print a validity, from one of two places:
ANAF's data_exp_uit when the caller has one, and otherwise the statutory
window of :mod:anafpy.etransport.validity — resolved in one place,
:meth:UitCard.validity, and rendered so the two can never be confused.
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 observed on the cards Romanian invoicing software already produces.
Rendering needs the anafpy[cards] extra; :func:load_cardpdf reports its
absence as an :class:~anafpy.exceptions.AnafConfigError rather than an
ImportError. :meth:UitCard.summary_text needs nothing and is the paste-
into-a-chat fallback for phones whose PDF viewer will not select text.
UitValidity ¶
Bases: BaseModel
A UIT's window, resolved — and, as importantly, where it came from.
source is what keeps the two apart: "anaf" when data_exp_uit was
read back from the info endpoint, "statutory" when it was derived
from the declared transport date because ANAF discloses that date only to
the transport organizer. A derived window carries days — the count
art. 11 gives that operation — so a caller can say how it was arrived at
instead of presenting it as fact.
UitCard ¶
Bases: BaseModel
A filing plus the identifiers ANAF returned for it, ready to render.
transport is the declaration itself; everything else is what the upload
and a later info lookup add. The optional fields are genuinely optional:
a card rendered straight after upload has no uit_expiry yet, and most
never get one — ANAF serves data_exp_uit only to the transport
organizer. :meth:validity is what fills the gap, from the statute rather
than from ANAF, and says so.
last_valid_day
property
¶
last_valid_day: date | None
The last day the UIT may still be used, or None when unknown.
ANAF's data_exp_uit is defined as "data incepand cu care UIT-ul este
considerat expirat" — the first expired day, not the last valid one
(API PDF p. 4; the info swagger pairs data_transp 2024-06-24 with
data_exp_uit 2024-06-29, i.e. the 5 calendar days of OUG 41/2022
art. 11 counted from the transport date). Printing it under "valabil
până la" would vouch for a day on which use is a contravention.
from_upload
classmethod
¶
from_upload(transport: FlatTransport, result: UploadResult, *, uit_expiry: date | None = None, declarant_name: str | None = None, declarant_code: str | None = None, filed_on: date | None = None, anaf_state: str | None = None, notes: list[str] | None = None) -> UitCard
Build from an accepted upload, carrying its UIT and upload index.
uit_expiry is not among them by accident: upload does not report
it, and only a later info lookup does.
validity ¶
validity(today: date | None = None) -> UitValidity
The window to show, and where its dates come from.
ANAF's data_exp_uit wins whenever it was reported; otherwise the
statutory window of :mod:anafpy.etransport.validity is derived from
the declared transport date. So there is always a window — what varies
is :attr:UitValidity.source, and everything that renders one is
expected to say which it is looking at.
is_expired ¶
is_expired(today: date | None = None) -> bool
Whether the UIT's validity has lapsed, by the best date available.
Expiry is inclusive of the first expired day itself — on that very date
the UIT already counts as expired. With no uit_expiry reported this
reads the statutory window rather than answering False: ANAF not
disclosing a date to a declarant who is not the transport organizer is
not evidence that the UIT still lives.
summary_text ¶
summary_text(today: date | None = None) -> str
The card's facts as plain text, for pasting into a chat message.
The UIT is alone on the first line so that copying the whole block, or just its first line, both yield a usable code. This is the fallback for viewers that will not select text in a PDF.
A derived window says so in the line itself and again in a closing note naming the rule — the reader has to be able to tell the estimate from ANAF's own date without knowing anafpy exists.
CardRenderModule ¶
Bases: Protocol
Typed shape of the optional :mod:anafpy.etransport.cardpdf module.
partner_label ¶
partner_label(operation: CodTipOperatiuneType) -> str
Furnizor / Client / Partener, by transport direction.
load_cardpdf ¶
load_cardpdf() -> CardRenderModule
Load the optional fpdf2-backed renderer with an install hint.
anafpy.etransport.validity ¶
The UIT's statutory validity window — the law's default, not ANAF's word.
ANAF reports the real end of a UIT's window as data_exp_uit, but only on the
info endpoint, which it scopes to the transport organizer (cui_op).
A declarant who is not also the carrier therefore never reads it back: the
lookup answers "Nu exista informatii pentru aceasta solicitare" and the card
handed to the driver would carry no validity at all.
The window is not ANAF's discretion, though. OUG 41/2022 art. 11 fixes it at
5 calendar days counted from the declared transport date, 15 for the
operations listed in :data:_EXTENDED, and ANAF applies it mechanically — its
own info example pairs data_transp 2024-06-24 with data_exp_uit
2024-06-29 (docs/anaf-reference/etransport/api.md §4). So the window can be
derived from a filing anafpy already holds. What must never happen is the
derived date being passed off as ANAF's: it is labelled as an estimate wherever
it is shown, and :attr:~anafpy.etransport.card.UitCard.uit_expiry keeps
holding ANAF's value alone (:class:~anafpy.etransport.card.UitValidity is
where the two meet, and ANAF always wins).
Counting, in ANAF's own terms: the transport date is day 1, so the last valid
day is transport_date + days - 1 and the first expired day —
data_exp_uit's definition — is transport_date + days.
Where the sources leave room for doubt the shorter window is encoded. The two errors are not symmetric: an estimate that runs long puts a driver on the road with a lapsed UIT, which is a contravention (fines 20,000-100,000 RON), while one that runs short makes a card announce itself early and prompts a check. Only the first is dangerous.
statutory_validity_days ¶
statutory_validity_days(operation: CodTipOperatiuneType) -> int
How many calendar days art. 11 gives a UIT for operation.
statutory_last_valid_day ¶
statutory_last_valid_day(operation: CodTipOperatiuneType, transport_date: date) -> date
The last day the UIT may still be used, counting the transport date in.
statutory_first_expired_day ¶
statutory_first_expired_day(operation: CodTipOperatiuneType, transport_date: date) -> date
The day the UIT counts as expired — the shape ANAF's data_exp_uit has.