Skip to content

e-Factura API

The client and its value types. The generated UBL 2.1 / CIUS-RO models (anafpy.efactura.Invoice, CreditNote, and the anafpy.efactura.ubl package) are intentionally not documented here — they mirror the UBL schema one-to-one.

anafpy.efactura.client

Async client for the RO e-Factura web services (FCTEL/rest).

Design (see DESIGN.md §4): discrete 1:1 methods are the primary surface and do no transport retry — a single call, one result-or-raise — so a non-idempotent upload is never silently repeated. upload_and_wait is the only place that loops, polling the processing state with tenacity. HTTP/auth failures raise; business outcomes (nok, upload rejections) are returned as values.

Outbound documents arrive two ways: upload takes complete UBL XML exported by the caller's invoicing software (the strongly recommended path when such software exists — ANAF's SPV purges filed messages after ~60 days, so the upstream system is what keeps the durable record), and upload_invoice composes one from the flat authoring models (:mod:anafpy.efactura.authoring) for callers with no upstream system, who then own archiving the signed downloads.

The stateless document services validare and transformare are public, no-auth, prod-only and live on :class:anafpy.public.client.PublicClient (validate_invoice / render_invoice_pdf); only the MF signature check (validate_signature, on the OAuth host) stays here.

EFacturaClient

EFacturaClient(provider: TokenProvider, *, environment: Environment = PROD, http: AsyncClient | None = None, timeout: float = 60.0)

Bases: HttpClientBase

Talks to ANAF e-Factura over OAuth2.

Construct with an authenticated :class:~anafpy.auth.provider.TokenProvider; the client owns an httpx.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, standard: UploadStandard = UBL, extern: bool = False, autofactura: bool = False, executare: bool = False, b2c: bool = False) -> UploadResult

Submit an invoice/credit-note/message XML for processing.

Returns an :class:UploadResult whose upload_id feeds get_status. A document rejected at submission comes back with accepted is False and errors populated — not as an exception.

upload_invoice async

upload_invoice(document: InvoiceDocument, *, cif: str, skip_validation: bool = False, **upload_kwargs: bool) -> UploadResult

Compose an authored :class:~anafpy.efactura.authoring.InvoiceDocument and submit it — filing without upstream invoicing software.

Renders the flat document to CIUS-RO UBL (totals and the VAT breakdown are computed unless supplied explicitly) and uploads it with the standard matching :attr:~anafpy.efactura.authoring.InvoiceDocument.kind (UBL for an invoice, CN for a credit note). The translated EN 16931 + CIUS-RO rule set runs first and raises :class:~anafpy.efactura.authoring.InvoiceValidationError on fatal findings; pass skip_validation=True to let ANAF be the only judge. Callers with ready-made XML from their invoicing software keep using :meth:upload — that path stays the recommendation when an upstream system exists.

Extra boolean flags (autofactura, extern, executare, b2c) pass through to :meth:upload.

get_status async

get_status(upload_id: str) -> MessageStatus

Poll the processing state for an upload_id (index_incarcare).

download async

download(message_id: str) -> DownloadedMessage

Download the ZIP (signed invoice/errors + MF signature) for a message id.

Raises:

Type Description
AnafResponseError

ANAF answered 200 with a non-ZIP body (it reports e.g. an unknown id as an error payload, not an HTTP error).

list_messages

list_messages(*, cif: str, days: int | None = None, start: datetime | None = None, end: datetime | None = None, filter: Filter | None = None) -> AsyncIterator[MessageListItem]

Iterate every e-Factura message in a window, paging under the hood.

Specify the window with either days (1-60) or both start and end (datetimes; a naive one is interpreted in the machine's local timezone — pass timezone-aware values to be explicit). Either way the window must lie within the last 60 days — ANAF retains e-Factura messages for 60 days and rejects older start values — and end may be neither before start nor in the future; violations raise :class:AnafConfigError locally, before any request. Yields each :class:MessageListItem across all pages of listaMesajePaginatieFactura; an empty window yields nothing.

Consume with async for; materialise via [m async for m in client.list_messages(...)].

Raises:

Type Description
AnafConfigError

the window arguments are invalid (raised eagerly).

AnafResponseError

ANAF reported a genuine list error (bad CIF/interval, …); a benign "no messages" note yields an empty iterator instead.

AnafRateLimitError / AnafTransportError

as for any request.

validate_signature async

validate_signature(file: str | bytes, signature: str | bytes) -> SignatureValidationResult

Validate the MF detached signature over an invoice XML.

Both files come from the descarcare ZIP (:attr:DownloadedMessage.content_xml / signature_xml). The endpoint lives at the host root (/api/validate/signature) — outside the FCTEL/rest prefix and with no test/prod segment — so it ignores this client's environment. A failed validation is returned as valid=False, not raised.

upload_and_wait async

upload_and_wait(xml: str | bytes, *, cif: str, standard: UploadStandard = UBL, timeout: float = 300.0, initial_wait: float = 2.0, max_wait: float = 30.0, **upload_kwargs: bool) -> MessageStatus

Upload, then poll get_status until a terminal state or timeout.

Raises :class:TimeoutError if the document is still processing when the budget is exhausted. A nok result is returned, not raised.

anafpy.efactura.models

Value types returned by :class:anafpy.efactura.client.EFacturaClient.

These are deliberately small, transport-facing types. Business outcomes (an upload rejected at submission, a nok status) are represented here as values, never exceptions — see :mod:anafpy.exceptions for the error half of the hybrid model.

The richer document models (Invoice/CreditNote) are the generated UBL types; DownloadedMessage preserves the raw signed bytes and parses them lazily — its view tier is the flat :class:~anafpy.efactura.authoring.InvoiceDocument from :mod:anafpy.efactura.authoring.

UploadStandard

Bases: StrEnum

standard query param for /upload (the document kind being submitted).

Filter

Bases: StrEnum

filtru query param for the message-list endpoints.

MessageState

Bases: StrEnum

Terminal/non-terminal states reported by stareMesaj.

UploadResult

Bases: BaseModel

Outcome of /upload.

upload_id (index_incarcare) is set when ANAF accepted the document for processing; errors carries any messages when it was rejected at submission.

MessageStatus

Bases: BaseModel

Outcome of stareMesaj.

SignatureValidationResult

Bases: BaseModel

Outcome of POST /api/validate/signature (MF signature over an invoice).

Both outcomes are HTTP 200 with a prose msg; valid reflects which of the two documented wordings ANAF answered with. A failed validation is a business outcome — never an exception.

MessageListItem

Bases: BaseModel

One entry from a message-list response.

Descriptive field names, with ANAF's wire names kept as validation aliases (data_creare -> created_at, ...); values are read verbatim. populate_by_name lets callers also construct by the field names.

sender_cif/receiver_cif are documented by ANAF's API PDF but never sent on the wire; when absent they are extracted (best-effort) from the details prose, staying None for wordings that carry no CIFs (ERORI FACTURA, buyer messages).

DownloadedMessage

Bases: BaseModel

A descarcare ZIP, with raw bytes preserved.

Three read tiers, cheapest first: raw_zip / content_xml (the legally meaningful, archived bytes), document (the full generated UBL model), and view (the flat :class:~anafpy.efactura.authoring.InvoiceDocument projection — full-fidelity, and renderable back via the authoring package). The latter two parse lazily; tier 1 stays authoritative.

document cached property

document: Invoice | CreditNote | None

Parse the content member into its UBL model, or None if it is not a UBL document (e.g. a nok errors file).

view cached property

view: InvoiceDocument | None

The flat :class:~anafpy.efactura.authoring.InvoiceDocument projection of :attr:document.

None when the content is not a parseable UBL invoice/credit-note (a nok errors file, a buyer message) or when the strict authoring reader cannot represent it — never an exception. Every inbox document passed ANAF's validation at filing, whose rules the authoring models mirror, so a None on parseable UBL signals rule drift: fall back to :attr:document / :attr:content_xml and consider re-vendoring the CIUS-RO code lists.

parse_ubl_document

parse_ubl_document(xml: bytes) -> Invoice | CreditNote | None

Parse e-Factura wire XML into its UBL model.

Returns an :class:Invoice or :class:CreditNote, or None when the bytes are not a parseable UBL document (e.g. a nok errors file). Never raises.