Skip to content

Python SDK

The official Python SDK wraps the public API with typed request/response models, automatic retries, and a clean exception hierarchy.

Terminal window
pip install zutax

Requires Python 3.11+. Pulls in httpx and pydantic (v2).

  1. Sign in to your ZUTAX workspace.
  2. Settings → API keysCreate key.
  3. 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.

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)

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 |

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.

client.invoices # transmit, create, get, get_by_external_id, list, cancel
client.parties # create, get, get_by_external_id, list
client.products # create, get, get_by_external_id, list
client.credit_notes # create, get, get_by_external_id, list

Each resource accepts either the matching Pydantic model or a plain dict (which gets validated on the way in).

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.

The SDK is open source: Zulaiy/zutax/zutax-sdks/python/. Issues and PRs welcome.