Python
useyona-einvoice is the official SDK for Python 3.10 and later. It is fully typed: every request and response dictionary has a TypedDict with the API’s field names, so your editor and type checker know the shape of what you send and receive.
Install
pip install useyona-einvoiceQuick start
Create one in the Yona app under API keys, with the invoicing preset; it starts with sk_test_. The SDK needs nothing else: no host and no environment setting, because the key chooses the sandbox or the live service.
import os
from useyona.einvoice import EInvoice
client = EInvoice(api_key=os.environ["YONA_API_KEY"])
# 1. A buyer
buyer = client.buyers.create({
"name": "Acme Nigeria Ltd",
"taxId": "12345678-0001",
"email": "accounts@acme.ng",
"partyType": "company",
"address": {"line1": "1 Marina", "city": "Lagos", "country": "NG"},
})
# 2. A saved item
item = client.items.create({
"name": "Laptop",
"itemType": "goods",
"hsnCode": "8471.30",
"productCategory": "Machinery",
"unitCode": "EA",
"unitPriceMinor": "45000000",
"currency": "NGN",
"taxCategory": "STANDARD_VAT",
})
# 3. A draft invoice
invoice = client.invoices.create({
"invoiceKind": "B2B",
"invoiceDate": "2026-10-05",
"currency": "NGN",
"buyerId": buyer["id"],
"lineItems": [{"itemId": item["id"], "quantity": 2}],
})
# 4. Finalise it and report it to the tax authority
client.invoices.finalise(invoice["id"])
client.submissions.submit(invoice["id"])
status = client.submissions.get_status(invoice["id"])Submitting is asynchronous: poll submissions.get_status or listen for the invoice.accepted webhook. The states are explained in Invoice lifecycle.
The client holds a connection pool: close it with client.close() or use with EInvoice(...) as client:.
Sandbox and live
An sk_test_ key works in the sandbox and an sk_live_ key in live, on the same host; client.mode tells you which. To make a deployment refuse a key of the wrong kind:
EInvoice(api_key=key, assert_mode="live")Responses and pagination
Methods return the API’s answer as a typed dictionary with the API’s field names (invoice["invoiceNumber"]). List methods return a page:
page = client.buyers.list({"limit": 50})page.data holds the buyers and page.pagination carries total, page, pageSize, totalPages, hasNext and hasPrevious.
paginate walks every page for you:
from useyona.einvoice import paginate
for buyer in paginate(client.buyers.list, {"limit": 100}):
print(buyer["name"])Received invoices and issued history return a Paginated object with the items under res.data["items"]. PDF downloads return a BinaryResponse with data (bytes), content_type and file_name.
Errors
A refused request raises an exception chosen by status:
| Status | Exception |
|---|---|
| 400, 422 | EInvoiceValidationError |
| 401 | EInvoiceAuthenticationError |
| 402 | EInvoiceInsufficientCreditsError |
| 403 | EInvoicePermissionError |
| 404 | EInvoiceNotFoundError |
| 409 | EInvoiceConflictError |
| 429 | EInvoiceRateLimitError |
| 5xx | EInvoiceServerError |
All extend EInvoiceApiError, which carries status, error_code, errors (per field), request_id and retry_after:
from useyona.einvoice import EInvoiceApiError, EInvoiceValidationError
try:
client.invoices.create(params)
except EInvoiceValidationError as err:
for e in err.errors:
print(e.field, e.message)
except EInvoiceApiError as err:
print(err.status, err.error_code, err.request_id)Branch on error_code, not on the message, and quote request_id when you write to support. Problems before the API answers are EInvoiceTimeoutError, EInvoiceConnectionError and EInvoiceConfigError; all SDK errors extend EInvoiceError. The codes are listed on each page of the API Reference.
Retries and idempotency
Reads, and writes that carry an Idempotency-Key, are retried automatically on network errors, timeouts, 408, 429 and 5xx, with backoff and respecting Retry-After. Other writes are never retried.
Where the API accepts an Idempotency-Key (creating an invoice, submitting, sending, downloading and so on) the SDK generates one per call, so a retry is never applied or charged twice. Pass your own to make a retry safe across restarts:
from useyona.einvoice import RequestOptions
client.invoices.create(params, RequestOptions(idempotency_key=f"order-{order_id}"))Tune the defaults with timeout and retry=RetryConfig(...) when creating the client, or per call with RequestOptions (timeout, headers, max_retries).
Webhooks
from useyona.einvoice.webhooks import verify_webhook
@app.post("/webhooks/yona")
def yona_webhook():
secret = os.environ["YONA_WEBHOOK_SECRET"]
event = verify_webhook(request.get_data(), request.headers, secret)
if event["type"] == "invoice.accepted":
...
return "", 200verify_webhook checks the Yona-Signature header over the raw body, accepts either signature while a rotated secret overlaps, and returns the event; a failed check raises EInvoiceWebhookError. Always pass the raw bytes, and deduplicate on event["id"]. sign_webhook_payload signs a payload the way Yona does, so you can test your handler locally.
Endpoints are created and their secrets rotated in the Yona app; the SDK can list and test endpoints and read and redeliver deliveries and events. See Webhooks for the events.
Configuration
EInvoice(api_key, ...) accepts:
| Option | Purpose |
|---|---|
assert_mode | "sandbox" or "live": refuse a key of the other kind |
timeout | per-attempt timeout in seconds (default 30) |
retry=RetryConfig(...) | max_retries (2), base_delay (0.5 s), max_delay (8 s), max_retry_after (60 s) |
headers | headers sent on every request |
http_client | your own httpx.Client (proxy, TLS) |
base_url | another gateway, for local development only; the key decides sandbox or live |
What it covers
Every module mirrors a tag of the API Reference, and the method names are the reference’s operation names in the casing of the language (issue_credit_note, get_status). Every method takes an optional trailing options: RequestOptions; request and query dictionaries are typed after the operation (CreateInvoiceBody, ListInvoicesQuery) in useyona.einvoice.types. Each method needs the capability shown on the key; the full_integration preset has them all.
| Module | What it does | Reference |
|---|---|---|
client.invoices, client.submissions, client.output, client.share_links | Create, edit and finalise drafts; submit, retry and track them; credit and debit notes; PDFs, sending and share links | Invoices |
client.buyers | The businesses and people you invoice, with tax-number checks and reachability | Buyers |
client.items | What you sell, with codes, units, prices and tax categories | Items |
client.reference | HS codes, reference lists, tax-ID lookup and invoice validation | Reference data |
client.sellers | Your organisation as the seller, read-only | Sellers |
client.inbound_invoices, client.issued_history | Invoices other businesses sent you, and your issued history retrieved from the authority | Received invoices |
client.organization, client.invoice_settings, client.tax_connection | Your organisation and its readiness, invoice settings, the tax connection | Organizations, Tax connection |
client.billing | Credits and usage, payments, statements and your subscription, in accounts, payments, sandbox, statements, subscriptions, transactions | Billing |
client.webhooks | List and test endpoints; read and redeliver deliveries and events, in endpoints, deliveries, events, event_types | Webhooks |
Account management (users, roles, API keys, purchases, webhook endpoint settings) is done in the Yona app, not through the API key.
Next steps
- Invoice lifecycle: the states a submission climbs through, and what each one means
- Webhooks: the events, and how deliveries are retried
- Going live: the checklist before you deploy a live key
- API Reference: every field, status code and error code