Skip to main content

Unofficial Python SDK for the Vendus invoicing API (Portugal)

Project description

vendus

PyPI version Python versions CI License: MIT Typed Docs

The first Python SDK for Vendus, Portugal's AT-certified invoicing & POS platform. Issue invoices, invoice-receipts, and credit notes — in 5 lines of Python.

Documentation: English · Português | Examples | API Reference

Community SDK — not affiliated with or endorsed by Vendus. For official integrations, visit vendus.pt.

Installation

pip install vendus

Requires Python 3.9+. No additional dependencies beyond httpx and Pydantic v2.

Quick Start

from decimal import Decimal
from vendus import ClientData, DocumentItem, TaxCategory, VendusClient

client = VendusClient(api_key="your-api-key")

# Issue an invoice (FT)
invoice = client.documents.create_invoice(
    register_id=1,
    client=ClientData(name="Acme Lda", fiscal_id="123456789"),
    items=[
        DocumentItem(
            description="Consulting hours",
            quantity=Decimal("10"),
            unit_price=Decimal("75.00"),  # gross (includes tax)
            tax_category=TaxCategory.NORMAL,
        ),
    ],
    external_reference="ORD-2026-001",   # enables safe POST retries
)

print(invoice.number)   # "FT 2026/123"
print(invoice.atcud)    # AT communication code
print(invoice.qrcode)   # AT QR code payload

Async Support

Every method has an async variant — same client, _async suffix:

invoice = await client.documents.create_invoice_async(
    register_id=1,
    client=ClientData(name="Acme Lda", fiscal_id="123456789"),
    items=[...],
)

Three client shapes (one API)

The same create_invoice / create_invoice_receipt handles all three cases:

# 1. Client with NIF (typical B2B)
client.documents.create_invoice(
    register_id=1, items=[...],
    client=ClientData(name="Acme Lda", fiscal_id="123456789"),
)

# 2. Client without NIF (B2C, customer gave name only)
client.documents.create_invoice(
    register_id=1, items=[...],
    client=ClientData(name="João Silva"),
)

# 3. Final consumer (anonymous, no identification at all)
client.documents.create_invoice(register_id=1, items=[...])

Do NOT pass fiscal_id="999999990" — the SDK rejects it. For final consumer, omit client.

Credit Notes

A credit note (NC) credits a previously issued invoice. It is also the only way to reverse a fiscal invoice — FT/FR cannot be cancelled. The SDK fetches the original and credits its full set of lines, so you pass only the id and a reason:

credit_note = client.documents.create_credit_note(
    reference_document_id=invoice.id,
    reason="Customer return",
    external_reference="REFUND-2026-001",
)

Error Handling

All errors inherit from VendusError with typed subclasses:

from vendus import (
    VendusClient,
    ValidationError,
    AuthenticationError,
    AuthorizationError,
    NotFoundError,
    RateLimitError,
    APIError,
    TransportError,
)

try:
    invoice = client.documents.create_invoice(...)
except ValidationError as e:
    # Local validation — invalid NIF, missing items, forbidden 999999990
    print(e)
except AuthenticationError:
    # API key rejected (401)
    ...
except RateLimitError:
    # 429 — back off
    ...
except APIError as e:
    # Other Vendus errors — inspect e.status_code and e.response_body
    ...
except TransportError:
    # Network failure — timeout, DNS, connection refused
    ...

Configuration

client = VendusClient(
    api_key="your-api-key",
    base_url="https://www.vendus.pt/ws",  # production (default)
    timeout=30.0,                          # seconds
    max_retries=3,                         # GET retries; POST only if external_reference present
)

# Or load from VENDUS_API_KEY
client = VendusClient.from_env()

Supported Documents

Document Code Method Status
Fatura FT client.documents.create_invoice
Fatura Simplificada FS client.documents.create_simplified_invoice
Fatura-Recibo FR client.documents.create_invoice_receipt
Recibo RG client.documents.create_receipt
Nota de Crédito NC client.documents.create_credit_note
Orçamento OT roadmap
Guia de Transporte GT roadmap
Nota de Débito ND roadmap

Validation status

The wire format of every operation is asserted by unit tests (respx mocks), and validated against the real Vendus API — in test mode (mode=tests, non-fiscal) where possible, and once in real mode for the operations that test mode can't reach:

Operation Unit Live
create_invoice (FT) ✅ test + real
create_simplified_invoice (FS) ✅ test + real (credited by NC)
create_invoice_receipt (FR) ✅ test + real (+ payment variations)
create_receipt (RG) ✅ test + real (references an invoice)
create_credit_note (NC) ✅ real (full + partial; credits FT/FR/FS)
cancel ✅ refuses FT/FR/NC; cancels a receipt (RG)
list_payment_methods / list_registers / list / get ✅ read-only

Test-mode documents ("Modo de Formação") are non-fiscal and never reported to the AT, but Vendus stores them in a separate space — they can't be retrieved or credited via /documents/{id}, so credit notes are validated in real mode. Fiscal invoices (FT/FR/NC) can't be cancelled (reverse them with a credit note); a receipt (RG) can — both paths are live-verified.

Why This SDK

  • Fully typedmypy --strict passes, py.typed marker included. Full autocomplete in VS Code and PyCharm.
  • Sync + Async — one client, no separate packages. httpx powers both.
  • Decimal amounts — no floating-point surprises with money. Decimal("49.90"), not 49.8999.... Cent-precision matters for AT.
  • Safe retries — GET retries with exponential backoff + jitter. POST retries only when external_reference is set (Vendus's deduplication anchor). Without it, POST fails immediately to avoid duplicate fiscal documents.
  • PII redaction — fiscal_id, email, phone, address are automatically redacted from logs.
  • NIF validation — Portuguese NIF check digit verified locally before any API call.
  • Exception hierarchy — catch ValidationError for local issues, AuthenticationError for bad keys, RateLimitError for 429s, or VendusError for everything.
  • AT communication is opaque — Vendus is the certified party. Hash, ATCUD, and QR code come ready from Vendus; the SDK never talks to AT directly.

Development

git clone https://github.com/bilouro/vendus-python.git
cd vendus-python
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pre-commit install

Run checks (all must pass before a PR):

ruff check .          # lint
ruff format --check . # formatting
mypy src/             # type check (strict)
pytest                # unit tests + coverage (≥85% enforced)

Live integration tests hit the real Vendus API; they're excluded from pytest and auto-skip without credentials. Run them in test mode against a demo account:

export VENDUS_API_KEY=... VENDUS_REGISTER_ID=...
pytest -m integration --no-cov

Full developer guide — testing, the live-validation discipline, and how to add a document type — is on the Contributing page, alongside CLAUDE.md.

Contributing

See CONTRIBUTING.md. PRs welcome — especially for new document types.

Security

Report vulnerabilities privately — see SECURITY.md. Do not open public issues for security bugs.

License

MIT — use it however you want.

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

vendus-0.1.0.tar.gz (86.5 kB view details)

Uploaded Source

Built Distribution

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

vendus-0.1.0-py3-none-any.whl (27.9 kB view details)

Uploaded Python 3

File details

Details for the file vendus-0.1.0.tar.gz.

File metadata

  • Download URL: vendus-0.1.0.tar.gz
  • Upload date:
  • Size: 86.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for vendus-0.1.0.tar.gz
Algorithm Hash digest
SHA256 93539db56aa10b7e027a4cea2dde319fe4daea05d7453c3c989b48b99ca47f4c
MD5 40fd5f93f2d308d261144ab950a952a2
BLAKE2b-256 fbe90ef5866ee274a4b8e7b2b4062eedaed9fda3fd02c77f81427ff26903e73b

See more details on using hashes here.

Provenance

The following attestation bundles were made for vendus-0.1.0.tar.gz:

Publisher: release.yml on bilouro/vendus-python

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

File details

Details for the file vendus-0.1.0-py3-none-any.whl.

File metadata

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

File hashes

Hashes for vendus-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9331745c0b6ac7148ad5041a29ee21d800359bb9092e56a00e38cc42021602ec
MD5 0a8bc7bc4e2af0134f86ad1528158169
BLAKE2b-256 844912fd594df6613cc074bc131c283b3b59a7c586bb885a89669f8e8ab530a9

See more details on using hashes here.

Provenance

The following attestation bundles were made for vendus-0.1.0-py3-none-any.whl:

Publisher: release.yml on bilouro/vendus-python

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