Skip to main content

Epistola Python Client

Python client library for the Epistola document generation API, generated from the Epistola OpenAPI contract with OpenAPI Generator (python / urllib3, pydantic v2 models).

It adds, on top of the stock generated client:

  • Client identity headers (User-Agent + X-EP-Node-Id) required on every request.
  • RFC 9457 problem-detail error handling — typed ProblemDetailException with a type_slug discriminator and generated KnownProblemSlugs constants.
  • Self-signed JWT authentication (JwtSigner), minting a fresh short-lived token per request.
  • Static API-key authentication via Authorization: ApiKey <key>.
  • NDJSON result collection (ResultCollector) with adaptive polling, compression, and partition-aware routing helpers.
  • Client-side JSON Schema validation of template data (TemplateSchemaValidator).

The package version tracks the Epistola contract version (info.version) and releases in lockstep with the Kotlin and .NET clients.

Install

pip install epistola-client

Prerelease snapshots (published on every push to main) are available from TestPyPI:

pip install -i https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ epistola-client

Quick start

from epistola_client import (
    EpistolaClientBuilder, ClientIdentity, JwtSigner, TemplatesApi,
)

identity = ClientIdentity.builder().node_id("my-pod-123").build()
signer = (
    JwtSigner.builder()
    .consumer_id("invoice-service")
    .private_key(JwtSigner.load_private_key("private.pem"))
    .build()
)

http = (
    EpistolaClientBuilder()
    .base_url("https://epistola.example.com/api")
    .identity(identity)
    .jwt_signer(signer)
    .install_problem_detail_handler()
    .build()
)

templates = TemplatesApi(http)
template = templates.get_template("acme", "invoices", "invoice")

For static tenant API keys, use .api_key("epk_...") instead of .jwt_signer(...). The legacy X-API-Key header remains supported for existing integrations, but is deprecated. Some Epistola Suite deployments may disable API-key authentication entirely; with .install_problem_detail_handler(), switch on e.type_slug == KnownProblemSlugs.API_KEY_AUTH_DISABLED and guide the caller to JWT auth.

Error handling

Install the opt-in problem-detail handler on the client (.install_problem_detail_handler()), then switch on type_slug against KnownProblemSlugs. The slug list is open — the API can introduce new problem types without a client release — so always keep a fallback branch and fall back to the HTTP status for unrecognized types.

from epistola_client import (
    TenantsApi, ProblemDetailException, KnownProblemSlugs,
)

try:
    tenants_api.get_tenant("acme")
except ProblemDetailException as e:
    match e.type_slug:
        case KnownProblemSlugs.NOT_FOUND:
            log.warning("tenant not found: %s", e.detail)
        case KnownProblemSlugs.FORBIDDEN:
            raise PermissionError(e.detail)
        case KnownProblemSlugs.VALIDATION_ERROR:
            for err in e.errors:               # field-level ValidationProblemDetail
                log.warning("%s: %s", err.var_field, err.message)
        case KnownProblemSlugs.DATA_MODEL_VALIDATION_ERROR:
            for example, failures in e.validation_errors.items():  # 422 data-model failures
                for f in failures:
                    log.warning("%s %s: %s", example, f.path, f.message)
        case _:
            raise                              # unknown / framework error — fall back to status

ProblemDetailException extends the generated ApiException, so existing except ApiException sites keep working. It exposes type, type_slug, title, problem_status, detail, errors, validation_errors, is_validation_problem, and is_data_model_validation_problem.

Result collection

from epistola_client import ResultCollector

collector = (
    ResultCollector.builder()
    .api_client(http)
    .tenant_id("acme")
    .handler(lambda result: print(result.request_id, result.status))
    .build()
)
collector.start()   # blocks; adaptive polling until collector.stop()

Development

The client is generated from the bundled spec. From the repository root:

make bundle                       # produce openapi.yaml
cd contracts/api/clients/python-urllib3
./generate.sh                     # stock client + derived sources
uv run pytest                     # run the tests

generated/ (stock client) and src/epistola_client/_generated/ (derived sources) are gitignored and rebuilt from the spec each time.

Download files

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

Source Distribution

epistola_client-1.0.1.tar.gz (169.2 kB view details)

Uploaded Source

Built Distribution

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

epistola_client-1.0.1-py3-none-any.whl (573.3 kB view details)

Uploaded Python 3

File details

Details for the file epistola_client-1.0.1.tar.gz.

File metadata

  • Download URL: epistola_client-1.0.1.tar.gz
  • Upload date:
  • Size: 169.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for epistola_client-1.0.1.tar.gz
Algorithm Hash digest
SHA256 a50fb15349fbffef449aa45aace9c6a04bb272b9063422a1db35b7002a2b699e
MD5 d0529327a01914418c3758df92a6b61b
BLAKE2b-256 9ce54148082e93450c29f0d5011f52e7f9282b299e50bce433c623881822ea09

See more details on using hashes here.

Provenance

The following attestation bundles were made for epistola_client-1.0.1.tar.gz:

Publisher: release.yml on epistola-app/epistola-contract

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

File details

Details for the file epistola_client-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: epistola_client-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 573.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for epistola_client-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 9a833dace295f600d8a3c9773d1d9f5c2d530496f2ca25c6b5abb249f2bc29ed
MD5 70174aac85aa1d9daafa8b880c0eedab
BLAKE2b-256 221bf968b722dbfd68099759e7333474ef849b4eefb2a65fdfa7c6d9fce1ed32

See more details on using hashes here.

Provenance

The following attestation bundles were made for epistola_client-1.0.1-py3-none-any.whl:

Publisher: release.yml on epistola-app/epistola-contract

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

Release history Release notifications | RSS feed

1.2.0

2 files

1.1.0

2 files

This release

1.0.1 This release

2 files

1.0.0

2 files

0.16.1

2 files

0.16.0

2 files

0.15.0

2 files

0.14.0

2 files

0.13.0

2 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