Skip to main content

beliq

Official Python SDK for the beliq e-invoicing compliance API. Generate, validate, parse, and convert EN 16931 invoices (XRechnung, ZUGFeRD, Factur-X, Peppol BIS) against authority-pinned, nightly-drift-checked rules.

beliq produces and checks the compliant document. Transmission (Peppol, PDP, KSeF, SDI), archiving, and tax-authority reporting stay with your access point.

Install

pip install beliq

Requires Python >= 3.10.

Quick start

from beliq import Beliq

beliq = Beliq(api_key="blq_...")

# Account, plan, and quota context (no quota cost).
account = beliq.me()

# Generate an XRechnung document from an EN 16931 invoice.
generated = beliq.generate(
    standard="xrechnung",
    verify=True,
    invoice={
        "number": "INV-2026-001",
        "issueDate": "2026-01-15",
        "currencyCode": "EUR",
        "seller": {"name": "Seller GmbH", "address": {"city": "Berlin", "postalCode": "10115", "countryCode": "DE"}},
        "buyer": {"name": "Buyer GmbH", "address": {"city": "Munich", "postalCode": "80331", "countryCode": "DE"}},
        "lines": [
            {"description": "Consulting", "quantity": 10, "unitCode": "HUR", "unitPrice": 100, "lineTotal": 1000, "vatRate": 19, "vatCategoryCode": "S"}
        ],
        "totalNetAmount": 1000,
        "totalTaxAmount": 190,
        "totalGrossAmount": 1190,
    },
)
print(generated.xml, generated.meta.schematron_version)

# Validate any document against authority-pinned rules.
result = beliq.validate(generated.xml, format="auto")
if not result.valid:
    for issue in result.errors:
        print(issue.rule_id, issue.message)

Authentication

Create an API key in the beliq dashboard under API Keys:

Beliq(api_key="blq_...")                    # sends X-API-Key (default)
Beliq(api_key="blq_...", auth="bearer")      # sends Authorization: Bearer
Beliq(api_key="blq_...", base_url="https://staging.beliq.eu")

Timeouts and retries

The client retries transient failures for you, so you do not have to reimplement backoff around it.

Beliq(
    api_key="blq_...",
    timeout=90.0,     # per-attempt deadline in seconds (default)
    max_retries=3,    # extra attempts after the first (default)
)

Only 429, 502 and 503 are retried, honouring the server's Retry-After with jitter. beliq refunds the document's quota unit on a 503, so a retry never costs you a second document.

504 and a client-side timeout are deliberately not retried: both mean the work may still be running on beliq's side, so retrying risks producing a second document rather than recovering the first.

A 429 carrying QUOTA_EXCEEDED is not retried either. RATE_LIMITED and ACCOUNT_THROTTLED clear on their own, but a spent monthly allowance only returns when your billing window turns, so the error is raised straight away and names the cause instead of sleeping against it.

The default deadline is generous because beliq runs the full Schematron rule set over each document, and a generate or validate can legitimately take tens of seconds. If you lower it, keep it above the latency you actually see: a deadline shorter than the server's own turns completed work into an unknown outcome. Pass max_retries=0 to handle retrying yourself. If you supply your own httpx.Client, its timeout is used as-is and timeout is ignored.

Async

AsyncBeliq mirrors the sync client with await:

import asyncio
from beliq import AsyncBeliq

async def main():
    async with AsyncBeliq(api_key="blq_...") as beliq:
        result = await beliq.validate(open("invoice.xml", "rb").read(), format="auto")
        print(result.valid)

asyncio.run(main())

API

Method Endpoint Input Returns
me() GET /v1/me none AccountInfo (no quota cost)
generate(...) POST /v1/generate EN 16931 invoice dict GenerateResult
validate(document, ...) POST /v1/validate XML or PDF ValidationResult
parse(document, ...) POST /v1/parse XML or PDF ParseResult
convert(document, ...) POST /v1/convert XML or PDF ConvertResult

document accepts a str, bytes, or bytearray. The content type is sniffed from the bytes (PDF vs XML) unless you pass content_type=. generate and convert return the raw document content (bytes) plus the response-header metadata: meta.schematron_version, meta.pdf_kind, meta.source_format/meta.target_format, meta.lost_elements, meta.conversion_tools, the ruleset fingerprint meta.ruleset_sha256 / meta.ruleset_artifacts, and meta.livemode. For an XML output, generate also decodes xml.

JSON responses are Pydantic models. Any field not explicitly typed (such as the per-country authority versions on a validation result) is preserved and accessible. Errors raise BeliqApiError with a typed .code, HTTP .status, and any .details:

from beliq import BeliqApiError

try:
    beliq.validate("not xml")
except BeliqApiError as err:
    print(err.code, err.status, err.message)

The seal: verify it yourself

Pass seal=True to generate to get the document back as a JSON envelope: the decoded content (bytes) plus its sha256 and the full validation_result. Hashing the returned bytes reproduces the returned hash, so you can prove which ruleset the document passed.

import hashlib

sealed = beliq.generate(standard="xrechnung", verify=True, invoice=invoice, seal=True)
assert hashlib.sha256(sealed.content).hexdigest() == sealed.sha256
print(sealed.validation_result.valid, sealed.meta.ruleset_sha256)

Without seal, generate returns the raw document body (unchanged, the default). The ruleset fingerprint (meta.ruleset_sha256, meta.ruleset_artifacts) is present in both modes.

Sandbox and live keys

A blq_test_ key is a sandbox key; a blq_live_ key is live. The client derives the mode from the key prefix before any request:

beliq = Beliq(api_key="blq_test_...")
beliq.livemode  # False for a sandbox key, True for a live key

Each generate / convert response also carries the authoritative mode from the server as meta.livemode.

Generate presets

LIVE_GENERATE_PRESETS is the curated set of public generate targets (matching beliq.eu's own generator). NLCIUS is a Peppol BIS profile rather than a standalone standard, so it is reachable here:

from beliq import LIVE_GENERATE_PRESETS

nlcius = next(p for p in LIVE_GENERATE_PRESETS if p.id == "nlcius")
beliq.generate(standard=nlcius.standard, profile=nlcius.profile, output=nlcius.output, invoice=invoice)

Development

python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
ruff check src tests
mypy
pytest                                   # unit tests (no network)
BELIQ_API_KEY=blq_xxx pytest tests/test_integration.py   # hits the live API; draws quota

tests/test_spec_contract.py reads the vendored openapi.json and fails if the error-code set, the core validate/seal fields, or the public option lists drift from the spec. Refresh the vendored spec with python scripts/sync_spec.py. A weekly workflow (scripts/check_live_drift.py) flags when the vendored spec falls behind the deployed API.

Publishing

Released to PyPI as beliq. Releases run from .github/workflows/release.yml via PyPI Trusted Publishing (OIDC, with attestations): push a v*.*.* tag to publish. No token is stored in the repo.

License

MIT

Release files for beliq 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for beliq 0.2.1
File Size Uploaded
beliq-0.2.1.tar.gz 52.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for beliq 0.2.1
File Interpreter ABI Platform
beliq-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 70.5 kB

Release files / beliq-0.2.1.tar.gz

Download URL beliq-0.2.1.tar.gz
Size 52.5 kB
Tags Source
SHA-256 checksum
How to use checksums
84e696319edc3fffc4d5c381cdda6402b068b14de9cb6def8cc035bbcaf94a2e
BLAKE2b-256 checksum
How to use checksums
a5f038c00f81e570acb9787523431e2638e0d0ab728045180975e8a1c18d3951
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release files / beliq-0.2.1-py3-none-any.whl

Download URL beliq-0.2.1-py3-none-any.whl
Size 18.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
36996c92b93940431ab5b80d14ee37765234a88f05f705397eb074460785687e
BLAKE2b-256 checksum
How to use checksums
da30fe3f9dec45b90ec5498da2c1d7183cb1a17c5bba18ace66e52256c89b662
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.0

2 release files

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page