Skip to main content

Typed Python clients for Romania's ANAF web services: e-Factura, e-Transport, and the public no-auth registries.

Project description

anafpy — Python client for ANAF e-Factura & e-Transport

CI Coverage PyPI version Python versions Docs License

anafpy

Typed Python clients for Romania's ANAF tax-authority web services — e-Factura (electronic invoicing), e-Transport (goods transport), and the public no-auth registries (VAT/taxpayer lookups, financial statements) — plus a local MCP server that exposes them as Claude Cowork skills.

anafpy is a thin transport client — no persistence, no accounting logic. For e-Factura there are two ways out: if you run invoicing software, bring the invoice XML it produced and anafpy validates it, files it with ANAF, tracks status, and pulls documents back (the strongly recommended path — your system's XML is never re-composed); if you don't, the invoice authoring models compose a complete CIUS-RO invoice or credit note from plain business fields — totals and the VAT breakdown computed for you, checked against a translated EN 16931 + CIUS-RO rule set before filing. Either way, remember that ANAF's SPV is not invoice storage — it purges filed messages after ~60 days — so your durable record must live on your side: your invoicing system's ledger, or, when you author with anafpy, the signed ZIPs you download and keep. Documents you read back (your filings and invoices suppliers issued to you) come wrapped in a friendly flat read view for easy display. e-Transport is fully translated too: you author declarations, UIT deletions, confirmations, and vehicle changes from structured fields, no XML handling needed, and the same models render what you read back.

Documentation: anafpy.readthedocs.io — the end-user setup walkthrough, the library guides, and the API reference.

What can you do with this?

With the MCP server connected to a Claude client (Claude Desktop, Claude Code), an accountant in Romania can ask Claude to:

Check partners and public data — no login required (these ride ANAF's public, no-auth services):

  • Verify a business partner by CUI/CIF — name, address, VAT status (plătitor de TVA), TVA la încasare, split-VAT, inactive flag — one call, in bulk if you like.
  • Check whether a partner is enrolled in RO e-Factura.
  • Look up the farmers' register (RegAgric) and religious-entities register (RegCult).
  • Pull a company's filed financial statements (bilanț) for a given year.
  • Validate an invoice XML against ANAF's authoritative server-side validare (CIUS-RO / BR-RO rules) — no filing.

Work your e-Factura inbox and file invoices (needs the certificate login):

  • List received and sent invoice messages for a date window.
  • Download an invoice as an easy-to-read view, and save the official signed ZIP and/or a rendered PDF to disk — powering batch flows like "export last month's invoices as <date> - <partner>.pdf".
  • File an invoice or credit note — from the XML your invoicing software exported (recommended when you have one), or composed by Claude from plain business fields when you don't. Either way filing is two-step gated: you see a preview and nothing reaches ANAF until you explicitly confirm.

Declare goods transport in e-Transport — with a confirmation step (needs the login):

  • File a declaration and get a UIT code from transport data in any source — an email, a PDF invoice, a CMR, a spreadsheet — and correct, delete, confirm, or change the vehicle on an existing one.
  • List recent notifications, check an upload's status, and look up active declarations / UIT codes.
  • Filing is two-step gated: Claude shows you a preview, and nothing is submitted to ANAF until you explicitly confirm.

Read your SPV mailbox and pull official reports (needs the certificate — read-only):

  • Check what arrived in SPV — receipts (recipise), payment notices, decisions, notifications — filtered by company and message kind, and save any document's PDF to a folder you name.
  • Request official reports and wait for them: fiscal vector (VECTOR FISCAL), outstanding obligations (Obligatii de plata), filing history (Istoric declaratii), declaration duplicates (D100/D112/D300/D390/D394...), receipt duplicates, income certificates (Adeverinte Venit), D112↔REVISAL mismatches — ANAF generates them asynchronously and Claude fetches the PDF when it lands.
  • See exactly which companies your certificate can query — SPV reports the authorization inventory on every call.

Setup caveats worth knowing: the e-Factura and e-Transport tools need a one-time login with your qualified digital certificate (the same one you use on ANAF's SPV) — the public checks above work without it. The server runs locally on your own machine, so downloaded invoices and PDFs land on your own filesystem. See the setup walkthrough for the full Claude Desktop + ANAF setup — also available in Romanian.

Status: early / alpha (0.x), on PyPI as anafpy. The OAuth2 auth layer, both async clients, the bidirectional invoice-authoring models, and the MCP server (two-step gated filing for both services, inbox, download, validate) are implemented and tested, as is the read-only SPV layer (certificate-mTLS mailbox: messages, downloads, report requests). ANAF's own server-side validare stays the authoritative validator; the authoring models add a local translated rule check for fast feedback. See DESIGN.md for the full design and docs/anaf-reference/ for a compiled local reference of ANAF's APIs.

Requires Python 3.12+. Built on httpx and Pydantic v2.

What works today

  • OAuth2 auth layer — Authorization-Code bootstrap (browser + qualified certificate), local token store, and headless refresh, exposed via the anafpy CLI and an httpx.Auth integration for the clients.
  • EFacturaClient (async) — upload (ready-made XML) and upload_invoice (an authored InvoiceDocument), get_status, download, validate_signature (checks the MF signature over a downloaded invoice), the upload_and_wait poll-until-terminal helper, and list_messages — a single async iterator that pages the message list under the hood (window by days or start/end; empty window → empty iterator, real ANAF errors → raise). download exposes three read tiers: raw signed bytes, the full UBL model, and the flat InvoiceDocument view (the same authoring model, read full-fidelity from the wire).
  • Invoice authoring (anafpy.efactura.authoring) — bidirectional CIUS-RO models: one InvoiceDocument covers invoice and credit note, totals and the VAT breakdown are computed from the lines (explicit values preserved), and a hand-translated EN 16931 + CIUS-RO rule set (validate()) reports findings with the official BR-* ids before anything is filed. render_invoice emits upload-ready XML; parse_invoice/read_invoice map wire XML back into the same models with byte-stable round-trips.
  • ETransportClient (async) — upload, get_status, info, upload_and_wait, list_notifications (same async-iterator shape), and upload_document, which composes and files any of the four flat documents — a FlatTransport declaration/correction, FlatDeletion, FlatConfirmation, or FlatVehicleChange — without the caller touching XML.
  • PublicClient (async) — ANAF's unauthenticated public services on webservicesp.anaf.ro: lookup_taxpayers (VAT registration, VAT-on-collection, inactive, split-VAT, and RO e-Factura register membership in one call), lookup_efactura_register, lookup_farmers, lookup_cult_entities, get_financial_statement (public bilanț indicators) — plus the stateless e-Factura document services: validate_invoice (ANAF's authoritative server-side validation, no filing) and render_invoice_pdf (the official transformare PDF rendering); both are prod-only on ANAF's side and need no login. No credentials, no test/prod split; requests are paced client-side at ANAF's stated 1 req/s rule.
  • SpvClient (async, read-only) — the taxpayer's SPV mailbox over the qualified certificate: list_messages (with the certificate's authorization inventory), download_document (PDF), request_report (the full cerere nomenclature — 35 report types with per-type parameter validation before any wire call), and wait_for_report. The certificate step is one interactive anafpy spv login (macOS Keychain/SecureTransport or Windows CertStore/Schannel via the OS-shipped curl — the keys are non-exportable, so Python's TLS stack never touches them); everything after rides an APM cookie session, prompt-free.
  • Flat models — the invoice authoring models above double as the inbox read view (DownloadedMessage.view), so what you download and what you author are the same shape. The e-Transport flat models are bidirectional: read_flat_transport views a parsed document and build_etransport / render_etransport author one — full translation of ANAF's XSD, with enum-coded fields (counties, border points, customs offices, operation types...) accepted by name or code.
  • Generated models — UBL 2.1 / CIUS-RO (from anafpy.efactura import Invoice, CreditNote) and the proprietary e-Transport XSD, generated from vendored schemas.
  • MCP server (anafpy[mcp]) — a local stdio connector exposing the operations as Cowork skills: read-first, with two-step gated filing for both services — e-Factura invoices (ready-made XML or composed from structured fields) and e-Transport declarations (see below).

A sync facade was dropped as a goal — the clients are async-only.

Install

Setting up a fresh machine end to end — ANAF app registration, the certificate login, and the Claude / Cowork configuration, written for a non-developer — follow the setup walkthrough. The short version for developers:

From PyPI:

pip install anafpy        # or: uv add anafpy
pip install 'anafpy[mcp]' # with the MCP server

The distribution offers one extra: anafpy[mcp] (the MCP server).

For the MCP server, prefer running from a checkout (as the setup walkthrough does): the compiled ANAF reference (docs/anaf-reference/, served as MCP resources) and the workflow skills (skills/, served as MCP prompts) live in the repo, not in the wheel. A PyPI-installed server runs fine but serves neither unless ANAFPY_DOCS_DIR / ANAFPY_SKILLS_DIR point at copies. From source:

git clone https://github.com/robert-malai/anafpy && cd anafpy
uv sync --all-extras

Authentication

ANAF uses OAuth2 (Authorization Code) gated by a qualified digital certificate. The one-time, interactive bootstrap runs on your machine (the cert lives there):

anafpy auth login --client-id <ID> --client-secret <SECRET> \
                  --redirect-uri https://localhost:9002/callback --paste
anafpy auth status        # show stored token validity
anafpy auth logout        # remove the stored tokens (signs this machine out)

This opens your browser for the certificate step, captures the authorization code (pasted, or via a local TLS listener), exchanges it for tokens, and stores them in the OS credential store (macOS Keychain, Windows Credential Manager, Linux Secret Service/KWallet — the default backend; a JSON-file backend is the opt-out for Docker/headless hosts). Tokens then refresh headlessly for ~a year (access token 90 days, refresh token 365 days), so the cert is needed only about once a year. The authentication guide covers the capture modes, token storage, and signing out.

Usage

The clients are async and used as context managers. Build a TokenProvider over your stored tokens, then call discrete operations:

from anafpy.auth import KeyringTokenStore, TokenProvider
from anafpy.efactura import EFacturaClient

provider = TokenProvider(
    client_id="<ID>",
    client_secret="<SECRET>",
    store=KeyringTokenStore(),  # OS credential store (the default backend)
    # or FileTokenStore("~/.anafpy/tokens.json") for headless/Docker hosts
)

async with EFacturaClient(provider) as efactura:
    result = await efactura.upload(invoice_xml, cif="RO12345678")
    status = await efactura.get_status(result.upload_id)
    # or, in one call: status = await efactura.upload_and_wait(invoice_xml, cif=...)

Discrete methods make a single call (no transport retry). HTTP/auth problems raise AnafError subclasses; business outcomes (a nok rejection, BR-RO findings) come back as typed values, not exceptions. On HTTP 429 the client raises AnafRateLimitError exposing retry_after rather than backing off itself.

e-Transport declarations are authored from typed models — no XML in sight. A FlatTransport holds the partner, vehicle, route, goods, and documents as structured fields (enum-coded values accepted by ANAF code or by name), and upload_document renders and files it in one step:

from anafpy.etransport import ETransportClient, FlatDeletion, FlatTransport

declaration = FlatTransport(...)  # full example in the e-Transport guide

async with ETransportClient(provider) as etransport:
    result = await etransport.upload_document(declaration, cif="12345678")
    print(result.uit)  # the UIT code, issued at upload time
    # later: delete / confirm / change vehicle on that UIT the same way, e.g.
    await etransport.upload_document(FlatDeletion(uit=result.uit), cif="12345678")

The e-Transport guide has the complete worked declaration.

The public registries need no auth at all:

from anafpy.public import PublicClient

async with PublicClient() as public:
    lookup = await public.lookup_taxpayers(["RO12345678"])
    if lookup.found:
        record = lookup.found[0]
        print(record.name, record.vat_registered, record.efactura_registered)

Reading SPV takes one interactive login (anafpy spv certsanafpy spv select <thumbprint>anafpy spv login, which fires your token PIN / 2FA once), then:

from anafpy.spv import (
    FileSessionStore, ReportRequest, ReportType, SpvClient, SpvSessionProvider,
)

async with SpvClient(SpvSessionProvider(store=FileSessionStore())) as spv:
    inbox = await spv.list_messages(30)
    print(inbox.authorized_cuis)  # every CUI/CNP your certificate may query
    result = await spv.request_report(
        ReportRequest(type_=ReportType.VECTOR_FISCAL, cui="12345678")
    )
    report = await spv.wait_for_report(result.request_id)  # PDF bytes

MCP server

The anafpy[mcp] extra ships a local stdio MCP server that wraps the clients as Cowork skills. The server is best-effort: configuring it — including registering your own OAuth application on ANAF's portal — is your responsibility, and the setup walkthrough walks you through every step. Run it host-side, where the token store written by anafpy auth login lives:

ANAFPY_CLIENT_ID=... ANAFPY_CLIENT_SECRET=... ANAFPY_CIF=... \
  python -m anafpy.mcp        # or the `anafpy-mcp` console script

The surface is read-first: freely callable read tools (the anaf_* public lookups and efactura_validate need no login at all; auth_status, the e-Factura inbox — which can also save the signed ZIP and ANAF's official PDF rendering to disk — and the e-Transport reads need the one-time login) plus two-step gated filing for both services: every *_prepare* tool returns a preview + a single-use confirmation token bound to the exact document and CIF, and the matching *_submit files only with that token and confirm=True. e-Factura filings arrive either as the XML your invoicing software produced (efactura_prepare, the recommended path) or composed from structured fields (efactura_prepare_invoice — no invoicing software needed); e-Transport declarations are composed from structured fields by etransport_prepare*. The spv_* tools read the SPV mailbox (list, download PDFs to disk, request official reports and await their delivery) over the certificate session established by anafpy spv login — read-only, no submissions. The compiled ANAF reference is served as read-only resources, and workflow playbooks as MCP prompts — etransport-declare, which takes a declaration from any source (an email, a PDF invoice, a CMR) through extract → prepare → your approval → submit → UIT, and personal-income-summary, which pulls a person's realized income from the SPV income certificates into a per-year summary (with an optional Excel workbook). See the tools overview and workflow skills for the full picture.

Register the server with any MCP client — e.g. with Claude Code, from a source checkout (locked deps, no PyPI needed):

claude mcp add anafpy \
  -e ANAFPY_CLIENT_ID=... -e ANAFPY_CLIENT_SECRET=... -e ANAFPY_CIF=... \
  -- uv run --directory /path/to/anafpy --frozen --extra mcp anafpy-mcp

No credentials yet? The server still starts; the public anaf_* lookups (registries, financial statements) and efactura_validate are fully usable. The remaining e-Factura / e-Transport tools unlock once you set the credentials and run the one-time anafpy auth login in a terminal.

Development

uv sync --all-extras
uv run pytest                              # respx-mocked, credential-free
uv run ruff check . && uv run ruff format --check .
uv run mypy                                # strict
ANAFPY_LIVE=1 uv run pytest -m live        # opt-in: live smoke against real ANAF

The live marker re-confirms wire shapes against real ANAF endpoints and is skipped by default (not a gate). It covers the public services (no credentials needed) plus, with .env credentials and an anafpy auth login token store, the authenticated TEST environment: read-only shape checks and two roundtrips that actually file a test document — a minimal CIUS-RO invoice (e-Factura) and a domestic transport declaration composed via the flat authoring models (e-Transport). The roundtrips target TEST only, never production.

Models under efactura/ubl/ and etransport/schema/ are generated (via scripts/generate_*.py from vendored XSDs in schemas/) and must not be hand-edited. See CLAUDE.md for repository conventions.

License

Apache-2.0. Independent / unofficial — not affiliated with ANAF.

anafpy is free to use and provided as-is, with no warranty: it moves documents to and from ANAF, it does not give tax advice, and filing outcomes are your responsibility. The MCP server is best-effort — configuring it, provisioning your own OAuth application on ANAF's portal, and holding the qualified certificate are up to you (the setup walkthrough covers all of it).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

anafpy-0.3.1.tar.gz (3.6 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

anafpy-0.3.1-py3-none-any.whl (463.1 kB view details)

Uploaded Python 3

File details

Details for the file anafpy-0.3.1.tar.gz.

File metadata

  • Download URL: anafpy-0.3.1.tar.gz
  • Upload date:
  • Size: 3.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for anafpy-0.3.1.tar.gz
Algorithm Hash digest
SHA256 adecd4f6d20653470f0ec7f49c4a1ec480322bbbadf27cc63aa1f56957181d78
MD5 f9ec7d167d602a5d4e7b68ef6a1a25df
BLAKE2b-256 b2cb19c0254a4f8cd329a5ecfe2b7c198f8bb91970ab83a2a0a62ac42efc9e8b

See more details on using hashes here.

Provenance

The following attestation bundles were made for anafpy-0.3.1.tar.gz:

Publisher: release.yml on robert-malai/anafpy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file anafpy-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: anafpy-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 463.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for anafpy-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 72767912ad7069306b319d63ae30cbc025f53cf4b75c66cbecc2465089b4a231
MD5 0341ca0124324fc473b021e053d21d45
BLAKE2b-256 0fcf82a979c518c719df1000c2fe6cac2edd362efc5e424d4c64d1d27c9ccd9a

See more details on using hashes here.

Provenance

The following attestation bundles were made for anafpy-0.3.1-py3-none-any.whl:

Publisher: release.yml on robert-malai/anafpy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page