Configuration¶
VendusClient options¶
from vendus import VendusClient
client = VendusClient(
api_key="your-key",
base_url="https://www.vendus.pt/ws", # production (default)
timeout=30.0, # seconds
max_retries=3, # GETs retry; POST only with external_reference
)
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key |
str |
— | Vendus API key (required) |
base_url |
str |
https://www.vendus.pt/ws |
API base. For Spain: https://www.vendus.es/ws |
timeout |
float |
30.0 |
HTTP timeout in seconds |
max_retries |
int |
3 |
Max retry attempts on eligible requests |
default_mode |
DocumentMode \| None |
None |
Mode applied to every create that omits mode (see below) |
Default document mode¶
mode defaults to the register's configured mode. New Vendus accounts default their register to test mode, so a create that omits mode silently issues a test (non-fiscal) document. Set default_mode once so every document is issued in the mode you intend:
from vendus import DocumentMode, VendusClient
client = VendusClient(api_key="...", default_mode=DocumentMode.NORMAL)
# every create_* now issues a real document unless you pass mode= explicitly
A per-call mode= always overrides the client default.
Registers¶
A register (caixa) is the POS configuration documents are issued from — you pass its id
as register_id. Registers are read-only via the API (created and configured in the
Vendus backoffice). List them to find the id and the working mode:
for r in client.documents.list_registers():
print(r.id, r.title, r.mode) # mode: "normal" or "tests"
From environment¶
client = VendusClient.from_env() # reads VENDUS_API_KEY
client = VendusClient.from_env(env_var="X") # custom variable
Retry policy¶
| Method | Retries? | When |
|---|---|---|
| GET | ✅ Always | 408, 429, 5xx, timeout |
| POST / PUT / PATCH | Conditional | Only if body contains external_reference |
| DELETE | ❌ Never | Cancellation must be explicitly idempotent on the app side |
The rule exists because Vendus does not offer idempotency keys. Without external_reference, a retried POST could create two fiscal documents (and burn two serial numbers). The external_reference parameter is the deduplication anchor Vendus accepts.
Recommendation: always pass external_reference when issuing documents.
invoice = client.documents.create_invoice(
register_id=1,
items=[...],
external_reference="ORD-2026-001", # idempotency
)
Logging¶
The SDK uses the vendus logger with an automatic PII redaction filter for fiscal_id, email, phone, mobile, address, postalcode, billing_email.
import logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("vendus").setLevel(logging.DEBUG)
Never bypass the logger
Don't print payloads directly with print(json) or another logger. Always use logging.getLogger("vendus") so redaction applies.
Testing¶
Vendus has no separate sandbox host — there is no test URL; every request goes to www.vendus.pt. What Vendus does have is a document-level test mode ("Modo de Formação/Testes"): documents issued in test mode have no fiscal validity and are never communicated to the AT.
Test mode is controlled two ways:
- Per register (caixa). Each register has a working mode (
normalortests), set in the Vendus backoffice (Configuração → Definições → Lojas e Caixas). New accounts default totests— a fresh account issues non-fiscal documents until you switch it tonormal. - Per request. Pass
modeto any create method:
from vendus import DocumentMode
invoice = client.documents.create_invoice(
register_id=1,
items=[...],
mode=DocumentMode.TESTS, # non-fiscal — not reported to the AT
)
Omit mode and Vendus uses the register's configured mode. A test document still gets a number and prints with a "sem validade fiscal" note. You can confirm a document was not reported to the AT because its tax_authority_id is empty (that field is only set once Vendus communicates the document to the AT).
What's verified, and what isn't
The behaviour above is taken from the official docs: the mode field (documents.doc), the per-register mode (registers.doc), and the help page Modo de Formação/Testes. What we have not yet confirmed against the live API is whether a per-request mode=tests is honoured on a register configured as normal. The reliable, verified path is to use a register that is itself in tests mode.
Recommended setup¶
- Unit tests — mock with
respx; never hit the real API. - Integration tests — run against a register in
testsmode, asserttax_authority_idis empty, and cancel what you create (seetests/integration/).