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-0.15.0.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-0.15.0-py3-none-any.whl (572.5 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for epistola_client-0.15.0.tar.gz
Algorithm Hash digest
SHA256 9b5b1c7e167ddfc251e13d851c767ebd41aeb6f3493e04031a3ec81606276999
MD5 3c359874586fa8b85e331c11fb52f05f
BLAKE2b-256 cf83bcd88e1c680b7b0a7d06d383bdd54ad052f84aab3b17104b096f0977a683

See more details on using hashes here.

Provenance

The following attestation bundles were made for epistola_client-0.15.0.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-0.15.0-py3-none-any.whl.

File metadata

File hashes

Hashes for epistola_client-0.15.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7bd6fee135c6e4c906e40e96a84f31c71a4d895af79d7bc858e85bd9eb3883ac
MD5 45086b9638f84fc3f5988936abd9db73
BLAKE2b-256 9f71026932ed7d41a77fc77fd8023c41c2299fe4c9824be7503136e264a7bcc2

See more details on using hashes here.

Provenance

The following attestation bundles were made for epistola_client-0.15.0-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

1.0.1

2 files

1.0.0

2 files

0.16.1

2 files

0.16.0

2 files

This release

0.15.0 This release

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