Metadata-Version: 2.4
Name: anafpy
Version: 0.1.1
Summary: Typed Python clients for Romania's ANAF web services: e-Factura, e-Transport, and the public no-auth registries.
Project-URL: Homepage, https://github.com/robert-malai/anafpy
Project-URL: Documentation, https://anafpy.readthedocs.io
Project-URL: Issues, https://github.com/robert-malai/anafpy/issues
Author: Robert Malai
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: anaf,cius-ro,e-Factura,e-Transport,einvoicing,romania,ubl
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Accounting
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: httpx>=0.27
Requires-Dist: keyring>=25
Requires-Dist: pydantic>=2.9
Requires-Dist: pyjwt>=2.8
Requires-Dist: tenacity>=8.3
Requires-Dist: tzdata; sys_platform == 'win32'
Requires-Dist: xsdata-pydantic>=24.5
Provides-Extra: mcp
Requires-Dist: mcp>=1.10; extra == 'mcp'
Requires-Dist: pydantic-settings>=2.4; extra == 'mcp'
Requires-Dist: python-frontmatter>=1.3; extra == 'mcp'
Description-Content-Type: text/markdown

# 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](https://claude.com) skills.

anafpy is a **thin transport client**, not invoicing software. For **e-Factura** you
bring invoice XML that your own invoicing system produced, and anafpy validates it,
files it with ANAF, tracks status, and pulls documents back — wrapping the XML you read
back (your filings and invoices suppliers issued to you) in a friendly **flat read
view** for easy display. (e-Factura is a filing endpoint; Romanian law presumes you
already run an invoicing system.) **e-Transport is different**: there is usually no
upstream software producing declaration XML, so anafpy translates ANAF's whole (small,
fully enumerated) schema into friendly typed models — 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](https://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 — read-only** (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`".

> Issuing outbound invoices is deliberately **not** here — that comes from your
> invoicing software, which files with ANAF directly. The e-Factura surface is read-only.

**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.

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](https://anafpy.readthedocs.io/en/latest/mcp/setup/) for the
full Claude Desktop + ANAF setup.

> Status: **early / alpha** (`0.x`), on PyPI as
> [`anafpy`](https://pypi.org/project/anafpy/). The OAuth2 auth layer,
> both async clients (with an easy-to-read flat view of downloaded documents), and the
> MCP server (structured e-Transport filing and a read-only e-Factura surface —
> inbox, download, validate) are implemented and tested.
> Validation is ANAF's own server-side `validare` endpoint — there is no local rule
> engine. See the [design notes](docs/design.md) for the full design and
> [`docs/anaf-reference/`](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`, `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 an easy-to-read `FlatInvoice` **view**.
- **`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.
- **Flat models** — `read_flat_invoice` projects UBL into a small, easy-to-read
  `FlatInvoice` **read view** (lossy, with a `complete` flag); anafpy never composes
  UBL from it. 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, with read-first, two-step gated e-Transport filing and a read-only
  e-Factura surface (see below).

CI (GitHub Actions, gates on 3.12 + 3.13) and tag-triggered PyPI publishing are in
place. (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](https://anafpy.readthedocs.io/en/latest/mcp/setup/). The
short version for developers:

From [PyPI](https://pypi.org/project/anafpy/):

```bash
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:

```bash
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):

```bash
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](https://anafpy.readthedocs.io/en/latest/library/auth/)
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:

```python
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:

```python
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](https://anafpy.readthedocs.io/en/latest/library/etransport/)
has the complete worked declaration.

The public registries need no auth at all:

```python
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)
```

## 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](https://anafpy.readthedocs.io/en/latest/mcp/setup/) walks you
through every step. Run it host-side, where the token store written by
`anafpy auth login` lives:

```bash
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 e-Transport**: `etransport_prepare*` composes the
declaration from structured fields and returns a preview + a single-use
confirmation token bound to the exact document and CIF; `etransport_submit` files
only with that token and `confirm=True`. **The e-Factura surface is read-only** —
outbound invoices come from your invoicing software, which files with ANAF itself.
The compiled ANAF reference is served as read-only resources, and workflow
playbooks (like `etransport-declare`, which takes a declaration from any source —
an email, a PDF invoice, a CMR — through extract → prepare → your approval →
submit → UIT) as MCP prompts. See the
[tools overview](https://anafpy.readthedocs.io/en/latest/mcp/tools/) and
[workflow skills](https://anafpy.readthedocs.io/en/latest/mcp/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):

```bash
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

```bash
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`](CLAUDE.md) for repository conventions.

## License

[Apache-2.0](LICENSE). 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](https://anafpy.readthedocs.io/en/latest/mcp/setup/) covers all
of it).
