e-Factura¶
EFacturaClient covers Romania's electronic-invoicing service: filing invoices
(as ready-made XML or composed from the authoring models),
tracking processing, listing the message inbox, and downloading documents.
Two outbound paths¶
Pass-through is the strongly recommended path when you run invoicing software: bring the complete UBL XML your system exported and anafpy moves it — validate, file, track, download — without ever re-composing it. Your invoicing system's document is authoritative; re-deriving it could only add drift. Just as important, ANAF's SPV is not invoice storage: it purges filed messages after ~60 days, so the durable record must live in a system you own — which an invoicing system gives you for free.
Structured authoring is the first-class alternative when there is no
upstream system: the anafpy.efactura.authoring models compose
a complete CIUS-RO invoice or credit note from business fields — totals and the
VAT breakdown computed for you — and upload_invoice files it in one call.
Archiving the signed documents is then on you: download each filing's ZIP and
keep it, because after the retention window ANAF won't have it either.
The generated UBL 2.1 / CIUS-RO models are available if you want typed access to a parsed document:
from anafpy.efactura import Invoice, CreditNote, parse_ubl_document
Filing and tracking¶
async with EFacturaClient(provider) as efactura:
result = await efactura.upload(invoice_xml, cif="RO12345678")
if result.accepted:
status = await efactura.get_status(result.upload_id)
uploadfiles the XML and returns anUploadResult— a rejection (BR-RO findings and all) comes back asaccepted is Falsewith the findings attached, not as an exception (see the error model).upload_invoicetakes an authoredInvoiceDocumentinstead of XML: it renders the document (running the local rule set first —skip_validation=Trueopts out) and uploads with the rightstandardfor an invoice or credit note.get_statusreports the processing state (MessageStatus).upload_and_waitcombines upload and polling: it pollsget_statusuntil the message leaves the "processing" state, retrying only on that business state — never on transport errors.
The inbox: list_messages¶
async for message in efactura.list_messages(cif="RO12345678", days=30):
print(message.message_id, message.message_type, message.details)
A single async iterator that pages ANAF's message list under the hood — window by
days or start/end. An empty window yields an empty iterator; a genuine
ANAF error raises AnafResponseError (ANAF overloads the same response note
for both cases; anafpy classifies them).
Clock skew is handled, so days=60 is safe. ANAF checks both ends of the
window against its own clock — end may not be in the future, start may not
predate its 60-day floor — and a full 60-day window sits on both limits at once,
where a one-second disagreement between your machine's clock and ANAF's fails the
whole listing. The days path therefore builds the window a short margin inside
each limit (5 minutes at the start, 60 seconds at the end — so the listing trails
the very newest filings by a minute, well under ANAF's own indexing lag). Larger
drift is corrected from ANAF itself: its rejection quotes its request moment, and
the window is rebuilt on that clock and retried once. An explicit
start/end window is never moved by the margins — you named those instants —
but the same one-shot correction applies to it.
Note a production-confirmed quirk (2026-07-06): the list's cif_emitent /
cif_beneficiar fields are never actually emitted by ANAF despite being
documented — partner CIFs ride only inside the free-text detalii. See the
e-Factura reference.
Downloading: three read tiers¶
download returns a DownloadedMessage exposing the same document at three
levels:
- Raw signed bytes — ANAF's ZIP archive, the authoritative artifact.
- The full UBL model — the parsed, typed document.
InvoiceDocument— the flat view (DownloadedMessage.view): parties, lines, totals, references, projected from the UBL by the authoring reader.
The view is full-fidelity and renderable back — the same model you author
with, so drafting a credit note from a received invoice is one step away. The
reader never re-judges a document ANAF already accepted, so the shapes real
inboxes are full of — negative amounts on a storno, empty optional elements, a
typo in a contact phone number — read cleanly rather than costing you the
invoice; run validate() on the result to judge it.
When the content is not a representable invoice (a rejection-errors file, a
buyer message, or a code off one of the closed lists), view is None — never
an exception — and the raw bytes plus the UBL model stay authoritative. That
second case is never silent: view_error carries the cause and a UserWarning
is emitted, so "unreadable" cannot be mistaken for "empty".
A message stays downloadable for 60 days, and the boundary is real: ANAF
lists messages descarcare then refuses to hand over, so any 60-day lookback
eventually asks for one. That refusal raises AnafDownloadExpiredError — a
terminal verdict you can act on directly instead of parsing Romanian prose (see
the error model). It is one more reason
the durable archive lives on your side, not in the SPV.
validate_signature checks the Ministry of Finance signature over a downloaded
archive.
Validation and PDF rendering¶
ANAF's stateless document services — validare (authoritative server-side
validation, no filing) and transformare (the official PDF rendering) — are
public, no-auth, and prod-only on ANAF's side, so they live on
PublicClient as validate_invoice and render_invoice_pdf, not
on EFacturaClient. Use them freely: they work with no OAuth credentials
configured at all.
ANAF's validator is authoritative by definition. The
authoring models additionally run a translated EN 16931 +
CIUS-RO rule set locally (validate(), findings with the official BR-* rule
ids) for fast feedback while composing — a clean local report is a strong
signal, never a guarantee.