Unofficial Python SDK for the Vendus invoicing API (Portugal)
Project description
vendus
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 typed —
mypy --strictpasses,py.typedmarker 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"), not49.8999.... Cent-precision matters for AT. - Safe retries — GET retries with exponential backoff + jitter. POST retries only when
external_referenceis 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
ValidationErrorfor local issues,AuthenticationErrorfor bad keys,RateLimitErrorfor 429s, orVendusErrorfor 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
93539db56aa10b7e027a4cea2dde319fe4daea05d7453c3c989b48b99ca47f4c
|
|
| MD5 |
40fd5f93f2d308d261144ab950a952a2
|
|
| BLAKE2b-256 |
fbe90ef5866ee274a4b8e7b2b4062eedaed9fda3fd02c77f81427ff26903e73b
|
Provenance
The following attestation bundles were made for vendus-0.1.0.tar.gz:
Publisher:
release.yml on bilouro/vendus-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vendus-0.1.0.tar.gz -
Subject digest:
93539db56aa10b7e027a4cea2dde319fe4daea05d7453c3c989b48b99ca47f4c - Sigstore transparency entry: 1681902257
- Sigstore integration time:
-
Permalink:
bilouro/vendus-python@9f6b41f1413a03bc3f14e3617c4649369d4f7f60 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bilouro
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9f6b41f1413a03bc3f14e3617c4649369d4f7f60 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9331745c0b6ac7148ad5041a29ee21d800359bb9092e56a00e38cc42021602ec
|
|
| MD5 |
0a8bc7bc4e2af0134f86ad1528158169
|
|
| BLAKE2b-256 |
844912fd594df6613cc074bc131c283b3b59a7c586bb885a89669f8e8ab530a9
|
Provenance
The following attestation bundles were made for vendus-0.1.0-py3-none-any.whl:
Publisher:
release.yml on bilouro/vendus-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vendus-0.1.0-py3-none-any.whl -
Subject digest:
9331745c0b6ac7148ad5041a29ee21d800359bb9092e56a00e38cc42021602ec - Sigstore transparency entry: 1681902340
- Sigstore integration time:
-
Permalink:
bilouro/vendus-python@9f6b41f1413a03bc3f14e3617c4649369d4f7f60 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bilouro
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9f6b41f1413a03bc3f14e3617c4649369d4f7f60 -
Trigger Event:
release
-
Statement type: