Skip to content

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

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.