Skip to content

Python client

typefold-client is the Python package for the Typefold API. It renders reports, HTML and template versions, email and SMS text, and batches, and handles render status, cancellation and downloads. The .NET, Python and TypeScript clients have the same features. For templates, assets, webhooks and administration, call the HTTP API.

  • Python 3.10 or later.
  • An API key with the render:create, render:read and template:read scopes; see API keys and scopes.
  • A published template to render, such as one named Invoice. See Build your first template.
  • PDF_URL set to the address you open Studio at, where the API also answers, and API_KEY set to the key.
Terminal window
pip install typefold-client

Its only dependency is httpx. A self-hosted server older than the client refuses fields and endpoints it does not know; see versioning.

import os
from typefold import TypefoldClient
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])

Create one client per API key and reuse it. Call client.close() when your application stops, or use the client in a with block.

render_report renders the published version of a template, found by name, through the Reports API and returns the file’s bytes:

from pathlib import Path
from typefold import RenderReportRequest, TemplateRef
pdf = client.render_report(
RenderReportRequest(
template=TemplateRef.by_name("Invoice"),
data={"number": "2026-0042", "customer": {"name": "Ada Lovelace"}},
)
)
Path("invoice.pdf").write_bytes(pdf)

Open invoice.pdf: you should see the template filled with your data, so {{customer.name}} prints Ada Lovelace.

For an Excel file from a spreadsheet template, pass format=ReportFormat.XLSX. To change the paper, margins or PDF standard for one render, pass pdf=ReportPdfOptions(...).

AsyncTypefoldClient has the same methods as coroutines. Use it in FastAPI, aiohttp and other asyncio applications:

import os
from typefold import AsyncTypefoldClient
async with AsyncTypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"]) as client:
pdf = await client.render_report(request)

Outside an async with block, close it with await client.aclose(). Cancelling an asyncio task stops your code waiting, not the render; call cancel_render for that.

render renders through the Renders API: HTML you send, or a published template version. It creates the render, waits for it and downloads the PDF.

from typefold import DirectRenderRequest, HtmlDocument, PageSize, RenderSettings
pdf = client.render(
DirectRenderRequest(
document=HtmlDocument(
html="<h1>Invoice 42</h1><p>Total: 1,250.00 EUR</p>",
css=["h1 { color: #0b5394; }"],
),
settings=RenderSettings(page_size=PageSize.LETTER),
)
)

To render a published template version, pass its ID and the data:

from typefold import TemplateVersionRenderRequest
ticket = client.render(
TemplateVersionRenderRequest(template_version_id=version_id, data={"ticket": {"number": "TK-1"}})
)

render waits two minutes by default; pass timeout=300 to wait five. Without settings, the page is A4 with 10 mm margins.

For long or numerous renders, create the render, store its ID, and pick up the PDF later:

from typefold import RenderState
# 1. Create. Returns as soon as the render is queued.
render = client.create_render(request)
save_render_id(order_id, render.id)
# 2. Later, in a job or after a webhook arrives:
render = client.get_render(render_id)
if render.state == RenderState.SUCCEEDED:
pdf = client.download(render)

To wait in the same process instead, call client.wait_for_render(render), which polls until the render finishes. To be called when it finishes, use webhooks. If you no longer need a render, call client.cancel_render(render.id).

create_batch creates up to 100 renders in one request; if any item is invalid, none is created. wait_for_batch waits until every item has finished, and download_batch returns one PDF of every item in order:

batch = client.create_batch(
[TemplateVersionRenderRequest(template_version_id=version_id, data=ticket) for ticket in tickets],
idempotency_key="tickets-2026-10-12",
)
batch = client.wait_for_batch(batch)
if batch.counts.succeeded == batch.total:
pdf = client.download_batch(batch.id)

A failed item does not stop the others. batch.items gives each item’s render id and state, to download or cancel it as a single render. See Render many documents at once.

render_report, create_render, render and create_batch take an optional idempotency_key. Retrying with the same key returns what the first call created instead of rendering again, so a network failure never produces a duplicate document:

key = f"invoice-{invoice.id}-v{invoice.revision}"
pdf = client.render_report(request, idempotency_key=key)

Use a key that names the document. For reports, keys apply to PDF only. The rules are in idempotency keys.

The client resends a request the API refused with 429 RATE_LIMITED, up to three times by default. When those run out, and for every other API error, it raises TypefoldError; its code says what went wrong and retryable whether trying again can help:

from typefold import TypefoldError
try:
pdf = client.render_report(request, idempotency_key=key)
except TypefoldError as error:
if error.code == "RENDER_TIMEOUT":
# Still rendering. Follow it later with client.get_render(error.error.render_id).
queue_follow_up(error.error.render_id)
elif error.retryable:
# RATE_LIMITED, a 5xx, and similar: back off and retry with the same idempotency key.
raise
else:
# Not worth retrying as is: fix the request, template or key.
log.error("PDF render failed: %s", error.code)

Every code is in the error reference.

render_string fills a template string and returns the text, with no PDF. The formats and engines are explained in Render email and SMS text.

from typefold import RenderStringRequest, StringEngine, StringFormat
body = client.render_string(
RenderStringRequest(
template="{{#if (isEqual Status 'paid')}}Paid{{else}}Due{{/if}}: {{Booking.Code}}",
data=model,
engine=StringEngine.HANDLEBARS,
helpers="function isEqual(a, b) { return a === b; }",
format=StringFormat.HTML,
)
)

Everything is in the typefold module.

TypefoldClient(base_url, api_key, *, http_client=None, max_rate_limit_retries=3):

Option Default Meaning
base_url Required The address you open Studio at, such as https://pdf.example.com. A base path such as /pdf is kept.
api_key Required Your API key.
http_client A new one, 120 s timeout A shared httpx.Client (httpx.AsyncClient for the async client), not closed with this client. Keep its timeout above the Reports API wait of 95 seconds.
max_rate_limit_retries 3 How many times a request refused with 429 is sent again, after the wait the API asks for. 0 raises the first one.

AsyncTypefoldClient has the same methods as coroutines.

Method Returns Scopes
render_report(request, *, idempotency_key) bytes render:create, template:read, plus render:read for PDF
render_string(request) str render:create
list_reports() list[ReportSummary] template:read
get_report_data_schema(format, template) dict (JSON Schema) template:read
create_render(request, *, idempotency_key) Render render:create
get_render(id) Render render:read
wait_for_render(render, *, timeout) Render render:read
download(render) bytes render:read, only when it refreshes the link
render(request, *, idempotency_key, timeout) bytes render:create, render:read
cancel_render(id) None render:cancel
create_batch(items, *, idempotency_key) RenderBatch render:create
get_batch(id) RenderBatch render:read
wait_for_batch(batch, *, timeout) RenderBatch render:read
download_batch(id) bytes render:read

wait_for_render and wait_for_batch poll every 1 to 5 seconds, and keep polling through a dropped connection or a retryable error such as RATE_LIMITED. They raise RENDER_TIMEOUT after timeout seconds: two minutes by default for a render, ten minutes for a batch. download re-reads the render first when its download link is about to expire.

Requests are dataclasses with the API’s fields in snake_case. Fields you don’t set are not sent.

Type Use it for
RenderReportRequest render_report: template, data, format (default pdf), locale (default en-US), pdf, image and test.
ReportPdfOptions A report’s pdf: paper, margins, page ranges, metadata, PDF/A, PDF/UA, attachments, passwords, signature, other templates’ pages and watermark.
TemplateVersionRenderRequest render, create_render and create_batch: a published template version, by template_version_id, with data.
DirectRenderRequest render, create_render and create_batch: your HTML, as an HtmlDocument, with RenderSettings.
RenderSettings A direct render’s page: size (default A4), orientation, margins (default 10 mm each), locale, page ranges, metadata, PDF/A, PDF/UA.
RenderStringRequest render_string: template, data, format, engine and helpers.
PdfMetadata The document’s title, author, language and other properties.
PdfEncryption Passwords, and the PdfPermissions readers get without the owner password.
PdfSignature A digital signature. certificate is the name of a certificate under Manage → Signing certificates in Studio; PdfSignatureField makes it visible.
PdfAttachment A file embedded in the PDF, such as e-invoice XML.
PdfOperation Another template’s or a PDF file’s pages added to a report, before, after or drawn onto its pages (PdfOperationType).
PdfWatermark Text such as DRAFT across every page; see watermarks.
ImageOptions The page, width and JPEG quality of an image, with format set to RenderFormat.PNG, JPEG or WEBP.

TemplateVersionRenderRequest and DirectRenderRequest also take metadata, attachments, encryption, signature, watermark, format, image and test, and TemplateVersionRenderRequest takes locale. test=True makes a free, watermarked test render.

TemplateRef.by_name, by_id and by_shortid pick the template; by_shortid is for templates you migrated. PageSize has A3, A4, A5, LETTER and LEGAL, or use PageSize(width_mm, height_mm). What each field does is in the API reference and PDF standards and security.

data is sent with its keys unchanged, so {"CustomerName": "Ada"} is {{CustomerName}} in the template, and None is sent as null. Dates and datetimes are written in ISO 8601, Decimal values as numbers, UUIDs as strings and dataclasses as objects. A NaN or infinity raises ValueError before the request is sent. A template version or string render without data sends {}.

TypefoldError has status_code (None when the client raised the error itself), code, retryable and error, the API’s error object with message, details, diagnostics and render_id. The client raises these codes itself:

Code When
RENDER_TIMEOUT render, wait_for_render or wait_for_batch ran out of time. Retryable. For a render, render_id names it to follow.
RENDER_FAILED A render was cancelled while render waited. A failed render raises with the error it recorded instead.
HTTP_ERROR A response that was not the API’s error format, such as a proxy’s error page or a failed download. Retryable for 429 and 5xx.

download raises ValueError for a render that has no file, because it has not succeeded.