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.
Before you start
Section titled “Before you start”- Python 3.10 or later.
- An API key with the
render:create,render:readandtemplate:readscopes; see API keys and scopes. - A published template to render, such as one named
Invoice. See Build your first template. PDF_URLset to the address you open Studio at, where the API also answers, andAPI_KEYset to the key.
1. Install the package
Section titled “1. Install the package”pip install typefold-clientIts only dependency is httpx. A self-hosted server older than
the client refuses fields and endpoints it does not know; see
versioning.
2. Create a client
Section titled “2. Create a client”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.
3. Render a report
Section titled “3. Render a report”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(...).
Use asyncio
Section titled “Use asyncio”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 HTML or a template version
Section titled “Render HTML or a template version”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.
Render in the background
Section titled “Render in the background”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).
Render a batch
Section titled “Render a batch”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.
Retry safely
Section titled “Retry safely”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.
Handle errors
Section titled “Handle errors”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 email and SMS text
Section titled “Render email and SMS text”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, ))Reference
Section titled “Reference”Everything is in the typefold module.
Client options
Section titled “Client options”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. |
Methods
Section titled “Methods”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.
Request types
Section titled “Request types”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.
How data is sent
Section titled “How data is sent”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
{}.
Errors
Section titled “Errors”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.