Python SDK
The official Python SDK wraps the public API with typed request/response models, automatic retries, and a clean exception hierarchy.
Install
Section titled “Install”pip install zutaxpoetry add zutaxRequires Python 3.11+. Pulls in httpx and pydantic (v2).
Get an API key
Section titled “Get an API key”- Sign in to your ZUTAX workspace.
- Settings → API keys → Create key.
- Copy the key — you’ll only see it once.
Use a staging key against https://staging-api.getzutax.com until you’re
ready to go live.
Your first transmit
Section titled “Your first transmit”The composite transmit endpoint upserts the customer, creates the invoice,
signs the IRN, and submits to the Access Point Provider in one call. Use
idempotency_key so retries are safe.
from decimal import Decimal
from zutax import ( EmbeddedAddress, EmbeddedCustomer, InvoiceTransmitInput, TransmitLineInput, ZutaxClient,)
client = ZutaxClient( api_key="zk_test_...", base_url="https://staging-api.getzutax.com",)
payload = InvoiceTransmitInput( external_id="INV-2026-0042", issue_date="2026-05-03", due_date="2026-06-03", customer=EmbeddedCustomer( external_id="C-100", legal_name="Acme Industries Limited", tin="12345678-0001", address=EmbeddedAddress(city_name="Lagos", country_code="NG"), ), lines=[ TransmitLineInput( name="Consulting services — May 2026", quantity=Decimal("1"), unit_price_naira=Decimal("250000.00"), vat_rate=Decimal("7.5"), ) ],)
result = client.invoices.transmit(payload, idempotency_key="INV-2026-0042")print(result.invoice_id, result.irn)Errors
Section titled “Errors”Every HTTP error surfaces as a typed exception so you can branch cleanly:
from zutax import ( AuthError, ConflictError, NotFoundError, RateLimitError, ServerError, ValidationError, ZutaxError,)
try: client.invoices.transmit(payload)except ValidationError as e: # Server rejected the payload — e.errors is a structured list of # {loc, msg, type} dicts pointing at the bad fields. for err in e.errors: print(err["loc"], err["msg"])except RateLimitError as e: # The SDK already retried with backoff; the server is overloaded. print(f"Slow down, retry in {e.retry_after}s")except ZutaxError as e: # Catch-all for anything else (auth, server, network) print(f"Failed: {e.status_code} {e}")| Exception | When it fires |
|---|---|
| AuthError | 401 / 403 — bad API key, or key lacks scope |
| NotFoundError | 404 — wrong UUID or external_id |
| ValidationError | 400 / 422 — payload didn’t pass server validation |
| ConflictError | 409 — idempotency-key reuse with mismatched payload |
| RateLimitError | 429 — SDK already retried, gave up |
| ServerError | 5xx — SDK already retried, gave up |
| ZutaxError | base class; catch this if you want one handler |
Idempotency
Section titled “Idempotency”Pass a key per call (recommended for transmit):
client.invoices.transmit(payload, idempotency_key="INV-2026-0042")…or scope a key to a block when several calls share one logical operation:
from zutax import with_idempotency_key
with with_idempotency_key("INV-2026-0042"): client.invoices.transmit(payload)The server treats retries with the same key as the same invoice — replays return the original result instead of creating a duplicate.
Resources
Section titled “Resources”client.invoices # transmit, create, get, get_by_external_id, list, cancelclient.parties # create, get, get_by_external_id, listclient.products # create, get, get_by_external_id, listclient.credit_notes # create, get, get_by_external_id, listEach resource accepts either the matching Pydantic model or a plain dict
(which gets validated on the way in).
Reference connector
Section titled “Reference connector”A complete end-to-end example — read invoices from CSV, transmit each row
with an idempotency key — lives in the
zutax-sdks/reference-connectors/python-csv-source/
folder. Fork it as a starting point for your own custom-ERP integration.
Source
Section titled “Source”The SDK is open source: Zulaiy/zutax/zutax-sdks/python/. Issues and PRs welcome.