Skip to main content

Python client SDK for the Xingen e-invoice validation API

Project description

xingen

Python client SDK for the Xingen e-invoice validation API — submit UBL, CII, ZUGFeRD, and SAP IDoc/OData invoices for validation against EN16931, XRechnung, and Peppol.

Requires Python 3.10+. Built on requests and pydantic v2.

Status: v1, covering invoice submission/validation and API key management. Contacts and dashboard/user endpoints are not exposed (they're Firebase-auth-only on the backend).

Install

pip install xingen-sdk

(The distribution is named xingen-sdk on PyPI since xingen was already taken by an unrelated project, but the import name is still xingen: from xingen import XingenClient.)

Authentication

Every request needs an API key (xgn_live_... for production, xgn_test_... for sandbox — sandbox requests never count toward quota). Create one from the Xingen dashboard or via client.api_keys.

from xingen import XingenClient

client = XingenClient(api_key=os.environ["XINGEN_API_KEY"])

XingenClient holds one connection-pooled requests.Session — construct it once and reuse it, don't rebuild it per request. base_url=... overrides the default https://app.xingen.de/api, useful for self-hosted or local (./gradlew bootRun, port 10001) testing.

Validate a file

Every validate/submit endpoint is asynchronous — the backend queues the invoice and returns immediately. Use a *_and_wait helper to submit and poll for the result in one call:

from xingen.models.enums import InvoiceStatus, ValidationProfile

result = client.invoices.validate_file_and_wait("invoice.xml", ValidationProfile.XRECHNUNG)

if result.status is InvoiceStatus.VALIDATED and result.validation_result.valid:
    print("Valid!")
else:
    for error in result.validation_result.errors:
        print(f"{error.severity}: {error.message} ({error.field})")

PollOptions controls the backoff (initial_interval, max_interval, backoff_multiplier), the overall timeout, and an optional cancellation_check. A failed validation is not an exception — it's a completed API call that found the invoice invalid, so *_and_wait returns normally with validation_result.valid == False. Only a transport failure, cancellation, or timeout raises.

from xingen.invoices import PollOptions

options = PollOptions(initial_interval=0.3, max_interval=3.0, timeout=30.0)
result = client.invoices.validate_file_and_wait("invoice.xml", ValidationProfile.XRECHNUNG, options)

If you'd rather manage polling yourself, use the low-level pair:

submitted = client.invoices.validate_file("invoice.xml", ValidationProfile.EN16931)
# ... later ...
record = client.invoices.get(submitted.id)

validate_idoc / validate_idoc_and_wait work the same way for SAP IDoc XML files. Both, and validate_file/validate_file_and_wait, also accept (filename, content_bytes) tuples if you already hold the file bytes in memory instead of a path.

Submit a structured invoice (JSON)

from datetime import date
from decimal import Decimal

from xingen.invoices import AddressInput, InvoiceSubmission, LineInput, PartyInput, PaymentMeansInput

submission = InvoiceSubmission(
    invoice_number="INV-2024-0042",
    issue_date=date(2024, 3, 15),
    currency="EUR",
    buyer_reference="991-12345-06",
    validation_profile=ValidationProfile.XRECHNUNG,
    supplier=PartyInput(
        name="Acme GmbH",
        vat_id="DE123456789",
        address=AddressInput(city="Berlin", country_code="DE"),
    ),
    buyer=PartyInput(
        name="Buyer Co",
        leitweg_id="991-12345-06",
        address=AddressInput(country_code="DE"),
    ),
    lines=[
        LineInput(
            description="Software License Q1",
            quantity=Decimal("5"),
            unit="C62",
            price=Decimal("199.00"),
            tax_rate=Decimal("19"),
        )
    ],
    payment_means=[
        PaymentMeansInput(type_code="58", credit_transfer_account_id="DE89370400440532013000")
    ],
)

result = client.invoices.submit_and_wait(submission)

InvoiceSubmission has full parity with the backend's domain model — every invoice type it can validate, it can also submit, including non-standard VAT categories (exempt/reverse-charge/export via LineInput.tax_category_code + exemption_reason/exemption_reason_code), payee/ tax_representative, delivery, invoice_period, preceding_invoice_references (for credit notes), and the full BT-11..BT-19 document reference fields. See xingen.invoices.models for the complete set of nested types.

SAP S/4HANA OData supplier-invoice payloads are supported as a thin passthrough — pass raw JSON or a dict rather than a fully typed model:

client.invoices.submit_odata(raw_odata_json, ValidationProfile.EN16931)

Extract an invoice from a PDF (AI)

Upload a plain invoice PDF — including scanned/image-based PDFs — and let the backend extract structured fields with Claude. Works exactly like the other submit endpoints: async, so use extract_invoice_and_wait or the low-level extract_invoice/get pair.

from xingen.models.enums import ExtractionModelTier

result = client.invoices.extract_invoice_and_wait(
    "scanned-invoice.pdf",
    ValidationProfile.EN16931,
    ExtractionModelTier.FAST,  # or ACCURATE -- higher accuracy, Pro subscription required
)

If the extraction missed a field or validation flagged something, correct it with a JSON merge-patch (RFC 7386) and re-validate synchronously — only invoices that finished processing (VALIDATED or FAILED_VALIDATION) can be corrected. Array fields (lines, paymentMeans, allowanceCharges, taxBreakdowns) are replaced wholesale when present in the patch. patch_invoice accepts a dict or a raw JSON string, the same as submit_odata:

corrected = client.invoices.patch_invoice(
    result.id, {"currency": "EUR", "buyerReference": "991-12345-06"}
)

To find out which fields the backend fills in automatically per profile (so you know what not to prompt the user for):

auto_filled = client.invoices.get_auto_filled_fields()
for field in auto_filled["EN16931"]:
    print(f"{field.field} -> {field.value} ({field.reason})")

List and retrieve invoices

page = client.invoices.list(0, 20, "createdAt,desc")

# or, to walk every invoice without managing page indices yourself:
for record in client.invoices.list_all(50):
    print(record.id, "->", record.status)

one = client.invoices.get("inv_01HXYZ")

Download results

pdf = client.invoices.download_pdf(id)          # ZUGFeRD PDF with embedded XML
idoc_xml = client.invoices.download_idoc_xml(id)  # SAP IDoc XML

API keys

from xingen.apikeys import CreateApiKeyRequest

created = client.api_keys.create(CreateApiKeyRequest(name="Production CI", sandbox=False))
print("Store this now, it's shown only once:", created.raw_key)

keys = client.api_keys.list()
client.api_keys.revoke(created.id)

Error handling

All SDK exceptions extend XingenException. HTTP errors map to typed subclasses of ApiException:

Exception Status Notes
AuthenticationException 401 Missing or invalid API key
PermissionException 403 Resource exists but isn't owned by the caller
NotFoundException 404
ValidationRequestException 400 .field_errors has details for request-body validation failures
QuotaExceededException 429 Monthly request quota exhausted
ApiException other 4xx/5xx Fallback; .status_code / .raw_body always available
from xingen.error import QuotaExceededException, ValidationRequestException, XingenException

try:
    client.invoices.submit(submission)
except ValidationRequestException as e:
    for field, message in e.field_errors.items():
        print(f"{field}: {message}")
except QuotaExceededException:
    print("Quota exceeded — upgrade or wait for the next billing period")
except XingenException as e:
    print(f"Request failed: {e}")

Design notes

  • No automatic retries. Retrying a submit() after a client-side timeout is unsafe without idempotency keys, which the API doesn't support yet — a retried submit could create a duplicate invoice. Handle retries at the call site if you need them.
  • Monetary and quantity fields are Decimal, never float. JSON is parsed through Python's stdlib json module with parse_float=Decimal rather than pydantic's native (float64-lossy) JSON parser, so exact literal precision is preserved end to end.

Contributing

pip install -e ".[dev]"
ruff check .
mypy src/xingen
pytest

Tests run against a real (loopback) http.server.HTTPServer, not a mocking framework — no network calls leave the machine, and no external test-server dependency is required.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

xingen_sdk-0.2.0.tar.gz (28.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

xingen_sdk-0.2.0-py3-none-any.whl (25.9 kB view details)

Uploaded Python 3

File details

Details for the file xingen_sdk-0.2.0.tar.gz.

File metadata

  • Download URL: xingen_sdk-0.2.0.tar.gz
  • Upload date:
  • Size: 28.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for xingen_sdk-0.2.0.tar.gz
Algorithm Hash digest
SHA256 876f793b03767f0fc29501e93dc01057efad86c478007aabde6c4974e0da1af9
MD5 89f6d491f1c1cef76beef4e538719423
BLAKE2b-256 90dde54c10517fcf4c1241ff53a8d6448e84c2ea3c9bbad2770efa73adcc0292

See more details on using hashes here.

Provenance

The following attestation bundles were made for xingen_sdk-0.2.0.tar.gz:

Publisher: publish.yml on nko-technologies/xingen-python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file xingen_sdk-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: xingen_sdk-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 25.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for xingen_sdk-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1af5aea607b7e852bbedf15e5bc7e14973fcee51c2f47c152ceeebb6b1698f9a
MD5 a5aa121ac9a1fd0b23e5e02566147232
BLAKE2b-256 b3cfdf59c644109372cc9e1ff943dd2f27f306b51cb436c43a57f3ed36f9f8eb

See more details on using hashes here.

Provenance

The following attestation bundles were made for xingen_sdk-0.2.0-py3-none-any.whl:

Publisher: publish.yml on nko-technologies/xingen-python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page