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
ProblemDetailExceptionwith atype_slugdiscriminator and generatedKnownProblemSlugsconstants. - 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file epistola_client-1.2.0.tar.gz.
File metadata
- Download URL: epistola_client-1.2.0.tar.gz
- Upload date:
- Size: 170.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4b301d221ca596fbd8aee912e6d24a72c9ad30e758a144a4bef8cda63b711390
|
|
| MD5 |
84b3c0bec759e72c4526dc2b84ab8070
|
|
| BLAKE2b-256 |
55286f33fb9d1da5cf2cf4ad3d18edc9f13209a76bc8861875f8ad748b704be5
|
Provenance
The following attestation bundles were made for epistola_client-1.2.0.tar.gz:
Publisher:
release.yml on epistola-app/epistola-contract
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
epistola_client-1.2.0.tar.gz -
Subject digest:
4b301d221ca596fbd8aee912e6d24a72c9ad30e758a144a4bef8cda63b711390 - Sigstore transparency entry: 2701131171
- Sigstore integration time:
-
Permalink:
epistola-app/epistola-contract@dea90610ffacf04ac866e7cdd0b188f03b96ae2b -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/epistola-app
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@dea90610ffacf04ac866e7cdd0b188f03b96ae2b -
Trigger Event:
release
-
Statement type:
File details
Details for the file epistola_client-1.2.0-py3-none-any.whl.
File metadata
- Download URL: epistola_client-1.2.0-py3-none-any.whl
- Upload date:
- Size: 574.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd88f37349c0f78cc8710194f2aca0bf4f6baf87cf5faf5136d10c90b4684606
|
|
| MD5 |
07d22a46e3ccc328fd8897e2a454fb4c
|
|
| BLAKE2b-256 |
ab38f639b99b6f0c0c45897c1bc478168a36bb36fd6d15436ce2f3f94a4243bc
|
Provenance
The following attestation bundles were made for epistola_client-1.2.0-py3-none-any.whl:
Publisher:
release.yml on epistola-app/epistola-contract
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
epistola_client-1.2.0-py3-none-any.whl -
Subject digest:
cd88f37349c0f78cc8710194f2aca0bf4f6baf87cf5faf5136d10c90b4684606 - Sigstore transparency entry: 2701131195
- Sigstore integration time:
-
Permalink:
epistola-app/epistola-contract@dea90610ffacf04ac866e7cdd0b188f03b96ae2b -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/epistola-app
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@dea90610ffacf04ac866e7cdd0b188f03b96ae2b -
Trigger Event:
release
-
Statement type: