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.